From af9b88de1105981beb81dac55688212e20cccdab Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 24 Nov 2020 09:33:26 +1100 Subject: [PATCH 1/5] Fix for BomItem clean function Handle the case where the sub_part does not exist --- InvenTree/part/models.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 4730ba1e9c..0852887ff7 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1086,7 +1086,7 @@ class Part(MPTTModel): for bom_item in self.bom_items.all().select_related('sub_part'): sub_part = bom_item.sub_part - + if sub_part not in parts: parts.add(sub_part) @@ -1885,25 +1885,28 @@ class BomItem(models.Model): - If the "sub_part" is trackable, then the "part" must be trackable too! """ - # If the sub_part is 'trackable' then the 'quantity' field must be an integer try: - if self.sub_part.trackable: - if not self.quantity == int(self.quantity): - raise ValidationError({ - "quantity": _("Quantity must be integer value for trackable parts") - }) - - # Force the upstream part to be trackable if the sub_part is trackable - if not self.part.trackable: - self.part.trackable = True - self.part.clean() - self.part.save() + # Check for circular BOM references + if self.sub_part: + self.sub_part.checkAddToBOM(self.part) + + # If the sub_part is 'trackable' then the 'quantity' field must be an integer + if self.sub_part.trackable: + if not self.quantity == int(self.quantity): + raise ValidationError({ + "quantity": _("Quantity must be integer value for trackable parts") + }) + # Force the upstream part to be trackable if the sub_part is trackable + if not self.part.trackable: + self.part.trackable = True + self.part.clean() + self.part.save() + else: + raise ValidationError({'sub_part': _('Sub part must be specified')}) except Part.DoesNotExist: - pass + raise ValidationError({'sub_part': _('Sub part must be specified')}) - # Check for circular BOM references - self.sub_part.checkAddToBOM(self.part) class Meta: verbose_name = _("BOM Item") From 3391db506a9970763034879abe72ebe3f9cc78fa Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 24 Nov 2020 09:43:32 +1100 Subject: [PATCH 2/5] Cleanup queryset for BomItemCreate view --- InvenTree/part/views.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index e2f03b89b8..5bcacb7913 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2428,30 +2428,35 @@ class BomItemCreate(AjaxCreateView): part_id = form['part'].value() + # Construct a queryset for the part field + part_query = Part.objects.filter(active=True) + + # Construct a queryset for the sub_part field + sub_part_query = Part.objects.filter( + component=True, + active=True + ) + try: part = Part.objects.get(id=part_id) - - # Only allow active parts to be selected - query = form.fields['part'].queryset.filter(active=True) - form.fields['part'].queryset = query - - # Don't allow selection of sub_part objects which are already added to the Bom! - query = form.fields['sub_part'].queryset - # Don't allow a part to be added to its own BOM - query = query.exclude(id=part.id) - query = query.filter(active=True) + # Hide the 'part' field + form.fields['part'].widget = HiddenInput() + + # Exclude the part from its own BOM + sub_part_query = sub_part_query.exclude(id=part.id) # Eliminate any options that are already in the BOM! - query = query.exclude(id__in=[item.id for item in part.getRequiredParts()]) - - form.fields['sub_part'].queryset = query + sub_part_query = sub_part_query.exclude(id__in=[item.id for item in part.getRequiredParts()]) - form.fields['part'].widget = HiddenInput() except (ValueError, Part.DoesNotExist): pass + # Set the querysets for the fields + form.fields['part'].queryset = part_query + form.fields['sub_part'].queryset = sub_part_query + return form def get_initial(self): From 371ec582e14ce10eab1817839049b944bfba265c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 24 Nov 2020 09:43:49 +1100 Subject: [PATCH 3/5] Cleanup queryset for BomItemEdit view --- InvenTree/part/views.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 5bcacb7913..f664127d82 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2497,6 +2497,8 @@ class BomItemEdit(AjaxUpdateView): - Remove any part items that are already in the BOM """ + item = self.get_object() + form = super().get_form() part_id = form['part'].value() @@ -2504,9 +2506,14 @@ class BomItemEdit(AjaxUpdateView): try: part = Part.objects.get(pk=part_id) - query = form.fields['sub_part'].queryset + # Construct a queryset + query = Part.objects.filter(component=True) - # Reduce the available selection options + # Limit to "active" items, *unless* the currently selected item is not active + if item.sub_part.active: + query = query.filter(active=True) + + # Prevent the parent part from being selected query = query.exclude(pk=part_id) # Eliminate any options that are already in the BOM, From ce825799303da20664c38760a7611516e0f7e8d0 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 24 Nov 2020 10:18:07 +1100 Subject: [PATCH 4/5] Cleanup getRequiredParts function --- InvenTree/part/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 0852887ff7..7a699c20b7 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1083,10 +1083,12 @@ class Part(MPTTModel): parts: Set of parts already found (to prevent recursion issues) """ - for bom_item in self.bom_items.all().select_related('sub_part'): + items = self.bom_items.all().prefetch_related('sub_part') + + for bom_item in items: sub_part = bom_item.sub_part - + if sub_part not in parts: parts.add(sub_part) From b5d75d6e6a34f75a8df89ce48d563f4acf87632e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 24 Nov 2020 11:56:51 +1100 Subject: [PATCH 5/5] PEP fixes --- InvenTree/part/models.py | 3 +-- InvenTree/part/views.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 7a699c20b7..ea26e5e5c7 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1907,8 +1907,7 @@ class BomItem(models.Model): else: raise ValidationError({'sub_part': _('Sub part must be specified')}) except Part.DoesNotExist: - raise ValidationError({'sub_part': _('Sub part must be specified')}) - + raise ValidationError({'sub_part': _('Sub part must be specified')}) class Meta: verbose_name = _("BOM Item") diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index f664127d82..ac4925685f 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2449,7 +2449,6 @@ class BomItemCreate(AjaxCreateView): # Eliminate any options that are already in the BOM! sub_part_query = sub_part_query.exclude(id__in=[item.id for item in part.getRequiredParts()]) - except (ValueError, Part.DoesNotExist): pass