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.
bom/views/views.py CHANGED
@@ -47,10 +47,14 @@ from bom.forms import (
47
47
  PartInfoForm,
48
48
  PartRevisionForm,
49
49
  PartRevisionNewForm,
50
+ PartRevisionPropertyDefinitionForm,
51
+ PartRevisionPropertyDefinitionFormSet,
52
+ QuantityOfMeasureForm,
50
53
  Seller,
51
54
  SellerForm,
52
55
  SellerPartForm,
53
56
  SubpartForm,
57
+ UnitDefinitionFormSet,
54
58
  UploadBOMForm,
55
59
  UserAddForm,
56
60
  UserCreateForm,
@@ -66,14 +70,18 @@ from bom.models import (
66
70
  Part,
67
71
  PartClass,
68
72
  PartRevision,
73
+ PartRevisionPropertyDefinition,
74
+ QuantityOfMeasure,
69
75
  SellerPart,
70
76
  Subpart,
77
+ UnitDefinition,
71
78
  User,
72
- UserMeta,
79
+ get_user_meta_model
73
80
  )
74
81
  from bom.utils import check_references_for_duplicates, listify_string, prep_for_sorting_nicely
75
82
 
76
83
  logger = logging.getLogger(__name__)
84
+ UserMeta = get_user_meta_model()
77
85
  BOM_LOGIN_URL = getattr(settings, "BOM_LOGIN_URL", None) or settings.LOGIN_URL
78
86
 
79
87
  def form_error_messages(form_errors) -> [str]:
@@ -244,7 +252,7 @@ def home(request):
244
252
  for field_name in csv_headers.get_default_all():
245
253
  if field_name not in csv_headers.get_defaults_list(['part_number', 'part_category', 'part_synopsis', 'part_revision', 'part_manufacturer', 'part_manufacturer_part_number', ]
246
254
  + seller_csv_headers.get_default_all()):
247
- attr = getattr(part_rev, field_name)
255
+ attr = part_rev.get_field_value(field_name)
248
256
  row.update({csv_headers.get_default(field_name): attr if attr is not None else ''})
249
257
  else:
250
258
  row = {
@@ -257,7 +265,7 @@ def home(request):
257
265
  for field_name in csv_headers.get_default_all():
258
266
  if field_name not in csv_headers.get_defaults_list(['part_number', 'part_synopsis', 'part_revision', 'part_manufacturer', 'part_manufacturer_part_number', ]
259
267
  + seller_csv_headers.get_default_all()):
260
- attr = getattr(part_rev, field_name)
268
+ attr = part_rev.get_field_value(field_name)
261
269
  row.update({csv_headers.get_default(field_name): attr if attr is not None else ''})
262
270
 
263
271
  sellerparts = part_rev.part.seller_parts()
@@ -355,6 +363,9 @@ def bom_settings(request, tab_anchor=None):
355
363
  name = 'settings'
356
364
 
357
365
  part_classes = PartClass.objects.all().filter(organization=organization)
366
+ property_definitions = PartRevisionPropertyDefinition.objects.available_to(organization=organization).order_by(
367
+ 'name')
368
+ quantities_of_measure = QuantityOfMeasure.objects.available_to(organization=organization).order_by('name')
358
369
 
359
370
  users_in_organization = User.objects.filter(
360
371
  id__in=UserMeta.objects.filter(organization=organization).values_list('user', flat=True)).order_by(
@@ -394,14 +405,22 @@ def bom_settings(request, tab_anchor=None):
394
405
  messages.error(request, "You need a Pro subscription to add users.")
395
406
  elif not user_can_manage_members:
396
407
  messages.error(request, "You are not allowed to manage users, contact your organization admin.")
408
+ elif not has_member_capacity:
409
+ messages.error(request,
410
+ "You have reached your organization's member capacity. Manage subscription to add more members.")
397
411
  else:
398
412
  user_add_form = UserAddForm(request.POST, organization=organization)
399
413
  if user_add_form.is_valid():
400
414
  added_user_profile = user_add_form.save()
401
415
  messages.info(request, f"Added {added_user_profile.user.first_name} {added_user_profile.user.last_name} to your organization.")
402
416
  else:
403
- messages.error(request, user_add_form.errors)
404
-
417
+ for field, errors in user_add_form.errors.items():
418
+ for error in errors:
419
+ messages.error(request, f"{field.capitalize()}: {error}")
420
+ users_in_organization.all()
421
+ users_in_organization_count = users_in_organization.count()
422
+ has_member_capacity = users_in_organization_count < organization.subscription_quantity
423
+ seats_available = max(organization.subscription_quantity - users_in_organization_count, 0)
405
424
  elif 'clear-add-user' in request.POST:
406
425
  tab_anchor = ORGANIZATION_TAB
407
426
  user_add_form = UserAddForm()
@@ -421,7 +440,10 @@ def bom_settings(request, tab_anchor=None):
421
440
  user_meta.save()
422
441
  except UserMeta.DoesNotExist:
423
442
  messages.error(request, "No user found with given id {}.".format(user_meta_id))
424
-
443
+ users_in_organization.all()
444
+ users_in_organization_count = users_in_organization.count()
445
+ has_member_capacity = users_in_organization_count < organization.subscription_quantity
446
+ seats_available = max(organization.subscription_quantity - users_in_organization_count, 0)
425
447
  elif 'submit-edit-organization' in request.POST:
426
448
  tab_anchor = ORGANIZATION_TAB
427
449
  organization_form = OrganizationFormEditSettings(request.POST, instance=organization, user=user)
@@ -521,6 +543,8 @@ def bom_settings(request, tab_anchor=None):
521
543
  profile.save()
522
544
  if users_in_organization == 0:
523
545
  organization.delete()
546
+ else:
547
+ messages.warning(request, "No action was taken because no form field was submitted.")
524
548
 
525
549
  user_form = UserForm(instance=user)
526
550
  user_add_form = UserAddForm()
@@ -697,29 +721,19 @@ def seller_delete(request, seller_id):
697
721
 
698
722
  @login_required(login_url=BOM_LOGIN_URL)
699
723
  def user_meta_edit(request, user_meta_id):
700
- user = request.user
701
- profile = user.bom_profile()
702
- organization = profile.organization
703
-
704
724
  user_meta = get_object_or_404(UserMeta, pk=user_meta_id)
705
- user_meta_user = get_object_or_404(User, pk=user_meta.user.id)
706
- title = 'Edit User {}'.format(user_meta.user.__str__())
725
+ user = user_meta.user
726
+ organization = user_meta.organization
727
+ title = f'Manage Member'
707
728
 
708
729
  if request.method == 'POST':
709
- user_meta_user_form = UserForm(request.POST, instance=user_meta_user)
710
- if user_meta_user_form.is_valid():
711
- user_meta_form = UserMetaForm(request.POST, instance=user_meta, organization=organization)
712
- if user_meta_form.is_valid():
713
- user_meta_user_form.save()
714
- user_meta_form.save()
715
- return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'organization'}))
716
-
730
+ form = UserAddForm(request.POST, instance=user_meta, organization=organization, exclude_username=True)
731
+ if form.is_valid():
732
+ form.save()
733
+ return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'organization'}))
717
734
  return TemplateResponse(request, 'bom/edit-user-meta.html', locals())
