django-bom 1.243__py3-none-any.whl → 1.257__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,576 @@
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
+ definition, created = PartRevisionPropertyDefinition.objects.get_or_create(
219
+ code=code,
220
+ defaults={'name': name, 'type': type_val, 'quantity_of_measure': qom_obj, 'organization': None}
221
+ )
222
+ definitions[old_field] = definition
223
+
224
+ # 2. Create Dynamic PLM Definitions (Resistance, Capacitance, etc)
225
+ # These replace the generic 'value' column
226
+ qom_res = QuantityOfMeasure.objects.get(name='Resistance')
227
+ def_resistance, _ = PartRevisionPropertyDefinition.objects.get_or_create(
228
+ code='resistance',
229
+ defaults={'name': 'Resistance', 'type': 'number', 'quantity_of_measure': qom_res, 'organization': None}
230
+ )
231
+
232
+ qom_cap = QuantityOfMeasure.objects.get(name='Capacitance')
233
+ def_capacitance, _ = PartRevisionPropertyDefinition.objects.get_or_create(
234
+ code='capacitance',
235
+ defaults={'name': 'Capacitance', 'type': 'number', 'quantity_of_measure': qom_cap, 'organization': None}
236
+ )
237
+
238
+ qom_ind = QuantityOfMeasure.objects.get(name='Inductance')
239
+ def_inductance, _ = PartRevisionPropertyDefinition.objects.get_or_create(
240
+ code='inductance',
241
+ defaults={'name': 'Inductance', 'type': 'number', 'quantity_of_measure': qom_ind, 'organization': None}
242
+ )
243
+
244
+ # Fallback for Generic Parts (ICs, etc) that have a 'value' but no specific type
245
+ def_value, _ = PartRevisionPropertyDefinition.objects.get_or_create(
246
+ code='value',
247
+ defaults={'name': 'Value', 'type': 'string', 'quantity_of_measure': None, 'organization': None}
248
+ )
249
+
250
+ # 3. Helper for Unit Lookup
251
+ unit_cache = {}
252
+
253
+ def get_unit(symbol):
254
+ if not symbol: return None
255
+ clean_symbol = symbol.strip().lower()
256
+ symbol_map = {
257
+ 'ohm': 'Ω', 'ohms': 'Ω', 'kohm': 'kΩ', 'kohms': 'kΩ', 'mohm': 'MΩ', 'mohms': 'MΩ',
258
+ 'Ω': 'Ω', 'kΩ': 'kΩ', 'mΩ': 'mΩ', 'MΩ': 'MΩ',
259
+ 'f': 'F', 'farad': 'F', 'farads': 'F', 'mf': 'mF', 'uf': 'uF', 'nf': 'nF', 'pf': 'pF',
260
+ 'microfarad': 'uF', 'µf': 'uF',
261
+ 'v': 'V', 'volt': 'V', 'volts': 'V', 'mv': 'mV', 'uv': 'uV', 'kv': 'kV', 'µv': 'uV',
262
+ 'a': 'A', 'amp': 'A', 'amps': 'A', 'ampere': 'A', 'ma': 'mA', 'ua': 'uA', 'ka': 'kA', 'µa': 'uA',
263
+ 'h': 'H', 'henry': 'H', 'henries': 'H', 'mh': 'mH', 'uh': 'uH', 'nh': 'nH', 'µh': 'uH',
264
+ 'hz': 'Hz', 'khz': 'kHz', 'mhz': 'MHz', 'ghz': 'GHz',
265
+ 'w': 'W', 'mw': 'mW', 'uw': 'uW', 'kw': 'kW', 'mw': 'MW',
266
+ 'm': 'm', 'mm': 'mm', 'cm': 'cm', 'km': 'km', 'g': 'g', 'kg': 'kg', 'mg': 'mg',
267
+ 's': 's', 'ms': 'ms', 'us': 'us', 'ns': 'ns',
268
+ 'c': 'C', 'degc': 'C', '°c': 'C', 'f': 'F', 'degf': 'F', '°f': 'F',
269
+ 'ppm': 'ppm', '%': '%', 'percent': '%'
270
+ }
271
+ mapped_symbol = symbol_map.get(clean_symbol, symbol.strip())
272
+ if mapped_symbol in unit_cache: return unit_cache[mapped_symbol]
273
+ try:
274
+ unit = UnitDefinition.objects.filter(symbol=mapped_symbol).first()
275
+ if unit:
276
+ unit_cache[mapped_symbol] = unit
277
+ return unit
278
+ return None
279
+ except Exception:
280
+ return None
281
+
282
+ # 4. Batch Process Data
283
+ class_def_map = defaultdict(set)
284
+ batch_properties = []
285
+ BATCH_SIZE = 2000
286
+
287
+ for pr in PartRevision.objects.select_related('part', 'part__number_class').iterator():
288
+ part_class_id = getattr(pr.part, 'number_class_id', None)
289
+ part_class_name = getattr(pr.part.number_class, 'name', '').lower() if pr.part.number_class else ''
290
+
291
+ # --- A. HANDLE LEGACY 'VALUE' COLUMN INTELLIGENTLY ---
292
+ val = getattr(pr, 'value', None)
293
+ if val is not None and val != '':
294
+ # Decide which Definition to use based on Class
295
+ target_def = def_value # Default fallback
296
+ target_unit_name = None # For inference
297
+
298
+ if 'resistor' in part_class_name:
299
+ target_def = def_resistance
300
+ target_unit_name = 'Ω' # Infer Ohms if missing
301
+ elif 'capacitor' in part_class_name:
302
+ target_def = def_capacitance
303
+ target_unit_name = 'F' # Infer Farads if missing
304
+ elif 'inductor' in part_class_name or 'ferrite' in part_class_name:
305
+ target_def = def_inductance
306
+ target_unit_name = 'H'
307
+
308
+ if part_class_id:
309
+ class_def_map[part_class_id].add(target_def.id)
310
+
311
+ # Check for existing unit in legacy column
312
+ legacy_unit = getattr(pr, 'value_units', None)
313
+
314
+ # If legacy unit is missing, use our inferred one (e.g. Ohms for Resistors)
315
+ unit_ref = get_unit(legacy_unit) if legacy_unit else get_unit(target_unit_name)
316
+
317
+ prop = PartRevisionProperty(
318
+ part_revision=pr,
319
+ property_definition=target_def,
320
+ value_raw=str(val),
321
+ unit_definition=unit_ref
322
+ )
323
+
324
+ # Normalize
325
+ if unit_ref and val:
326
+ try:
327
+ clean_val = str(val).lower().replace('k', '').replace('m', '').replace('u', '')
328
+ val_dec = Decimal(clean_val)
329
+ mult_dec = Decimal(unit_ref.base_multiplier)
330
+ prop.value_normalized = val_dec * mult_dec
331
+ except (ValueError, TypeError, Exception):
332
+ pass
333
+
334
+ batch_properties.append(prop)
335
+
336
+ # --- B. HANDLE STANDARD COLUMNS ---
337
+ for old_field, definition in definitions.items():
338
+ val = getattr(pr, old_field, None)
339
+
340
+ if val is not None and val != '':
341
+ if part_class_id:
342
+ class_def_map[part_class_id].add(definition.id)
343
+
344
+ unit_field = old_field + '_units'
345
+ unit_val = getattr(pr, unit_field, None)
346
+
347
+ # Intelligent Inference for Tolerance
348
+ if old_field == 'tolerance':
349
+ str_val = str(val).lower()
350
+ if 'ppm' in str_val:
351
+ unit_val = 'ppm'
352
+ val = str_val.replace('ppm', '').strip()
353
+ elif '%' in str_val:
354
+ unit_val = '%'
355
+ val = str_val.replace('%', '').strip()
356
+ else:
357
+ if not any(x in str_val for x in ['f', 'v', 'h']):
358
+ unit_val = '%'
359
+
360
+ unit_ref = get_unit(unit_val)
361
+
362
+ prop = PartRevisionProperty(
363
+ part_revision=pr,
364
+ property_definition=definition,
365
+ value_raw=str(val),
366
+ unit_definition=unit_ref
367
+ )
368
+
369
+ if unit_ref and val:
370
+ try:
371
+ clean_val = str(val).lower().replace('k', '').replace('m', '').replace('u', '')
372
+ val_dec = Decimal(clean_val)
373
+ mult_dec = Decimal(unit_ref.base_multiplier)
374
+ prop.value_normalized = val_dec * mult_dec
375
+ except (ValueError, TypeError, Exception):
376
+ pass
377
+
378
+ batch_properties.append(prop)
379
+
380
+ if len(batch_properties) >= BATCH_SIZE:
381
+ PartRevisionProperty.objects.bulk_create(batch_properties)
382
+ batch_properties = []
383
+
384
+ if batch_properties:
385
+ PartRevisionProperty.objects.bulk_create(batch_properties)
386
+
387
+ for class_id, def_ids in class_def_map.items():
388
+ try:
389
+ pc = PartClass.objects.get(id=class_id)
390
+ pc.property_definitions.add(*def_ids)
391
+ except PartClass.DoesNotExist:
392
+ continue
393
+
394
+
395
+ def reverse_migrate_part_revision_data(apps, schema_editor):
396
+ """Restores data from PartRevisionProperties back to PartRevision columns."""
397
+ PartRevision = apps.get_model('bom', 'PartRevision')
398
+ PartRevisionProperty = apps.get_model('bom', 'PartRevisionProperty')
399
+
400
+ # Reverse Map: Property Name -> (Old Field Name, Old Unit Field Name)
401
+ field_map = get_field_map()
402
+ prop_name_to_field = {}
403
+ for old_field, (code, name, _, _) in field_map.items():
404
+ unit_field = old_field + '_units'
405
+ prop_name_to_field[name] = (old_field, unit_field)
406
+
407
+ # ADD DYNAMIC MAPPINGS FOR REVERSE
408
+ # Map the new PLM fields back to the old 'value' column so data is not lost on reverse
409
+ prop_name_to_field['Value'] = ('value', 'value_units')
410
+ prop_name_to_field['Resistance'] = ('value', 'value_units')
411
+ prop_name_to_field['Capacitance'] = ('value', 'value_units')
412
+ prop_name_to_field['Inductance'] = ('value', 'value_units')
413
+
414
+ batch_update_list = []
415
+ BATCH_SIZE = 2000
416
+ fields_to_update = set()
417
+
418
+ # Iterate Revisions
419
+ # Prefetch properties to avoid N+1
420
+ qs = PartRevision.objects.prefetch_related('properties', 'properties__property_definition',
421
+ 'properties__unit_definition')
422
+
423
+ for pr in qs.iterator():
424
+ updated = False
425
+ for prop in pr.properties.all():
426
+ prop_name = prop.property_definition.name
427
+ if prop_name in prop_name_to_field:
428
+ col_name, unit_col_name = prop_name_to_field[prop_name]
429
+
430
+ # Restore Value
431
+ if prop.value_raw:
432
+ setattr(pr, col_name, prop.value_raw)
433
+ fields_to_update.add(col_name)
434
+ updated = True
435
+
436
+ # Restore Unit
437
+ if prop.unit_definition and hasattr(pr, unit_col_name):
438
+ setattr(pr, unit_col_name, prop.unit_definition.symbol)
439
+ fields_to_update.add(unit_col_name)
440
+ updated = True
441
+
442
+ if updated:
443
+ batch_update_list.append(pr)
444
+
445
+ if len(batch_update_list) >= BATCH_SIZE:
446
+ PartRevision.objects.bulk_update(batch_update_list, list(fields_to_update))
447
+ batch_update_list = []
448
+
449
+ if batch_update_list and fields_to_update:
450
+ PartRevision.objects.bulk_update(batch_update_list, list(fields_to_update))
451
+
452
+ class Migration(migrations.Migration):
453
+ dependencies = [
454
+ ('bom', '0051_alter_manufacturer_organization_and_more'),
455
+ ]
456
+
457
+ operations = [
458
+ migrations.CreateModel(
459
+ name='PartRevisionPropertyDefinition',
460
+ fields=[
461
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
462
+ ('code', models.CharField(blank=True, max_length=64)),
463
+ ('name', models.CharField(max_length=64)),
464
+ ('type', models.CharField(choices=[('S', 'String'), ('D', 'Number'), ('B', 'Boolean')], max_length=1)),
465
+ ('required', models.BooleanField(default=False)),
466
+ ('organization',
467
+ models.ForeignKey(blank=True, help_text='Leave empty for a Global/System record.', null=True,
468
+ on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL)),
469
+ ],
470
+ ),
471
+ migrations.AddField(
472
+ model_name='partclass',
473
+ name='property_definitions',
474
+ field=models.ManyToManyField(blank=True, related_name='part_classes',
475
+ to='bom.partrevisionpropertydefinition'),
476
+ ),
477
+ migrations.CreateModel(
478
+ name='QuantityOfMeasure',
479
+ fields=[
480
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
481
+ ('name', models.CharField(help_text='e.g. Voltage', max_length=64)),
482
+ ('organization',
483
+ models.ForeignKey(blank=True, help_text='Leave empty for a Global/System record.', null=True,
484
+ on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL)),
485
+ ],
486
+ options={
487
+ 'unique_together': {('organization', 'name')},
488
+ },
489
+ ),
490
+ migrations.AddField(
491
+ model_name='partrevisionpropertydefinition',
492
+ name='quantity_of_measure',
493
+ field=models.ForeignKey(blank=True,
494
+ null=True, on_delete=django.db.models.deletion.SET_NULL,
495
+ to='bom.quantityofmeasure'),
496
+ ),
497
+ migrations.CreateModel(
498
+ name='UnitDefinition',
499
+ fields=[
500
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
501
+ ('name', models.CharField(max_length=64)),
502
+ ('symbol', models.CharField(max_length=16)),
503
+ ('base_multiplier', models.DecimalField(decimal_places=20, default=Decimal('1.0'), max_digits=40)),
504
+ ('organization',
505
+ models.ForeignKey(blank=True, help_text='Leave empty for a Global/System record.', null=True,
506
+ on_delete=django.db.models.deletion.CASCADE, to=settings.BOM_ORGANIZATION_MODEL)),
507
+ ('quantity_of_measure',
508
+ models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='units',
509
+ to='bom.quantityofmeasure')),
510
+ ],
511
+ options={
512
+ 'ordering': ['base_multiplier'],
513
+ 'unique_together': {('organization', 'quantity_of_measure', 'symbol')},
514
+ },
515
+ ),
516
+ migrations.CreateModel(
517
+ name='PartRevisionProperty',
518
+ fields=[
519
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
520
+ ('value_raw', models.CharField(max_length=255)),
521
+ ('value_normalized', models.DecimalField(blank=True, decimal_places=20, max_digits=40, null=True)),
522
+ ('part_revision',
523
+ models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='properties',
524
+ to='bom.partrevision')),
525
+ ('property_definition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
526
+ to='bom.partrevisionpropertydefinition')),
527
+ ('unit_definition',
528
+ models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
529
+ to='bom.unitdefinition')),
530
+ ],
531
+ ),
532
+ migrations.AlterUniqueTogether(
533
+ name='partrevisionpropertydefinition',
534
+ unique_together={('organization', 'code')},
535
+ ),
536
+
537
+ migrations.RunPython(create_unit_definitions, remove_unit_definitions),
538
+ migrations.RunPython(migrate_part_revision_data, reverse_migrate_part_revision_data),
539
+
540
+ migrations.RemoveField(model_name='partrevision', name='attribute'),
541
+ migrations.RemoveField(model_name='partrevision', name='color'),
542
+ migrations.RemoveField(model_name='partrevision', name='current_rating'),
543
+ migrations.RemoveField(model_name='partrevision', name='current_rating_units'),
544
+ migrations.RemoveField(model_name='partrevision', name='finish'),
545
+ migrations.RemoveField(model_name='partrevision', name='frequency'),
546
+ migrations.RemoveField(model_name='partrevision', name='frequency_units'),
547
+ migrations.RemoveField(model_name='partrevision', name='height'),
548
+ migrations.RemoveField(model_name='partrevision', name='height_units'),
549
+ migrations.RemoveField(model_name='partrevision', name='interface'),
550
+ migrations.RemoveField(model_name='partrevision', name='length'),
551
+ migrations.RemoveField(model_name='partrevision', name='length_units'),
552
+ migrations.RemoveField(model_name='partrevision', name='material'),
553
+ migrations.RemoveField(model_name='partrevision', name='memory'),
554
+ migrations.RemoveField(model_name='partrevision', name='memory_units'),
555
+ migrations.RemoveField(model_name='partrevision', name='package'),
556
+ migrations.RemoveField(model_name='partrevision', name='pin_count'),
557
+ migrations.RemoveField(model_name='partrevision', name='power_rating'),
558
+ migrations.RemoveField(model_name='partrevision', name='power_rating_units'),
559
+ migrations.RemoveField(model_name='partrevision', name='supply_voltage'),
560
+ migrations.RemoveField(model_name='partrevision', name='supply_voltage_units'),
561
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating'),
562
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating_range_max'),
563
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating_range_min'),
564
+ migrations.RemoveField(model_name='partrevision', name='temperature_rating_units'),
565
+ migrations.RemoveField(model_name='partrevision', name='tolerance'),
566
+ migrations.RemoveField(model_name='partrevision', name='value'),
567
+ migrations.RemoveField(model_name='partrevision', name='value_units'),
568
+ migrations.RemoveField(model_name='partrevision', name='voltage_rating'),
569
+ migrations.RemoveField(model_name='partrevision', name='voltage_rating_units'),
570
+ migrations.RemoveField(model_name='partrevision', name='wavelength'),
571
+ migrations.RemoveField(model_name='partrevision', name='wavelength_units'),
572
+ migrations.RemoveField(model_name='partrevision', name='weight'),
573
+ migrations.RemoveField(model_name='partrevision', name='weight_units'),
574
+ migrations.RemoveField(model_name='partrevision', name='width'),
575
+ migrations.RemoveField(model_name='partrevision', name='width_units'),
576
+ ]