django-bom 1.252__py3-none-any.whl → 1.258__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.
@@ -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
+ ]