718
-
719
735
  else:
720
- user_meta_user_form = UserForm(instance=user_meta_user)
721
- user_meta_form = UserMetaForm(instance=user_meta, organization=organization)
722
-
736
+ form = UserAddForm(instance=user_meta, organization=organization, exclude_username=True)
723
737
  return TemplateResponse(request, 'bom/edit-user-meta.html', locals())
724
738
 
725
739
 
@@ -940,7 +954,7 @@ def upload_bom(request):
940
954
  else:
941
955
  messages.error(request, upload_bom_form.errors)
942
956
  else:
943
- upload_bom_form = UploadBOMForm(initial={'organization': organization})
957
+ upload_bom_form = UploadBOMForm(organization=organization, initial={'organization': organization})
944
958
  bom_csv_form = BOMCSVForm()
945
959
 
946
960
  return TemplateResponse(request, 'bom/upload-bom.html', locals())
@@ -1019,22 +1033,15 @@ def export_part_list(request):
1019
1033
  'number_item',
1020
1034
  'number_variation')
1021
1035
 
1022
- fieldnames = [
1023
- 'part_number',
1024
- 'part_synopsis',
1025
- 'part_revision',
1026
- 'part_manufacturer',
1027
- 'part_manufacturer_part_number',
1028
- ]
1029
-
1030
1036
  csv_headers = organization.part_list_csv_headers()
1031
- writer = csv.DictWriter(response, fieldnames=fieldnames)
1037
+ writer = csv.DictWriter(response, fieldnames=csv_headers.get_default_all())
1032
1038
  writer.writeheader()
1033
1039
  for item in parts:
1034
1040
  try:
1041
+ latest_rev = item.latest()
1035
1042
  row = {
1036
1043
  csv_headers.get_default('part_number'): item.full_part_number(),
1037
- csv_headers.get_default('part_revision'): item.latest().revision,
1044
+ csv_headers.get_default('part_revision'): latest_rev.revision if latest_rev else '',
1038
1045
  csv_headers.get_default('part_manufacturer'): item.primary_manufacturer_part.manufacturer.name if item.primary_manufacturer_part is not None and item.primary_manufacturer_part.manufacturer is not None else '',
1039
1046
  csv_headers.get_default('part_manufacturer_part_number'): item.primary_manufacturer_part.manufacturer_part_number if item.primary_manufacturer_part is not None and item.primary_manufacturer_part.manufacturer is not None else '',
1040
1047
  }
@@ -1042,7 +1049,7 @@ def export_part_list(request):
1042
1049
  if field_name not in csv_headers.get_defaults_list(
1043
1050
  ['part_number', 'part_category', 'part_synopsis', 'part_revision', 'part_manufacturer',
1044
1051
  'part_manufacturer_part_number', ]):
