django-bom 1.262__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-bom might be problematic. Click here for more details.

Files changed (191) hide show
  1. bom/__init__.py +1 -0
  2. bom/admin.py +207 -0
  3. bom/apps.py +8 -0
  4. bom/auth_backends.py +47 -0
  5. bom/base_classes.py +31 -0
  6. bom/constants.py +217 -0
  7. bom/context_processors.py +9 -0
  8. bom/csv_headers.py +252 -0
  9. bom/decorators.py +32 -0
  10. bom/form_fields.py +59 -0
  11. bom/forms.py +1328 -0
  12. bom/helpers.py +367 -0
  13. bom/local_settings.py +35 -0
  14. bom/migrations/0001_initial.py +135 -0
  15. bom/migrations/0002_auto_20180908_2151.py +24 -0
  16. bom/migrations/0003_sellerpart_data_source.py +18 -0
  17. bom/migrations/0004_auto_20180911_0011.py +18 -0
  18. bom/migrations/0005_auto_20181007_1934.py +56 -0
  19. bom/migrations/0006_auto_20181007_1949.py +41 -0
  20. bom/migrations/0007_auto_20181009_0256.py +19 -0
  21. bom/migrations/0008_auto_20181030_0427.py +19 -0
  22. bom/migrations/0009_subpart_reference.py +18 -0
  23. bom/migrations/0010_auto_20181202_0733.py +23 -0
  24. bom/migrations/0011_auto_20181202_2113.py +22 -0
  25. bom/migrations/0012_partchangehistory.py +30 -0
  26. bom/migrations/0013_auto_20190222_1631.py +19 -0
  27. bom/migrations/0014_auto_20190223_2353.py +18 -0
  28. bom/migrations/0015_auto_20190303_1915.py +136 -0
  29. bom/migrations/0016_auto_20190405_2308.py +58 -0
  30. bom/migrations/0017_auto_20190616_1912.py +19 -0
  31. bom/migrations/0018_auto_20190616_2143.py +24 -0
  32. bom/migrations/0019_auto_20190624_1246.py +45 -0
  33. bom/migrations/0020_auto_20190627_0207.py +38 -0
  34. bom/migrations/0021_auto_20190627_0428.py +23 -0
  35. bom/migrations/0022_auto_20190811_2140.py +35 -0
  36. bom/migrations/0023_auto_20191205_2351.py +255 -0
  37. bom/migrations/0024_auto_20191214_1342.py +89 -0
  38. bom/migrations/0025_auto_20191221_1907.py +38 -0
  39. bom/migrations/0026_auto_20191222_2258.py +22 -0
  40. bom/migrations/0027_auto_20191222_2347.py +17 -0
  41. bom/migrations/0028_partrevision_displayable_synopsis.py +74 -0
  42. bom/migrations/0029_auto_20191231_1630.py +23 -0
  43. bom/migrations/0030_auto_20200101_2253.py +22 -0
  44. bom/migrations/0031_auto_20200104_1352.py +38 -0
  45. bom/migrations/0032_auto_20200126_1806.py +27 -0
  46. bom/migrations/0033_auto_20200203_0618.py +29 -0
  47. bom/migrations/0034_auto_20200222_0359.py +30 -0
  48. bom/migrations/0035_auto_20200303_0111.py +34 -0
  49. bom/migrations/0036_auto_20200303_0538.py +17 -0
  50. bom/migrations/0037_auto_20200405_1642.py +44 -0
  51. bom/migrations/0038_auto_20200422_0504.py +19 -0
  52. bom/migrations/0039_auto_20200929_2315.py +41 -0
  53. bom/migrations/0040_alter_organization_currency.py +19 -0
  54. bom/migrations/0041_organization_subscription_quantity.py +18 -0
  55. bom/migrations/0042_auto_20210720_2137.py +23 -0
  56. bom/migrations/0043_auto_20211123_0157.py +24 -0
  57. bom/migrations/0044_auto_20220831_1241.py +23 -0
  58. bom/migrations/0045_sellerpart_link.py +18 -0
  59. bom/migrations/0046_alter_sellerpart_unique_together.py +17 -0
  60. bom/migrations/0047_sellerpart_seller_part_number.py +18 -0
  61. bom/migrations/0048_rename_part_organization_number_class_bom_part_organiz_b333d6_idx_and_more.py +1017 -0
  62. bom/migrations/0049_alter_assembly_id_alter_assemblysubparts_id_and_more.py +99 -0
  63. bom/migrations/0050_alter_organization_options.py +17 -0
  64. bom/migrations/0051_alter_manufacturer_organization_and_more.py +41 -0
  65. bom/migrations/0052_remove_partrevision_attribute_and_more.py +584 -0
  66. bom/migrations/__init__.py +0 -0
  67. bom/models.py +886 -0
  68. bom/part_bom.py +192 -0
  69. bom/settings.py +262 -0
  70. bom/static/bom/css/dashboard.css +17 -0
  71. bom/static/bom/css/jquery.treetable.css +28 -0
  72. bom/static/bom/css/materialize.min.css +13 -0
  73. bom/static/bom/css/part-info.css +15 -0
  74. bom/static/bom/css/style.css +482 -0
  75. bom/static/bom/css/tablesorter-theme.materialize.css +176 -0
  76. bom/static/bom/css/treetable-theme.css +42 -0
  77. bom/static/bom/doc/sample_part_classes.csv +38 -0
  78. bom/static/bom/doc/test_bom.csv +6 -0
  79. bom/static/bom/doc/test_bom_5_intelligent.csv +4 -0
  80. bom/static/bom/doc/test_full_bom.csv +37 -0
  81. bom/static/bom/doc/test_new_parts.csv +5 -0
  82. bom/static/bom/doc/test_new_parts_5_intelligent.csv +5 -0
  83. bom/static/bom/img/_ionicons_svg_md-arrow-dropdown.svg +1 -0
  84. bom/static/bom/img/_ionicons_svg_md-arrow-dropright.svg +1 -0
  85. bom/static/bom/img/favicon.ico +0 -0
  86. bom/static/bom/img/google/web/1x/btn_google_signin_dark_disabled_web.png +0 -0
  87. bom/static/bom/img/google/web/1x/btn_google_signin_dark_focus_web.png +0 -0
  88. bom/static/bom/img/google/web/1x/btn_google_signin_dark_normal_web.png +0 -0
  89. bom/static/bom/img/google/web/1x/btn_google_signin_dark_pressed_web.png +0 -0
  90. bom/static/bom/img/google/web/1x/btn_google_signin_light_disabled_web.png +0 -0
  91. bom/static/bom/img/google/web/1x/btn_google_signin_light_focus_web.png +0 -0
  92. bom/static/bom/img/google/web/1x/btn_google_signin_light_normal_web.png +0 -0
  93. bom/static/bom/img/google/web/1x/btn_google_signin_light_pressed_web.png +0 -0
  94. bom/static/bom/img/google/web/2x/btn_google_signin_dark_disabled_web@2x.png +0 -0
  95. bom/static/bom/img/google/web/2x/btn_google_signin_dark_focus_web@2x.png +0 -0
  96. bom/static/bom/img/google/web/2x/btn_google_signin_dark_normal_web@2x.png +0 -0
  97. bom/static/bom/img/google/web/2x/btn_google_signin_dark_pressed_web@2x.png +0 -0
  98. bom/static/bom/img/google/web/2x/btn_google_signin_light_disabled_web@2x.png +0 -0
  99. bom/static/bom/img/google/web/2x/btn_google_signin_light_focus_web@2x.png +0 -0
  100. bom/static/bom/img/google/web/2x/btn_google_signin_light_normal_web@2x.png +0 -0
  101. bom/static/bom/img/google/web/2x/btn_google_signin_light_pressed_web@2x.png +0 -0
  102. bom/static/bom/img/google/web/vector/btn_google_dark_disabled_ios.eps +814 -0
  103. bom/static/bom/img/google/web/vector/btn_google_dark_disabled_ios.svg +24 -0
  104. bom/static/bom/img/google/web/vector/btn_google_dark_focus_ios.eps +1866 -0
  105. bom/static/bom/img/google/web/vector/btn_google_dark_focus_ios.svg +51 -0
  106. bom/static/bom/img/google/web/vector/btn_google_dark_normal_ios.eps +1031 -0
  107. bom/static/bom/img/google/web/vector/btn_google_dark_normal_ios.svg +50 -0
  108. bom/static/bom/img/google/web/vector/btn_google_dark_pressed_ios.eps +1031 -0
  109. bom/static/bom/img/google/web/vector/btn_google_dark_pressed_ios.svg +50 -0
  110. bom/static/bom/img/google/web/vector/btn_google_light_disabled_ios.eps +814 -0
  111. bom/static/bom/img/google/web/vector/btn_google_light_disabled_ios.svg +24 -0
  112. bom/static/bom/img/google/web/vector/btn_google_light_focus_ios.eps +1837 -0
  113. bom/static/bom/img/google/web/vector/btn_google_light_focus_ios.svg +44 -0
  114. bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.eps +1002 -0
  115. bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.svg +43 -0
  116. bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.eps +1002 -0
  117. bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.svg +43 -0
  118. bom/static/bom/img/google_drive_logo.svg +1 -0
  119. bom/static/bom/img/indabom.png +0 -0
  120. bom/static/bom/img/mouser.png +0 -0
  121. bom/static/bom/img/octopart_blue.svg +19 -0
  122. bom/static/bom/js/formset-handler.js +65 -0
  123. bom/static/bom/js/jquery-3.4.1.min.js +2 -0
  124. bom/static/bom/js/jquery.ba-floatingscrollbar.min.js +10 -0
  125. bom/static/bom/js/jquery.treetable.js +629 -0
  126. bom/static/bom/js/materialize.min.js +6 -0
  127. bom/templates/bom/account-delete.html +23 -0
  128. bom/templates/bom/add-manufacturer-part.html +66 -0
  129. bom/templates/bom/add-sellerpart.html +93 -0
  130. bom/templates/bom/base-menu.html +16 -0
  131. bom/templates/bom/base.html +129 -0
  132. bom/templates/bom/bom-action-btn.html +23 -0
  133. bom/templates/bom/bom-action-table.html +57 -0
  134. bom/templates/bom/bom-base-menu.html +6 -0
  135. bom/templates/bom/bom-base.html +24 -0
  136. bom/templates/bom/bom-form-modal.html +36 -0
  137. bom/templates/bom/bom-form.html +30 -0
  138. bom/templates/bom/bom-modal-add-users.html +49 -0
  139. bom/templates/bom/bom-signup.html +12 -0
  140. bom/templates/bom/components/bom-flat.html +131 -0
  141. bom/templates/bom/components/bom-indented.html +237 -0
  142. bom/templates/bom/components/manufacturer-part-list.html +270 -0
  143. bom/templates/bom/components/seller-part-list.html +62 -0
  144. bom/templates/bom/create-part.html +65 -0
  145. bom/templates/bom/dashboard-menu.html +15 -0
  146. bom/templates/bom/dashboard.html +303 -0
  147. bom/templates/bom/edit-manufacturer-part.html +72 -0
  148. bom/templates/bom/edit-part-class.html +120 -0
  149. bom/templates/bom/edit-part.html +67 -0
  150. bom/templates/bom/edit-quantity-of-measure.html +119 -0
  151. bom/templates/bom/edit-user-meta.html +70 -0
  152. bom/templates/bom/help.html +1356 -0
  153. bom/templates/bom/manufacturer-info.html +82 -0
  154. bom/templates/bom/manufacturers.html +97 -0
  155. bom/templates/bom/nothing-to-see.html +15 -0
  156. bom/templates/bom/organization-create.html +135 -0
  157. bom/templates/bom/part-info.html +448 -0
  158. bom/templates/bom/part-revision-display.html +50 -0
  159. bom/templates/bom/part-revision-edit.html +39 -0
  160. bom/templates/bom/part-revision-manage-bom.html +115 -0
  161. bom/templates/bom/part-revision-new.html +57 -0
  162. bom/templates/bom/part-revision-release.html +41 -0
  163. bom/templates/bom/search-help.html +101 -0
  164. bom/templates/bom/seller-info.html +82 -0
  165. bom/templates/bom/sellers.html +97 -0
  166. bom/templates/bom/settings.html +734 -0
  167. bom/templates/bom/signup.html +28 -0
  168. bom/templates/bom/subscription_panel.html +16 -0
  169. bom/templates/bom/table_of_contents.html +47 -0
  170. bom/templates/bom/upload-bom.html +111 -0
  171. bom/templates/bom/upload-parts-help.html +103 -0
  172. bom/templates/bom/upload-parts.html +50 -0
  173. bom/templates/registration/login.html +39 -0
  174. bom/tests.py +1592 -0
  175. bom/third_party_apis/__init__.py +0 -0
  176. bom/third_party_apis/base_api.py +51 -0
  177. bom/third_party_apis/google_drive.py +166 -0
  178. bom/third_party_apis/mouser.py +132 -0
  179. bom/third_party_apis/test_apis.py +24 -0
  180. bom/urls.py +100 -0
  181. bom/utils.py +228 -0
  182. bom/validators.py +23 -0
  183. bom/views/__init__.py +0 -0
  184. bom/views/json_views.py +55 -0
  185. bom/views/views.py +1773 -0
  186. bom/wsgi.py +16 -0
  187. django_bom-1.262.dist-info/METADATA +206 -0
  188. django_bom-1.262.dist-info/RECORD +191 -0
  189. django_bom-1.262.dist-info/WHEEL +5 -0
  190. django_bom-1.262.dist-info/licenses/LICENSE +674 -0
  191. django_bom-1.262.dist-info/top_level.txt +1 -0
