diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 4730ba1e9c..ea26e5e5c7 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1083,7 +1083,9 @@ 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 @@ -1885,25 +1887,27 @@ 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 - - # Check for circular BOM references - self.sub_part.checkAddToBOM(self.part) + 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 e2f03b89b8..ac4925685f 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2428,30 +2428,34 @@ 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 - - form.fields['part'].widget = HiddenInput() + sub_part_query = sub_part_query.exclude(id__in=[item.id for item in part.getRequiredParts()]) 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): @@ -2492,6 +2496,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() @@ -2499,9 +2505,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,