1045
- attr = getattr(item, field_name)
1052
+ attr = latest_rev.get_field_value(field_name) if latest_rev else None
1046
1053
  row.update({csv_headers.get_default(field_name): attr if attr is not None else ''})
1047
1054
  writer.writerow({k: smart_str(v) for k, v in row.items()})
1048
1055
 
@@ -1071,7 +1078,7 @@ def create_part(request):
1071
1078
  part_form = PartForm(request.POST, organization=organization)
1072
1079
  manufacturer_form = ManufacturerForm(request.POST)
1073
1080
  manufacturer_part_form = ManufacturerPartForm(request.POST, organization=organization)
1074
- part_revision_form = PartRevisionForm(request.POST)
1081
+ part_revision_form = PartRevisionForm(request.POST, organization=organization)
1075
1082
  # Checking if part form is valid checks for number uniqueness
1076
1083
  if part_form.is_valid() and manufacturer_form.is_valid() and manufacturer_part_form.is_valid():
1077
1084
  mpn = manufacturer_part_form.cleaned_data['manufacturer_part_number']
@@ -1096,6 +1103,8 @@ def create_part(request):
1096
1103
  new_part.number_class = None
1097
1104
  new_part.number_variation = None
1098
1105
 
1106
+ part_revision_form = PartRevisionForm(request.POST, part_class=new_part.number_class,
1107
+ organization=organization)
1099
1108
  if part_revision_form.is_valid():
1100
1109
  # Save the Part before the PartRevision, as this will again check for part
1101
1110
  # number uniqueness. This way if someone else(s) working concurrently is also
@@ -1106,7 +1115,8 @@ def create_part(request):
1106
1115
  pr.part = new_part # Associate PartRevision with Part
1107
1116
  pr.save()
1108
1117
  except IntegrityError as err:
1109
- messages.error(request, "Error! Already created a part with part number {0}-{1}-{3}}".format(new_part.number_class.code, new_part.number_item, new_part.number_variation))
1118
+ messages.error(request, "Error! Already created a part with part number {0}-{1}-{2}}".format(
1119
+ new_part.number_class.code, new_part.number_item, new_part.number_variation))
1110
1120
  return TemplateResponse(request, 'bom/create-part.html', locals())
1111
1121
  else:
1112
1122
  messages.error(request, part_revision_form.errors)
@@ -1126,7 +1136,8 @@ def create_part(request):
1126
1136
  else:
1127
1137
  # Initialize organization in the form's model and in the form itself:
1128
1138
  part_form = PartForm(initial={'organization': organization}, organization=organization)
1129
- part_revision_form = PartRevisionForm(initial={'revision': 1, 'organization': organization})
1139
+ part_revision_form = PartRevisionForm(initial={'revision': 1, 'organization': organization},
1140
+ organization=organization)
1130
1141
  manufacturer_form = ManufacturerForm(initial={'organization': organization})
1131
1142
  manufacturer_part_form = ManufacturerPartForm(organization=organization)
1132
1143
 
@@ -1266,6 +1277,117 @@ def remove_subpart(request, part_id, part_revision_id, subpart_id):
1266
1277
  reverse('bom:part-manage-bom', kwargs={'part_id': part_id, 'part_revision_id': part_revision_id}))
1267
1278
 
1268
1279
 