@@ -0,0 +1,584 @@
1
+ # Generated by Django 5.2.8 on 2026-01-13 00:43
2
+
3
+ from collections import defaultdict
4
+ from decimal import Decimal
5
+
6
+ import django.db.models.deletion
7
+ from django.conf import settings
8
+ from django.db import migrations, models
9
+
10
+
11
+ def create_unit_definitions(apps, schema_editor):
12
+ QuantityOfMeasure = apps.get_model('bom', 'QuantityOfMeasure')
13
+ UnitDefinition = apps.get_model('bom', 'UnitDefinition')
14
+
15
+ # Voltage
16
+ voltage = QuantityOfMeasure.objects.create(name='Voltage')
17
+ UnitDefinition.objects.create(name='Volt', symbol='V', quantity_of_measure=voltage, base_multiplier=Decimal('1.0'))
18
+ UnitDefinition.objects.create(name='Millivolt', symbol='mV', quantity_of_measure=voltage,
19
+ base_multiplier=Decimal('0.001'))
20
+ UnitDefinition.objects.create(name='Microvolt', symbol='uV', quantity_of_measure=voltage,
21
+ base_multiplier=Decimal('0.000001'))
22
+ UnitDefinition.objects.create(name='Kilovolt', symbol='kV', quantity_of_measure=voltage,
23
+ base_multiplier=Decimal('1000.0'))
24
+ UnitDefinition.objects.create(name='Megavolt', symbol='MV', quantity_of_measure=voltage,
25
+ base_multiplier=Decimal('1000000.0'))
26
+
27
+ # Current
28
+ current = QuantityOfMeasure.objects.create(name='Current')
29
+ UnitDefinition.objects.create(name='Ampere', symbol='A', quantity_of_measure=current,
30
+ base_multiplier=Decimal('1.0'))
31
+ UnitDefinition.objects.create(name='Milliampere', symbol='mA', quantity_of_measure=current,
32
+ base_multiplier=Decimal('0.001'))
33
+ UnitDefinition.objects.create(name='Microampere', symbol='uA', quantity_of_measure=current,
34
+ base_multiplier=Decimal('0.000001'))
35
+ UnitDefinition.objects.create(name='Kiloampere', symbol='kA', quantity_of_measure=current,
36
+ base_multiplier=Decimal('1000.0'))
37
+ UnitDefinition.objects.create(name='Megaampere', symbol='MA', quantity_of_measure=current,
38
+ base_multiplier=Decimal('1000000.0'))
39
+
40
+ # Resistance
41
+ resistance = QuantityOfMeasure.objects.create(name='Resistance')
42
+ UnitDefinition.objects.create(name='Ohm', symbol='Ω', quantity_of_measure=resistance,
43
+ base_multiplier=Decimal('1.0'))
44
+ UnitDefinition.objects.create(name='Milliohm', symbol='mΩ', quantity_of_measure=resistance,
45
+ base_multiplier=Decimal('0.001'))
46
+ UnitDefinition.objects.create(name='Kiloohm', symbol='kΩ', quantity_of_measure=resistance,
47
+ base_multiplier=Decimal('1000.0'))
48
+ UnitDefinition.objects.create(name='Megaohm', symbol='MΩ', quantity_of_measure=resistance,
49
+ base_multiplier=Decimal('1000000.0'))
50
+
51
+ # Capacitance
52
+ capacitance = QuantityOfMeasure.objects.create(name='Capacitance')
53
+ UnitDefinition.objects.create(name='Farad', symbol='F', quantity_of_measure=capacitance,
54
+ base_multiplier=Decimal('1.0'))
55
+ UnitDefinition.objects.create(name='Millifarad', symbol='mF', quantity_of_measure=capacitance,
56
+ base_multiplier=Decimal('0.001'))
57
+ UnitDefinition.objects.create(name='Microfarad', symbol='uF', quantity_of_measure=capacitance,
58
+ base_multiplier=Decimal('0.000001'))
59
+ UnitDefinition.objects.create(name='Nanofarad', symbol='nF', quantity_of_measure=capacitance,
60
+ base_multiplier=Decimal('0.000000001'))
61
+ UnitDefinition.objects.create(name='Picofarad', symbol='pF', quantity_of_measure=capacitance,
62
+ base_multiplier=Decimal('0.000000000001'))
63
+
64
+ # Inductance
65
+ inductance = QuantityOfMeasure.objects.create(name='Inductance')
66
+ UnitDefinition.objects.create(name='Henry', symbol='H', quantity_of_measure=inductance,
67
+ base_multiplier=Decimal('1.0'))
68
+ UnitDefinition.objects.create(name='Millihenry', symbol='mH', quantity_of_measure=inductance,
69
+ base_multiplier=Decimal('0.001'))
70
+ UnitDefinition.objects.create(name='Microhenry', symbol='uH', quantity_of_measure=inductance,
71
+ base_multiplier=Decimal('0.000001'))
72
+ UnitDefinition.objects.create(name='Nanohenry', symbol='nH', quantity_of_measure=inductance,
73
+ base_multiplier=Decimal('0.000000001'))
74
+
75
+ # Frequency
76
+ frequency = QuantityOfMeasure.objects.create(name='Frequency')
77
+ UnitDefinition.objects.create(name='Hertz', symbol='Hz', quantity_of_measure=frequency,
78
+ base_multiplier=Decimal('1.0'))
79
+ UnitDefinition.objects.create(name='Kilohertz', symbol='kHz', quantity_of_measure=frequency,
80
+ base_multiplier=Decimal('1000.0'))
81
+ UnitDefinition.objects.create(name='Megahertz', symbol='MHz', quantity_of_measure=frequency,
82
+ base_multiplier=Decimal('1000000.0'))
83
+ UnitDefinition.objects.create(name='Gigahertz', symbol='GHz', quantity_of_measure=frequency,
84
+ base_multiplier=Decimal('1000000000.0'))
85
+
86
+ # Power
87
+ power = QuantityOfMeasure.objects.create(name='Power')
88
+ UnitDefinition.objects.create(name='Watt', symbol='W', quantity_of_measure=power, base_multiplier=Decimal('1.0'))
89
+ UnitDefinition.objects.create(name='Milliwatt', symbol='mW', quantity_of_measure=power,
90
+ base_multiplier=Decimal('0.001'))
91
+ UnitDefinition.objects.create(name='Microwatt', symbol='uW', quantity_of_measure=power,
92
+ base_multiplier=Decimal('0.000001'))
93
+ UnitDefinition.objects.create(name='Kilowatt', symbol='kW', quantity_of_measure=power,
94
+ base_multiplier=Decimal('1000.0'))
95
+ UnitDefinition.objects.create(name='Megawatt', symbol='MW', quantity_of_measure=power,
96
+ base_multiplier=Decimal('1000000.0'))
97
+
98
+ # Distance
99
+ distance = QuantityOfMeasure.objects.create(name='Distance')
100
+ UnitDefinition.objects.create(name='Meter', symbol='m', quantity_of_measure=distance,
101
+ base_multiplier=Decimal('1.0'))
102
+ UnitDefinition.objects.create(name='Centimeter', symbol='cm', quantity_of_measure=distance,
103
+ base_multiplier=Decimal('0.01'))
104
+ UnitDefinition.objects.create(name='Millimeter', symbol='mm', quantity_of_measure=distance,
105
+ base_multiplier=Decimal('0.001'))
106
+ UnitDefinition.objects.create(name='Micrometer', symbol='um', quantity_of_measure=distance,
107
+ base_multiplier=Decimal('0.000001'))
108
+ UnitDefinition.objects.create(name='Nanometer', symbol='nm', quantity_of_measure=distance,
109
+ base_multiplier=Decimal('0.000000001'))
110
+ UnitDefinition.objects.create(name='Kilometer', symbol='km', quantity_of_measure=distance,
111
+ base_multiplier=Decimal('1000.0'))
112
+ UnitDefinition.objects.create(name='Inch', symbol='in', quantity_of_measure=distance,
113
+ base_multiplier=Decimal('0.0254'))
114
+ UnitDefinition.objects.create(name='Foot', symbol='ft', quantity_of_measure=distance,
115
+ base_multiplier=Decimal('0.3048'))
116
+ UnitDefinition.objects.create(name='Yard', symbol='yd', quantity_of_measure=distance,
117
+ base_multiplier=Decimal('0.9144'))
118
+ UnitDefinition.objects.create(name='Mil', symbol='mil', quantity_of_measure=distance,
119
+ base_multiplier=Decimal('0.0000254'))
120
+
121
+ # Weight
122
+ weight = QuantityOfMeasure.objects.create(name='Weight')
123
+ UnitDefinition.objects.create(name='Gram', symbol='g', quantity_of_measure=weight, base_multiplier=Decimal('1.0'))
124
+ UnitDefinition.objects.create(name='Milligram', symbol='mg', quantity_of_measure=weight,
125
+ base_multiplier=Decimal('0.001'))
126
+ UnitDefinition.objects.create(name='Kilogram', symbol='kg', quantity_of_measure=weight,
127
+ base_multiplier=Decimal('1000.0'))
128
+ UnitDefinition.objects.create(name='Ounce', symbol='oz', quantity_of_measure=weight,
129
+ base_multiplier=Decimal('28.3495'))
130
+ UnitDefinition.objects.create(name='Pound', symbol='lb', quantity_of_measure=weight,
131
+ base_multiplier=Decimal('453.592'))
132
+
133
+ # Temperature
134
+ temperature = QuantityOfMeasure.objects.create(name='Temperature')
135
+ UnitDefinition.objects.create(name='Celsius', symbol='C', quantity_of_measure=temperature,
136
+ base_multiplier=Decimal('1.0'))
137
+ UnitDefinition.objects.create(name='Fahrenheit', symbol='F', quantity_of_measure=temperature,
138
+ base_multiplier=Decimal('1.0'))
139
+
140
+ # Memory
141
+ memory = QuantityOfMeasure.objects.create(name='Memory')
142
+ UnitDefinition.objects.create(name='Byte', symbol='B', quantity_of_measure=memory, base_multiplier=Decimal('1.0'))
143
+ UnitDefinition.objects.create(name='Kilobyte', symbol='KB', quantity_of_measure=memory,
144
+ base_multiplier=Decimal('1024.0'))
145
+ UnitDefinition.objects.create(name='Megabyte', symbol='MB', quantity_of_measure=memory,
146
+ base_multiplier=Decimal('1048576.0'))
147
+ UnitDefinition.objects.create(name='Gigabyte', symbol='GB', quantity_of_measure=memory,
148
+ base_multiplier=Decimal('1073741824.0'))
149
+ UnitDefinition.objects.create(name='Terabyte', symbol='TB', quantity_of_measure=memory,
150
+ base_multiplier=Decimal('1099511627776.0'))
151
+
152
+ # Dimensionless / Ratio
153
+ dimensionless = QuantityOfMeasure.objects.create(name='Dimensionless')
154
+ UnitDefinition.objects.create(name='Percent', symbol='%', quantity_of_measure=dimensionless,
155
+ base_multiplier=Decimal('0.01'))
156
+ UnitDefinition.objects.create(name='Parts per Million', symbol='ppm', quantity_of_measure=dimensionless,
157
+ base_multiplier=Decimal('0.000001'))
158
+
159
+
160
+ def remove_unit_definitions(apps, schema_editor):
161
+ """Reverse function to clean up units."""
162
+ QuantityOfMeasure = apps.get_model('bom', 'QuantityOfMeasure')
163
+ UnitDefinition = apps.get_model('bom', 'UnitDefinition')
164
+ UnitDefinition.objects.all().delete()
165
+ QuantityOfMeasure.objects.all().delete()
166
+
167
+
168
+ def get_field_map():
169
+ """
170
+ Shared mapping for forward and reverse.
171
+ NOTE: 'value' is intentionally excluded here as it is handled dynamically.
172
+ """
173
+ return {
174
+ 'attribute': ('attribute', 'Attribute', 'string', None),
175
+ 'pin_count': ('pin_count', 'Pin Count', 'number', None),
176
+ 'tolerance': ('tolerance', 'Tolerance', 'string', 'Dimensionless'),
177
+ 'package': ('package', 'Package', 'string', None),
178
+ 'material': ('material', 'Material', 'string', None),
179
+ 'finish': ('finish', 'Finish', 'string', None),
180
+ 'color': ('color', 'Color', 'string', None),
181
+ 'length': ('length', 'Length', 'number', 'Distance'),
182
+ 'width': ('width', 'Width', 'number', 'Distance'),
183
+ 'height': ('height', 'Height', 'number', 'Distance'),
184
+ 'weight': ('weight', 'Weight', 'number', 'Weight'),
185
+ 'temperature_rating': ('temperature_rating', 'Temperature Rating', 'number', 'Temperature'),
186
+ 'temperature_rating_range_max': ('temperature_rating_range_max', 'Temperature Rating Max', 'number',
187
+ 'Temperature'),
188
+ 'temperature_rating_range_min': ('temperature_rating_range_min', 'Temperature Rating Min', 'number',
189
+ 'Temperature'),
190
+ 'wavelength': ('wavelength', 'Wavelength', 'number', 'Distance'),
191
+ 'frequency': ('frequency', 'Frequency', 'number', 'Frequency'),
192
+ 'memory': ('memory', 'Memory', 'number', 'Memory'),
193
+ 'interface': ('interface', 'Interface', 'string', None),
194
+ 'power_rating': ('power_rating', 'Power Rating', 'number', 'Power'),
195
+ 'supply_voltage': ('supply_voltage', 'Supply Voltage', 'number', 'Voltage'),
196
+ 'voltage_rating': ('voltage_rating', 'Voltage Rating', 'number', 'Voltage'),
197
+ 'current_rating': ('current_rating', 'Current Rating', 'number', 'Current'),
198
+ }
199
+
200
+
201
+ def migrate_part_revision_data(apps, schema_editor):
202
+ PartRevision = apps.get_model('bom', 'PartRevision')
203
+ PartClass = apps.get_model('bom', 'PartClass')
204
+ PartRevisionPropertyDefinition = apps.get_model('bom', 'PartRevisionPropertyDefinition')
205
+ PartRevisionProperty = apps.get_model('bom', 'PartRevisionProperty')
206
+ UnitDefinition = apps.get_model('bom', 'UnitDefinition')
207
+ QuantityOfMeasure = apps.get_model('bom', 'QuantityOfMeasure')
208
+
209
+ field_map = get_field_map()
210
+ definitions = {}
211
+
212
+ # 1. Create Standard Property Definitions
213
+ for old_field, (code, name, type_val, quantity_name) in field_map.items():
214
+ qom_obj = None
215
+ if quantity_name:
216
+ qom_obj = QuantityOfMeasure.objects.get(name=quantity_name)
217
+
218
+ # Map long type names to single character codes
219
+ type_mapping = {
220
+ 'number': 'D',
221
+ 'string': 'S',
222
+ 'boolean': 'B',
223
+ }
224
+ mapped_type = type_mapping.get(type_val, type_val)
225
+
226
+ definition, created = PartRevisionPropertyDefinition.objects.get_or_create(
227
+ code=code,
228
+ defaults={'name': name, 'type': mapped_type, 'quantity_of_measure': qom_obj, 'organization': None}
229
+ )
230
+ definitions[old_field] = definition
231
+
232
+ # 2. Create Dynamic PLM Definitions (Resistance, Capacitance, etc)
233
+ # These replace the generic 'value' column
234
+ qom_res = QuantityOfMeasure.objects.get(name='Resistance')
235
+ def_resistance, _ = PartRevisionPropertyDefinition.objects.get_or_create(
236
+ code='resistance',
237
+ defaults={'name': 'Resistance', 'type': 'D', 'quantity_of_measure': qom_res, 'organization': None}
238
+ )
239
+
240
+ qom_cap = QuantityOfMeasure.objects.get(name='Capacitance')
241
+ def_capacitance, _ = PartRevisionPropertyDefinition.objects.get_or_create(
242
+ code='capacitance',
243
+ defaults={'name': 'Capacitance', 'type': 'D', 'quantity_of_measure': qom_cap, 'organization': None}
244
+ )
245
+
246
+ qom_ind = QuantityOfMeasure.objects.get(name='Inductance')
247
+ def_inductance, _ = PartRevisionPropertyDefinition.objects.get_or_create(
248
+ code='inductance',
249
+ defaults={'name': 'Inductance', 'type': 'D', 'quantity_of_measure': qom_ind, 'organization': None}
250
+ )
251
+
252
+ # Fallback for Generic Parts (ICs, etc) that have a 'value' but no specific type
253
+ def_value, _ = PartRevisionPropertyDefinition.objects.get_or_create(
254
+ code='value',
255
+ defaults={'name': 'Value', 'type': 'S', 'quantity_of_measure': None, 'organization': None}
256
+ )
257
+
258
+ # 3. Helper for Unit Lookup
259
+ unit_cache = {}
260
+
261
+ def get_unit(symbol):
262
+ if not symbol: return None
263
+ clean_symbol = symbol.strip().lower()
264
+ symbol_map = {
265
+ 'ohm': 'Ω', 'ohms': 'Ω', 'kohm': 'kΩ', 'kohms': 'kΩ', 'mohm': 'MΩ', 'mohms': 'MΩ',
266
+ 'Ω': 'Ω', 'kΩ': 'kΩ', 'mΩ': 'mΩ', 'MΩ': 'MΩ',
267
+ 'f': 'F', 'farad': 'F', 'farads': 'F', 'mf': 'mF', 'uf': 'uF', 'nf': 'nF', 'pf': 'pF',
268
+ 'microfarad': 'uF', 'µf': 'uF',
269
+ 'v': 'V', 'volt': 'V', 'volts': 'V', 'mv': 'mV', 'uv': 'uV', 'kv': 'kV', 'µv': 'uV',
270
+ 'a': 'A', 'amp': 'A', 'amps': 'A', 'ampere': 'A', 'ma': 'mA', 'ua': 'uA', 'ka': 'kA', 'µa': 'uA',
271
+ 'h': 'H', 'henry': 'H', 'henries': 'H', 'mh': 'mH', 'uh': 'uH', 'nh': 'nH', 'µh': 'uH',
272
+ 'hz': 'Hz', 'khz': 'kHz', 'mhz': 'MHz', 'ghz': 'GHz',
273
+ 'w': 'W', 'mw': 'mW', 'uw': 'uW', 'kw': 'kW', 'mw': 'MW',
274
+ 'm': 'm', 'mm': 'mm', 'cm': 'cm', 'km': 'km', 'g': 'g', 'kg': 'kg', 'mg': 'mg',
275
+ 's': 's', 'ms': 'ms', 'us': 'us', 'ns': 'ns',
276
+ 'c': 'C', 'degc': 'C', '°c': 'C', 'f': 'F', 'degf': 'F', '°f': 'F',
277
+ 'ppm': 'ppm', '%': '%', 'percent': '%'
278
+ }
279
+ mapped_symbol = symbol_map.get(clean_symbol, symbol.strip())
280
+ if mapped_symbol in unit_cache: return unit_cache[mapped_symbol]
281
+ try:
282
+ unit = UnitDefinition.objects.filter(symbol=mapped_symbol).first()
283
+ if unit:
284
+ unit_cache[mapped_symbol] = unit
285
+ return unit
286
+ return None
287
+ except Exception:
288
+ return None
289
+
290
+ # 4. Batch Process Data
291
+ class_def_map = defaultdict(set)
292
+ batch_properties = []
293
+ BATCH_SIZE = 2000
294
+
295
+ for pr in PartRevision.objects.select_related('part', 'part__number_class').iterator():
296
+ part_class_id = getattr(pr.part, 'number_class_id', None)
297
+ part_class_name = getattr(pr.part.number_class, 'name', '').lower() if pr.part.number_class else ''
298
+
299
+ # --- A. HANDLE LEGACY 'VALUE' COLUMN INTELLIGENTLY ---
300
+ val = getattr(pr, 'value', None)
301
+ if val is not None and val != '':
302
+ # Decide which Definition to use based on Class
303
+ target_def = def_value # Default fallback
304
+ target_unit_name = None # For inference
305
+
306
+ if 'resistor' in part_class_name:
307
+ target_def = def_resistance
308
+ target_unit_name = 'Ω' # Infer Ohms if missing
309
+ elif 'capacitor' in part_class_name:
310
+ target_def = def_capacitance
311
+ target_unit_name = 'F' # Infer Farads if missing
312
+ elif 'inductor' in part_class_name or 'ferrite' in part_class_name:
313
+ target_def = def_inductance
314
+ target_unit_name = 'H'
315
+
316
+ if part_class_id:
317
+ class_def_map[part_class_id].add(target_def.id)
318
+
319
+ # Check for existing unit in legacy column
320
+ legacy_unit = getattr(pr, 'value_units', None)
321
+
322
+ # If legacy unit is missing, use our inferred one (e.g. Ohms for Resistors)
323
+ unit_ref = get_unit(legacy_unit) if legacy_unit else get_unit(target_unit_name)
324
+
325
+ prop = PartRevisionProperty(
326
+ part_revision=pr,
327
+ property_definition=target_def,
328
+ value_raw=str(val),
329
+ unit_definition=unit_ref
330
+ )
331
+
332
+ # Normalize
333
+ if unit_ref and val:
334
+ try:
335
+ clean_val = str(val).lower().replace('k', '').replace('m', '').replace('u', '')
336
+ val_dec = Decimal(clean_val)
337
+ mult_dec = Decimal(unit_ref.base_multiplier)
338
+ prop.value_normalized = val_dec * mult_dec
339
+ except (ValueError, TypeError, Exception):
340
+ pass
341
+
342
+ batch_properties.append(prop)
343
+
344
+ # --- B. HANDLE STANDARD COLUMNS ---
345
+ for old_field, definition in definitions.items():
346
+ val = getattr(pr, old_field, None)
347
+
348
+ if val is not None and val != '':
349
+ if part_class_id:
350
+ class_def_map[part_class_id].add(definition.id)
351
+
352
+ unit_field = old_field + '_units'
353
+ unit_val = getattr(pr, unit_field, None)
354
+
355
+ # Intelligent Inference for Tolerance
356
+ if old_field == 'tolerance':
357
+ str_val = str(val).lower()
358
+ if 'ppm' in str_val:
359
+ unit_val = 'ppm'
360
+ val = str_val.replace('ppm', '').strip()
361
+ elif '%' in str_val:
362
+ unit_val = '%'
363
+ val = str_val.replace('%', '').strip()
364
+ else:
365
+ if not any(x in str_val for x in ['f', 'v', 'h']):
366
+ unit_val = '%'
367
+
368
+ unit_ref = get_unit(unit_val)
369
+
370
+ prop = PartRevisionProperty(
371
+ part_revision=pr,
372
+ property_definition=definition,
373
+ value_raw=str(val),
374
+ unit_definition=unit_ref
375
+ )
376
+
377
+ if unit_ref and val:
378
+ try:
379
+ clean_val = str(val).lower().replace('k', '').replace('m', '').replace('u', '')
380
+ val_dec = Decimal(clean_val)
381
+ mult_dec = Decimal(unit_ref.base_multiplier)
382
+ prop.value_normalized = val_dec * mult_dec
383
+ except (ValueError, TypeError, Exception):
384
+ pass
385
+
386
+ batch_properties.append(prop)
387
+
388
+ if len(batch_properties) >= BATCH_SIZE:
389
+ PartRevisionProperty.objects.bulk_create(batch_properties)
390
+ batch_properties = []
391
+
392
+ if batch_properties:
393
+ PartRevisionProperty.objects.bulk_create(batch_properties)
394
+
395
+ for class_id, def_ids in class_def_map.items():
396
+ try:
397
+ pc = PartClass.objects.get(id=class_id)
398
+ pc.property_definitions.add(*def_ids)
399
+ except PartClass.DoesNotExist:
400
+ continue
401
+
402
+
403
+ def reverse_migrate_part_revision_data(apps, schema_editor):
404
+ """Restores data from PartRevisionProperties back to PartRevision columns."""
405
+ PartRevision = apps.get_model('bom', 'PartRevision')
406
+ PartRevisionProperty = apps.get_model('bom', 'PartRevisionProperty')
407
+
408
+ # Reverse Map: Property Name -> (Old Field Name, Old Unit Field Name)
409
+ field_map = get_field_map()
410
+ prop_name_to_field = {}
411
+ for old_field, (code, name, _, _) in field_map.items():
412
+ unit_field = old_field + '_units'
413
+ prop_name_to_field[name] = (old_field, unit_field)
414
+
415
+ # ADD DYNAMIC MAPPINGS FOR REVERSE
416
+ # Map the new PLM fields back to the old 'value' column so data is not lost on reverse
417
+ prop_name_to_field['Value'] = ('value', 'value_units')
418
+ prop_name_to_field['Resistance'] = ('value', 'value_units')
419
+ prop_name_to_field['Capacitance'] = ('value', 'value_units')
420
+ prop_name_to_field['Inductance'] = ('value', 'value_units')
421
+
422
+ batch_update_list = []
423
+ BATCH_SIZE = 2000
424
+ fields_to_update = set()
425
+
426
+ # Iterate Revisions
427
+ # Prefetch properties to avoid N+1
428
+ qs = PartRevision.objects.prefetch_related('properties', 'properties__property_definition',
429
+ 'properties__unit_definition')
430
+
431
+ for pr in qs.iterator():
432
+ updated = False
433
+ for prop in pr.properties.all():
434
+ prop_name = prop.property_definition.name
435
+ if prop_name in prop_name_to_field:
436
+ col_name, unit_col_name = prop_name_to_field[prop_name]
437
+
438
+ # Restore Value
439
+ if prop.value_raw:
440
+ setattr(pr, col_name, prop.value_raw)
441
+ fields_to_update.add(col_name)
442
+ updated = True
443
+
444
+ # Restore Unit
445
+ if prop.unit_definition and hasattr(pr, unit_col_name):
446
+ setattr(pr, unit_col_name, prop.unit_definition.symbol)
447
+ fields_to_update.add(unit_col_name)
448
+ updated = True
449
+
450
+ if updated:
451
+ batch_update_list.append(pr)
452
+
453
+ if len(batch_update_list) >= BATCH_SIZE:
454
+ PartRevision.objects.bulk_update(batch_update_list, list(fields_to_update))
455
+ batch_update_list = []
456
+
457
+ if batch_update_list and fields_to_update:
458
+ PartRevision.objects.bulk_update(batch_update_list, list(fields_to_update))
459
+
460
+ class Migration(migrations.Migration):
461
+ dependencies = [
462
+ ('bom', '0051_alter_manufacturer_organization_and_more'),
463
+ ]
464
+
465
+ operations = [
466
+ migrations.CreateModel(
467
+ name='PartRevisionPropertyDefinition',
468
+ fields=[
469
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
470
+ ('code', models.CharField(blank=True, max_length=64)),
471
+ ('name', models.CharField(max_length=64)),
472
+ ('type', models.CharField(choices=[('S', 'String'), ('D', 'Number'), ('B', 'Boolean')], max_length=1)),
473
+ ('required', models.BooleanField(default=False)),
474
+ ('organization',
475
+ models.ForeignKey(blank=True, help_text='Leave empty for a Global/System record.', null=True,
476
+ on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL)),
477
+ ],
478
+ ),
479
+ migrations.AddField(
480
+ model_name='partclass',
481
+ name='property_definitions',
482
+ field=models.ManyToManyField(blank=True, related_name='part_classes',
483
+ to='bom.partrevisionpropertydefinition'),
484
+ ),
485
+ migrations.CreateModel(
486
+ name='QuantityOfMeasure',
487
+ fields=[
488
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
489
+ ('name', models.CharField(help_text='e.g. Voltage', max_length=64)),
490
+ ('organization',
491
+ models.ForeignKey(blank=True, help_text='Leave empty for a Global/System record.', null=True,
492
+ on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL)),
493
+ ],
494
+ options={
495
+ 'unique_together': {('organization', 'name')},
496
+ },
497
+ ),
498
+ migrations.AddField(
499
+ model_name='partrevisionpropertydefinition',
500
+ name='quantity_of_measure',
501
+ field=models.ForeignKey(blank=True,
502
+ null=True, on_delete=django.db.models.deletion.SET_NULL,
503
+ to='bom.quantityofmeasure'),
504
+ ),
505
+ migrations.CreateModel(
506
+ name='UnitDefinition',
507
+ fields=[
508
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
509
+ ('name', models.CharField(max_length=64)),
510
+ ('symbol', models.CharField(max_length=16)),
511
+ ('base_multiplier', models.DecimalField(decimal_places=20, default=Decimal('1.0'), max_digits=40)),
512
+ ('organization',
513
+ models.ForeignKey(blank=True, help_text='Leave empty for a Global/System record.', null=True,
514
+ on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL)),
515
+ ('quantity_of_measure',
516
+ models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='units',
517
+ to='bom.quantityofmeasure')),
518
+ ],
519
+ options={
520
+ 'ordering': ['base_multiplier'],
521
+ 'unique_together': {('organization', 'quantity_of_measure', 'symbol')},
522
+ },
523
+ ),
524
+ migrations.CreateModel(
525
+ name='PartRevisionProperty',
526
+ fields=[
527
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
528
+ ('value_raw', models.CharField(max_length=255)),
529
+ ('value_normalized', models.DecimalField(blank=True, decimal_places=20, max_digits=40, null=True)),
530
+ ('part_revision',
531
+ models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='properties',
532
+ to='bom.partrevision')),
533
+ ('property_definition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
534
+ to='bom.partrevisionpropertydefinition')),
535
+ ('unit_definition',
536
+ models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
537
+ to='bom.unitdefinition')),
538
+ ],
539
+ ),
540
+ migrations.AlterUniqueTogether(
541
+ name='partrevisionpropertydefinition',
542
+ unique_together={('organization', 'code')},
543
+ ),
544
+
545
+ migrations.RunPython(create_unit_definitions, remove_unit_definitions),
546
+ migrations.RunPython(migrate_part_revision_data, reverse_migrate_part_revision_data),
547
+
548
+ migrations.RemoveField(model_name='partrevision', name='attribute'),
549
+ migrations.RemoveField(model_name='partrevision', name='color'),
550
+ migrations.RemoveField(model_name='partrevision', name='current_rating'),
551
+ migrations.RemoveField(model_name='partrevision', name='current_rating_units'),
552
+ migrations.RemoveField(model_name='partrevision', name='finish'),
553
+ migrations.RemoveField(model_name='partrevision', name='frequency'),
554
+ migrations.RemoveField(model_name='partrevision', name='frequency_units'),
555
+ migrations.RemoveField(model_name='partrevision', name='height'),
556
+ migrations.RemoveField(model_name='partrevision', name='height_units'),
557
+ migrations.RemoveField(model_name='partrevision', name='interface'),
558
+ migrations.RemoveField(model_name='partrevision', name='length'),
559
+ migrations.RemoveField(model_name='partrevision', name='length_units'),
560
+ migrations.RemoveField(model_name='partrevision', name='material'),
561
+ migrations.RemoveField(model_name='partrevision', name='memory'),
562
+ migrations.RemoveField(model_name='partrevision', name='memory_units'),
563
+ migrations.RemoveField(model_name='partrevision', name='package'),
564
+ migrations.RemoveField(model_name='partrevision', name='pin_count'),
565
+ migrations.RemoveField(model_name='partrevision', name='power_rating'),
566
+ migrations.RemoveField(model_name='partrevision', name='power_rating_units'),
567
+ migrations.RemoveField(model_name='partrevision', name='supply_voltage'),
568
+ migrations.RemoveField(model_name='partrevision', name='supply_voltage_units'),
569
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating'),
570
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating_range_max'),
571
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating_range_min'),
572
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating_units'),
573
+ migrations.RemoveField(model_name='partrevision', name='tolerance'),
574
+ migrations.RemoveField(model_name='partrevision', name='value'),
575
+ migrations.RemoveField(model_name='partrevision', name='value_units'),
576
+ migrations.RemoveField(model_name='partrevision', name='voltage_rating'),
577
+ migrations.RemoveField(model_name='partrevision', name='voltage_rating_units'),
578
+ migrations.RemoveField(model_name='partrevision', name='wavelength'),
579
+ migrations.RemoveField(model_name='partrevision', name='wavelength_units'),
580
+ migrations.RemoveField(model_name='partrevision', name='weight'),
581
+ migrations.RemoveField(model_name='partrevision', name='weight_units'),
582
+ migrations.RemoveField(model_name='partrevision', name='width'),
583
+ migrations.RemoveField(model_name='partrevision', name='width_units'),
584
+ ]
File without changes