1280
+ @login_required(login_url=BOM_LOGIN_URL)
1281
+ def property_definition_edit(request, property_definition_id=None):
1282
+ user = request.user
1283
+ profile = user.bom_profile()
1284
+ organization = profile.organization
1285
+
1286
+ if property_definition_id:
1287
+ property_definition = get_object_or_404(PartRevisionPropertyDefinition, pk=property_definition_id)
1288
+ if property_definition.organization != organization:
1289
+ messages.error(request, "Can't access a property definition that is not yours!")
1290
+ return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'indabom'}))
1291
+ title = f'Edit Property Definition {property_definition.name}'
1292
+ else:
1293
+ property_definition = None
1294
+ title = 'Add Property Definition'
1295
+
1296
+ if request.method == 'POST':
1297
+ form = PartRevisionPropertyDefinitionForm(request.POST, instance=property_definition, organization=organization)
1298
+ if form.is_valid():
1299
+ property_definition = form.save(commit=False)
1300
+ property_definition.organization = organization
1301
+ property_definition.save()
1302
+ return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'indabom'}))
1303
+ else:
1304
+ form = PartRevisionPropertyDefinitionForm(instance=property_definition, organization=organization)
1305
+
1306
+ return TemplateResponse(request, 'bom/bom-form.html', locals())
1307
+
1308
+
1309
+ @login_required(login_url=BOM_LOGIN_URL)
1310
+ @organization_admin
1311
+ def property_definition_delete(request, property_definition_id):
1312
+ user = request.user
1313
+ profile = user.bom_profile()
1314
+ organization = profile.organization
1315
+ property_definition = get_object_or_404(PartRevisionPropertyDefinition, pk=property_definition_id)
1316
+ if property_definition.organization != organization:
1317
+ messages.error(request, "Can't delete a property definition that is not yours!")
1318
+ else:
1319
+ property_definition.delete()
1320
+ return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'indabom'}))
1321
+
1322
+
1323
+ @login_required(login_url=BOM_LOGIN_URL)
1324
+ def quantity_of_measure_edit(request, quantity_of_measure_id=None):
1325
+ user = request.user
1326
+ profile = user.bom_profile()
1327
+ organization = profile.organization
1328
+
1329
+ if quantity_of_measure_id:
1330
+ quantity_of_measure = get_object_or_404(QuantityOfMeasure, pk=quantity_of_measure_id)
1331
+ if quantity_of_measure.organization != organization:
1332
+ messages.error(request, "Can't access a quantity of measure that is not yours!")
1333
+ return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'indabom'}))
1334
+ title = f'Edit Quantity of Measure {quantity_of_measure.name}'
1335
+ else:
1336
+ quantity_of_measure = None
1337
+ title = 'Add Quantity of Measure'
1338
+
1339
+ if request.method == 'POST':
1340
+ form = QuantityOfMeasureForm(request.POST, instance=quantity_of_measure, organization=organization)
1341
+ unit_formset = UnitDefinitionFormSet(
1342
+ request.POST,
1343
+ queryset=quantity_of_measure.units.all() if quantity_of_measure else UnitDefinition.objects.none(),
1344
+ form_kwargs={'organization': organization},
1345
+ prefix='units'
1346
+ )
1347
+
1348
+ if form.is_valid() and unit_formset.is_valid():
1349
+ quantity_of_measure = form.save()
1350
+ units = unit_formset.save(commit=False)
1351
+ base_multipliers = [float(unit.base_multiplier) for unit in units]
1352
+ if 1.0 not in base_multipliers:
1353
+ messages.error(request, "Must have a base multiplier of 1.0 for at least 1 unit.")
1354
+ return TemplateResponse(request, 'bom/edit-quantity-of-measure.html', locals())
1355
+
1356
+ for unit in units:
1357
+ unit.organization = organization
1358
+ unit.quantity_of_measure = quantity_of_measure
1359
+ unit.save()
1360
+ for obj in unit_formset.deleted_objects:
1361
+ obj.delete()
1362
+ return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'indabom'}))
1363
+ else:
1364
+ messages.error(request, form.errors)
1365
+ messages.error(request, unit_formset.errors)
1366
+ else:
1367
+ form = QuantityOfMeasureForm(instance=quantity_of_measure, organization=organization)
1368
+ unit_formset = UnitDefinitionFormSet(
1369
+ queryset=quantity_of_measure.units.all() if quantity_of_measure else UnitDefinition.objects.none(),
1370
+ form_kwargs={'organization': organization},
1371
+ prefix='units'
1372
+ )
1373
+
1374
+ return TemplateResponse(request, 'bom/edit-quantity-of-measure.html', locals())
1375
+
1376
+
1377
+ @login_required(login_url=BOM_LOGIN_URL)
1378
+ @organization_admin
1379
+ def quantity_of_measure_delete(request, quantity_of_measure_id):
1380
+ user = request.user
1381
+ profile = user.bom_profile()
1382
+ organization = profile.organization
1383
+ quantity_of_measure = get_object_or_404(QuantityOfMeasure, pk=quantity_of_measure_id)
1384
+ if quantity_of_measure.organization != organization:
1385
+ messages.error(request, "Can't delete a quantity of measure that is not yours!")
1386
+ else:
1387
+ quantity_of_measure.delete()
1388
+ return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'indabom'}))
1389
+
1390
+
1269
1391
  @login_required(login_url=BOM_LOGIN_URL)
1270
1392
  def part_class_edit(request, part_class_id):
1271
1393
  user = request.user
@@ -1277,15 +1399,40 @@ def part_class_edit(request, part_class_id):
1277
1399
 
1278
1400
  if request.method == 'POST':
1279
1401
  part_class_form = PartClassForm(request.POST, instance=part_class, organization=organization)
1280
- if part_class_form.is_valid():
1281
- part_class_form.save()
1402
+ property_definitions_formset = PartRevisionPropertyDefinitionFormSet(
1403
+ request.POST,
1404
+ form_kwargs={'organization': organization},
1405
+ prefix='prop-def'
1406
+ )
1407
+
1408
+ if part_class_form.is_valid() and property_definitions_formset.is_valid():
1409
+ part_class = part_class_form.save()
1410
+
1411
+ # Clear current associations and re-add from formset
1412
+ part_class.property_definitions.clear()
1413
+ for form in property_definitions_formset:
1414
+ if form.cleaned_data and not form.cleaned_data.get('DELETE'):
1415
+ definition = form.cleaned_data.get('property_definition')
1416
+ if definition:
1417
+ part_class.property_definitions.add(definition)
1418
+
1282
1419
  return HttpResponseRedirect(reverse('bom:settings', kwargs={'tab_anchor': 'indabom'}))
1283
1420
 
1284
1421
  else:
1422
+ if not part_class_form.is_valid():
1423
+ messages.error(request, part_class_form.errors)
1424
+ if not property_definitions_formset.is_valid():
1425
+ messages.error(request, property_definitions_formset.errors)
1285
1426
  return TemplateResponse(request, 'bom/edit-part-class.html', locals())
1286
1427
 
1287
1428
  else:
1288
1429
  part_class_form = PartClassForm(instance=part_class, organization=organization)
1430
+ initial = [{'property_definition': pd} for pd in part_class.property_definitions.all().order_by('name')]
1431
+ property_definitions_formset = PartRevisionPropertyDefinitionFormSet(
1432
+ initial=initial,
1433
+ form_kwargs={'organization': organization},
1434
+ prefix='prop-def',
1435
+ )
1289
1436
 
1290
1437
  return TemplateResponse(request, 'bom/edit-part-class.html', locals())
1291
1438
 
@@ -1536,7 +1683,8 @@ def part_revision_new(request, part_id):
1536
1683
  used_part_revisions = all_used_in_prs.filter(configuration='W')
1537
1684
 
1538
1685
  if request.method == 'POST':
1539
- part_revision_new_form = PartRevisionNewForm(request.POST, part=part, revision=next_revision_number, assembly=latest_revision.assembly)
1686
+ part_revision_new_form = PartRevisionNewForm(request.POST, part=part, revision=next_revision_number,
1687
+ assembly=latest_revision.assembly, organization=organization)
1540
1688
  if part_revision_new_form.is_valid():
1541
1689
  new_part_revision = part_revision_new_form.save()
1542
1690
 
@@ -1568,9 +1716,9 @@ def part_revision_new(request, part_id):
1568
1716
  if latest_revision:
1569
1717
  messages.info(request, 'New revision automatically incremented to `{}` from your last revision `{}`.'.format(next_revision_number, latest_revision.revision))
1570
1718
  latest_revision.revision = next_revision_number # use updated object to populate form but don't save changes
1571
- part_revision_new_form = PartRevisionNewForm(instance=latest_revision)
1719
+ part_revision_new_form = PartRevisionNewForm(instance=latest_revision, organization=organization)
1572
1720
  else:
1573
- part_revision_new_form = PartRevisionNewForm()
1721
+ part_revision_new_form = PartRevisionNewForm(organization=organization)
1574
1722
 
1575
1723
  return TemplateResponse(request, 'bom/part-revision-new.html', locals())
1576
1724
 
@@ -1588,12 +1736,12 @@ def part_revision_edit(request, part_id, part_revision_id):
1588
1736
  action = reverse('bom:part-revision-edit', kwargs={'part_id': part_id, 'part_revision_id': part_revision_id})
1589
1737
 
1590
1738
  if request.method == 'POST':
1591
- form = PartRevisionForm(request.POST, instance=part_revision)
1739
+ form = PartRevisionForm(request.POST, instance=part_revision, organization=organization)
1592
1740
  if form.is_valid():
1593
1741
  form.save()
1594
1742
  return HttpResponseRedirect(reverse('bom:part-info', kwargs={'part_id': part_id}))
1595
1743
  else:
1596
- form = PartRevisionForm(instance=part_revision)
1744
+ form = PartRevisionForm(instance=part_revision, organization=organization)
1597
1745
 
1598
1746
  return TemplateResponse(request, 'bom/part-revision-edit.html', locals())
1599
1747
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-bom
3
- Version: 1.243
3
+ Version: 1.257
4
4
  Summary: A simple Django app to manage a bill of materials.
5
5
  Author-email: Mike Kasparian <mpkasp@gmail.com>
6
6
  License: GPL-3.0-only
@@ -1,21 +1,21 @@
1
1
  bom/__init__.py,sha256=HuvSMR9cYQcppTZGD0XjUVUBtHWwWMh1yMQzk_2wTS4,41
2
- bom/admin.py,sha256=4xN38uSIGo54MVoo2MXUbvcfuPSAifDc8g0arrEAW4Q,4093
2
+ bom/admin.py,sha256=MmCa4X9J5aKNbQTrfyLnDdoIt9MrlS3rI1sN9EkhsIs,5892
3
3
  bom/apps.py,sha256=TJMUTSX1h2genPwCq6SN6g1fhrrSmjEXGg2zQFa_ryM,147
4
- bom/auth_backends.py,sha256=8ViZfP01fITPQnPvsTe_RuKx-ur9dhSOQE3aOp9ESV8,1429
4
+ bom/auth_backends.py,sha256=Xj3MCUJg3r9M4v1c8wrxC3lwpE5F8_2_HD5s9M_5Rms,1480
5
5
  bom/base_classes.py,sha256=CrWD7wlIkwYb90VoGcVOcw2WoQszRrCje-Re5d_UW1Q,1183
6
- bom/constants.py,sha256=5CFE0uvKTL91w24rqdAB6EMgpIyw0HhfmmktxF4gCgs,4296
6
+ bom/constants.py,sha256=Vs3-s1_ej8hDMlq5YC01vZXs1TECuPe9sn834UierLI,4666
7
7
  bom/context_processors.py,sha256=OxMVCqxGtRoHR7aJfTs6xANpxJQzBDUIuNTG1f_Xjoo,312
8
- bom/csv_headers.py,sha256=1VDJ7aNqYjDhf5_rfWBZqO6wsIS99oD01yXx2nNmIHQ,12620
8
+ bom/csv_headers.py,sha256=0WLvgchbTtW_9ya3UC_qL5hv7muuklf_vtoMNBFAOoc,11468
9
9
  bom/decorators.py,sha256=fCK-lxJTBp3LEdZtKMyQg5hRqxQm5RJny9eb7oyjbtE,1233
10
10
  bom/form_fields.py,sha256=tLqchl0j8izBCZnm82NMyHpQV6EuGgCjCl5lrnGR2V0,2816
11
- bom/forms.py,sha256=RUwiMKeaQkapBwz6JCi4Kkl4e5DlFAgSEMjytEDzmoE,69302
12
- bom/helpers.py,sha256=ONsDM0agG9sKJWMjN4IRNlWx2HNF7T0CXM-ts0GRiAY,15031
11
+ bom/forms.py,sha256=_hsEdhyr2QDQ-Ei7ZRcv4N9s4lmbYR4xT5-NhyJ8rB4,57312
12
+ bom/helpers.py,sha256=jzwNDgD-4htdJfWWjNy8ZhVqfdgSxhNpsFn6kDpC5UU,20172
13
13
  bom/local_settings.py,sha256=yE4aupIquCWsFms44qoCrRrlIyM3sqpOkiwyj1WLxI8,820
14
- bom/models.py,sha256=poZoct60kAULCs8RlqC3MFtOl84l2ZfZzjb_d3t9z2g,36537
14
+ bom/models.py,sha256=dzvhU68hu5Wam4RvD3e75dcsqsbVmBWVe8vi57w40hQ,38424
15
15
  bom/part_bom.py,sha256=30HYAKAEhtadiM9tk6vgCQnn7gNJeuXbzF5gXvMvKG4,8720
16
- bom/settings.py,sha256=t3jamLeW4yJWIfB0aa5lbyz9xBCi-wcxTPEZxv5hswQ,8116
17
- bom/tests.py,sha256=ZqcTUYVXeWjAqzKAV6hp6SKTU0_IOTwIEboTujl7N_M,69905
18
- bom/urls.py,sha256=sGNKO8BsTO_TDPsqB-c_fqRozaNHOf9WYRaOy-7OLAE,6841
16
+ bom/settings.py,sha256=aBg0PHaK9NvNZNmfnPrU4-kp2GQeeAGMB_EVvMoAmJs,8197
17
+ bom/tests.py,sha256=Grn2aoXR7AnoVJ9Wa_stgkofk94WS7sPcVKWxpelvY0,73980
18
+ bom/urls.py,sha256=7yjQ7ONydqZHHFHd9jWThEu_03r3LyDcVJ-BGYNy6LQ,7589
19
19
  bom/utils.py,sha256=z_2jACSkRc0hsc0mdR8tOK10KiSDeM0a6rXIpztPDuA,7302
20
20
  bom/validators.py,sha256=tiQYIblITCOwb8m1kfs_CFmFnJofCtFzqENlxBuGL7Y,872
21
21
  bom/wsgi.py,sha256=-2oAfSSVdNFCjxaKXcPp-JL_K8AhVClzls6OC_ZI1Jc,380
@@ -69,12 +69,14 @@ bom/migrations/0047_sellerpart_seller_part_number.py,sha256=QdjdWMNlSyLHB7uSTq_8
69
69
  bom/migrations/0048_rename_part_organization_number_class_bom_part_organiz_b333d6_idx_and_more.py,sha256=rZ_mfd_xFu4BglhYxkNuQVqwThZ721kjrlCAn9LkNRo,50969
70
70
  bom/migrations/0049_alter_assembly_id_alter_assemblysubparts_id_and_more.py,sha256=l1q5BCNVYWD6Ngf9pGzYq1hQMvvshmdFlm33On63YNc,3247
71
71
  bom/migrations/0050_alter_organization_options.py,sha256=n-YGAoUdUxYdh5NY0Zpz2T4CWEOR7tDjSFFk-KZD_tw,432
72
+ bom/migrations/0051_alter_manufacturer_organization_and_more.py,sha256=xjnkZhEgsJDsfK9leBPxxQ2oG_Qf2FdrttTx-lldmjw,1539
73
+ bom/migrations/0052_remove_partrevision_attribute_and_more.py,sha256=dD-wYk2VUkFJvyeJZTftcb60qS48c1foXUh9NkFgbiE,31148
72
74
  bom/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
75
  bom/static/bom/css/dashboard.css,sha256=saLXUpnVRjsV9HNdsmQD4Eq-zBlm8R2YePhvoc1uifk,245
74
76
  bom/static/bom/css/jquery.treetable.css,sha256=H37aGBAAFP3R6v08nui9gKSdLE2VGsGsmlttrIImzfE,652
75
77
  bom/static/bom/css/materialize.min.css,sha256=OweaP_Ic6rsV-lysfyS4h-LM6sRwuO3euTYfr6M124g,141841
76
78
  bom/static/bom/css/part-info.css,sha256=xQ6zJXHLStwU76UVoYxjG-MjdQzFRwg-jANqrj_KFS0,252
77
- bom/static/bom/css/style.css,sha256=_UDH_6aa77N7tWxZNNQv6LblE0hHSLkdTxWzpKJpEwk,6326
79
+ bom/static/bom/css/style.css,sha256=NARNKKTNIE-3CU2Glcqbs8fzfedRhbRzIFSRza_bT6I,6737
78
80
  bom/static/bom/css/tablesorter-theme.materialize.css,sha256=S7DYXb4vdqdDSUouZ8aIbAxIpemhIzFfeRySdnvvSlc,7435
79
81
  bom/static/bom/css/treetable-theme.css,sha256=RxMklK-XfcF90gxekH3IULmyr7_HRA0TdNz_9Xjxuro,24013
80
82
  bom/static/bom/doc/sample_part_classes.csv,sha256=nAWhBV9KtHSejLUKD7OKKtbkfUYCeLgCwze9BPstiFo,1694
@@ -122,6 +124,7 @@ bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.eps,sha256=xZ8g
122
124
  bom/static/bom/img/google/web/vector/btn_google_light_normal_ios.svg,sha256=Rk6WGzHe0lGJGEyWiN6lTusosfK8ubtqSdf0ZzuWLBE,4358
123
125
  bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.eps,sha256=WUvJDjV62btGu5W1SkKE6juGMOHnA62JJ_0p8C_eOnw,26107
124
126
  bom/static/bom/img/google/web/vector/btn_google_light_pressed_ios.svg,sha256=CR9QqAnt_NVL2YweEZe4fN6kQ6QQ31nZHxSmwqVs1t0,4360
127
+ bom/static/bom/js/formset-handler.js,sha256=eEFRa1Sln5Uj7P660_nLDcIHr-mri3SewB89Pr0CwPA,2389
125
128
  bom/static/bom/js/jquery-3.4.1.min.js,sha256=CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo,88145
126
129
  bom/static/bom/js/jquery.ba-floatingscrollbar.min.js,sha256=NptA5rS2zjhkcu26WQJtMr34psHYzUYRsLrSziTusgg,1271
127
130
  bom/static/bom/js/jquery.treetable.js,sha256=JEXnh_LuKYxk8CUIT1anQ4es4nfOqC0bds_NjsbMBUI,16611
@@ -143,16 +146,17 @@ bom/templates/bom/create-part.html,sha256=UjHx6rkPJl-BRt9gKs5zB3s5Y4vrImDTuD25lv
143
146
  bom/templates/bom/dashboard-menu.html,sha256=4MnFSw0x4w7R0CsSwEDFP3FuZVBFxVChIHr58l3rpSk,799
144
147
  bom/templates/bom/dashboard.html,sha256=m-jGdf4aeIcQqbdqSTwzRCIoLldiIIUQrfM-zRJgyvU,16314
145
148
  bom/templates/bom/edit-manufacturer-part.html,sha256=4WagGptcVtYCxW1zWvHGeaZSc-0qqEKDRHFyKarqIjU,3518
146
- bom/templates/bom/edit-part-class.html,sha256=WOWaxaaGZTIumHI_lTeBgPSSmBHnTlASzq03nvIulQ4,1430
149
+ bom/templates/bom/edit-part-class.html,sha256=1DV8_bQiYuxYUuqMT2SsKDI4UWnnZnEx5EU7Nw7KJlw,5757
147
150
  bom/templates/bom/edit-part.html,sha256=bGfBI3X8eb7iLy_nYDPQRP2CKyp4tNO2-vjakx-_YiM,3051
148
- bom/templates/bom/edit-user-meta.html,sha256=8M4TOPigOBX71wCRVZ4Z_qn2XVNO4aQQujSOHwJoER8,1681
151
+ bom/templates/bom/edit-quantity-of-measure.html,sha256=79ftu8klYrryj3JrSPEQ3Up5NLh2Qbf4qHp_8qZm5aM,5975
152
+ bom/templates/bom/edit-user-meta.html,sha256=Z4iMA0OErrvVNHbFjLuq7tpO5f6C9ovDpI8-zhC6oX0,3489
149
153
  bom/templates/bom/help.html,sha256=hi16fwtVqeWP3Vb3vOpLSKx0ioB1MODJ8lkh6bczfiM,83247
150
154
  bom/templates/bom/manufacturer-info.html,sha256=jR98qXONqquEeda92djQgmFfmJiC8jbsGgyXuzJY100,3742
151
155
  bom/templates/bom/manufacturers.html,sha256=3ZF8Up-IiAvR6CCmrj_92qPPh7vKrVQaEN05KKX2yrA,5550
152
156
  bom/templates/bom/nothing-to-see.html,sha256=cRspNAHlSfv7KjQwp34gANhVqQbXzfFpqtRSm6NoV9s,657
153
- bom/templates/bom/organization-create.html,sha256=4o3C58CQDF3Jz1cZ1UPoLfX0qNvwMKK3UnFeXGMmPww,7899
154
- bom/templates/bom/part-info.html,sha256=lilYygmR8cQhQ5lJeYK6dj-OdIm72s1SerELHqyrPSs,25406
155
- bom/templates/bom/part-revision-display.html,sha256=t_wwzf910fhBc2vFjqoISnhX4OEr7pkfh8R-RGq_6ac,5509
157
+ bom/templates/bom/organization-create.html,sha256=yLrEJH8BlD-W1USk8pYAzTvr-Qw9ZM5rBGYO3n6lCu0,7982
158
+ bom/templates/bom/part-info.html,sha256=236TZ6yTvR5LCravYiuKgzL_CB6nXrd0_C299gvQF8U,26051
159
+ bom/templates/bom/part-revision-display.html,sha256=f5o-9cjGBz7KPp8ubSiUiFNa2tWzHqSKSe3rC7smR6A,1414
156
160
  bom/templates/bom/part-revision-edit.html,sha256=gOWiRd8Vq0z912_fI9UtBC0yYkcF_lruMQfAWN4kqw0,1624
157
161
  bom/templates/bom/part-revision-manage-bom.html,sha256=Nd1CIANnCwYU5KzijlIDfKTXjXqrDOeuwmGOk-CYyrY,5073
158
162
  bom/templates/bom/part-revision-new.html,sha256=BMgKBNmFkrsimqSeviP1Rn3huOB_0kfK-77oIyqwItw,2691
@@ -160,7 +164,7 @@ bom/templates/bom/part-revision-release.html,sha256=voG7wmYc1Cm3e_H1IasvQcPuyqnn
160
164
  bom/templates/bom/search-help.html,sha256=Wh_tXBJtz0bznk0F1C7OSdRhMe2qpOs9NMCBb2i0CFI,4398
161
165
  bom/templates/bom/seller-info.html,sha256=MACsHMYQXMWfRslXuvh9hD2z28VXzVi0DSy4yg7WQMk,3595
162
166
  bom/templates/bom/sellers.html,sha256=6ut7LwRMGUKYB4BRjiSpDBP9BGgqT7nxpNQpUVWDvkw,5412
163
- bom/templates/bom/settings.html,sha256=et6ct7SxA8hJK8GyLcfEgmYb6esS2zvDoUJb432dfd0,28237
167
+ bom/templates/bom/settings.html,sha256=g7PhrtbQSf7VRK6pDTPUxUr-PjCLzrJyUErno2pDekY,36184
164
168
  bom/templates/bom/signup.html,sha256=tB_x7q3IufSNXsd9Dfh8fdWpkiWSGH2_Zgw749B1PaU,884
165
169
  bom/templates/bom/subscription_panel.html,sha256=Ute49APwiXONQW2z0AApJRaSwnwtsYt3_opn0bW5BX8,843
166
170
  bom/templates/bom/table_of_contents.html,sha256=7wXWOfmVkk5Itjax5x1PE-g5QjxqmYBr7RW8NgtGRng,1763
@@ -178,10 +182,10 @@ bom/third_party_apis/google_drive.py,sha256=8ECE9_8f1KAyNTtkalws-Gl51wxAaTLP40bD
178
182
  bom/third_party_apis/mouser.py,sha256=q2-p0k2n-LNel_QRlfak0kAXT-9hh59k_Pt51PTG09s,5576
179
183
  bom/third_party_apis/test_apis.py,sha256=2W0jtTisGTmktC7l556pn9-pZYseTQmmQfo6_4uP4Dc,679
180
184
  bom/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
181
- bom/views/json_views.py,sha256=CaDMxHGnp2182ZV9QZfNkgM7tc_rNmokkelav9rF2dE,2462
182
- bom/views/views.py,sha256=uTxpDDB-Bt4y125JnqnxCe--kluIu1fk0Pfg4Abg8yc,72229
183
- django_bom-1.243.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
184
- django_bom-1.243.dist-info/METADATA,sha256=aeEnhLXxjc9KEpcghiDDpelDtmYHpwRXnZ9_ww19-ow,7558
185
- django_bom-1.243.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
186
- django_bom-1.243.dist-info/top_level.txt,sha256=6zytg4lnnobI96dO-ZEadPOCslrrFmf4t2Pnv-y8x0Y,4
187
- django_bom-1.243.dist-info/RECORD,,
185
+ bom/views/json_views.py,sha256=LK3-njLZrILLqZxCuE-_sUEC2z2GBxQFRysX67-h14c,2334
186
+ bom/views/views.py,sha256=rYEtIPEJMu1rFSNUARcBlJUe8NgBKoHxl38VWYb8htY,80320
187
+ django_bom-1.257.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
188
+ django_bom-1.257.dist-info/METADATA,sha256=50SYINtL9U2pkOe6ypyXI4JCdb5i3GYG2mnOV7bzQ00,7558
189
+ django_bom-1.257.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
190
+ django_bom-1.257.dist-info/top_level.txt,sha256=6zytg4lnnobI96dO-ZEadPOCslrrFmf4t2Pnv-y8x0Y,4
191
+ django_bom-1.257.dist-info/RECORD,,