From 75a1be0284244e79ba08d986c2957d9e852d4955 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 4 Aug 2021 17:25:51 +1000 Subject: [PATCH 1/2] Use API forms for creating and editing BomItem objects --- InvenTree/part/forms.py | 28 ----- InvenTree/part/templates/part/detail.html | 30 ++--- InvenTree/part/urls.py | 10 -- InvenTree/part/views.py | 128 ---------------------- InvenTree/templates/js/translated/bom.js | 35 ++++-- 5 files changed, 43 insertions(+), 188 deletions(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 9523550198..1fc2848440 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -18,7 +18,6 @@ import common.models from common.forms import MatchItemForm from .models import Part, PartCategory, PartRelated -from .models import BomItem from .models import PartParameterTemplate, PartParameter from .models import PartCategoryParameterTemplate from .models import PartSellPriceBreak, PartInternalPriceBreak @@ -317,33 +316,6 @@ class EditCategoryParameterTemplateForm(HelperForm): ] -class EditBomItemForm(HelperForm): - """ Form for editing a BomItem object """ - - quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity')) - - sub_part = PartModelChoiceField(queryset=Part.objects.all(), label=_('Sub part')) - - class Meta: - model = BomItem - fields = [ - 'part', - 'sub_part', - 'quantity', - 'reference', - 'overage', - 'note', - 'allow_variants', - 'inherited', - 'optional', - ] - - # Prevent editing of the part associated with this BomItem - widgets = { - 'part': forms.HiddenInput() - } - - class PartPriceForm(forms.Form): """ Simple form for viewing part pricing information """ diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 59aec17944..267b880d49 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -440,22 +440,22 @@ }); $("#bom-item-new").click(function () { - launchModalForm( - "{% url 'bom-item-create' %}?parent={{ part.id }}", - { - success: function() { - $("#bom-table").bootstrapTable('refresh'); - }, - secondary: [ - { - field: 'sub_part', - label: '{% trans "New Part" %}', - title: '{% trans "Create New Part" %}', - url: "{% url 'part-create' %}", - }, - ] + + var fields = bomItemFields(); + + fields.part.value = {{ part.pk }}; + fields.sub_part.filters = { + active: true, + }; + + constructForm('{% url "api-bom-list" %}', { + fields: fields, + method: 'POST', + title: '{% trans "Create BOM Item" %}', + onSuccess: function() { + $('#bom-table').bootstrapTable('refresh'); } - ); + }); }); {% else %} diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 62061f8279..52e9b929c1 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -78,10 +78,6 @@ category_urls = [ ])) ] -part_bom_urls = [ - url(r'^edit/?', views.BomItemEdit.as_view(), name='bom-item-edit'), -] - # URL list for part web interface part_urls = [ @@ -92,9 +88,6 @@ part_urls = [ url(r'^import/', views.PartImport.as_view(), name='part-import'), url(r'^import-api/', views.PartImportAjax.as_view(), name='api-part-import'), - # Create a new BOM item - url(r'^bom/new/?', views.BomItemCreate.as_view(), name='bom-item-create'), - # Download a BOM upload template url(r'^bom_template/?', views.BomUploadTemplate.as_view(), name='bom-upload-template'), @@ -122,9 +115,6 @@ part_urls = [ # Change category for multiple parts url(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'), - # Bom Items - url(r'^bom/(?P\d+)/', include(part_bom_urls)), - # Individual part using IPN as slug url(r'^(?P[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index dd2868b72b..b35e752351 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -2078,134 +2078,6 @@ class CategoryParameterTemplateDelete(AjaxDeleteView): return self.object -class BomItemCreate(AjaxCreateView): - """ - Create view for making a new BomItem object - """ - - model = BomItem - form_class = part_forms.EditBomItemForm - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Create BOM Item') - - def get_form(self): - """ Override get_form() method to reduce Part selection options. - - - Do not allow part to be added to its own BOM - - Remove any Part items that are already in the BOM - """ - - form = super(AjaxCreateView, self).get_form() - - 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) - - # 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! - 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): - """ Provide initial data for the BomItem: - - - If 'parent' provided, set the parent part field - """ - - # Look for initial values - initials = super(BomItemCreate, self).get_initial().copy() - - # Parent part for this item? - parent_id = self.request.GET.get('parent', None) - - if parent_id: - try: - initials['part'] = Part.objects.get(pk=parent_id) - except Part.DoesNotExist: - pass - - return initials - - -class BomItemEdit(AjaxUpdateView): - """ Update view for editing BomItem """ - - model = BomItem - form_class = part_forms.EditBomItemForm - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Edit BOM item') - - def get_form(self): - """ Override get_form() method to filter part selection options - - - Do not allow part to be added to its own BOM - - Remove any part items that are already in the BOM - """ - - item = self.get_object() - - form = super().get_form() - - part_id = form['part'].value() - - try: - part = Part.objects.get(pk=part_id) - - # Construct a queryset - query = Part.objects.filter(component=True) - - # 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, - # *except* for the item which is already selected - try: - sub_part_id = int(form['sub_part'].value()) - except ValueError: - sub_part_id = -1 - - existing = [item.pk for item in part.getRequiredParts()] - - if sub_part_id in existing: - existing.remove(sub_part_id) - - query = query.exclude(id__in=existing) - - form.fields['sub_part'].queryset = query - - except (ValueError, Part.DoesNotExist): - pass - - return form - - class PartSalePriceBreakCreate(AjaxCreateView): """ View for creating a sale price break for a part diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 20829bad79..34a6206ac9 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -8,6 +8,26 @@ */ +function bomItemFields() { + + return { + part: { + hidden: true, + }, + sub_part: { + }, + quantity: {}, + reference: {}, + overage: {}, + note: {}, + allow_variants: {}, + inherited: {}, + optional: {}, + }; + +} + + function reloadBomTable(table, options) { table.bootstrapTable('refresh'); @@ -528,14 +548,15 @@ function loadBomTable(table, options) { var pk = $(this).attr('pk'); var url = `/part/bom/${pk}/edit/`; - launchModalForm( - url, - { - success: function() { - reloadBomTable(table); - } + var fields = bomItemFields(); + + constructForm(`/api/bom/${pk}/`, { + fields: fields, + title: '{% trans "Edit BOM Item" %}', + onSuccess: function() { + reloadBomTable(table); } - ); + }); }); table.on('click', '.bom-validate-button', function() { From 2e8a490ca9cbb18b67309da5c15b8d42103dbbb0 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 4 Aug 2021 17:41:47 +1000 Subject: [PATCH 2/2] Fixes for unit tests --- InvenTree/part/test_views.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index 139ec20479..206d4dd56a 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -259,22 +259,3 @@ class CategoryTest(PartViewTestCase): response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) - - -class BomItemTests(PartViewTestCase): - """ Tests for BomItem related views """ - - def test_create_valid_parent(self): - """ Create a BomItem for a valid part """ - response = self.client.get(reverse('bom-item-create'), {'parent': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - def test_create_no_parent(self): - """ Create a BomItem without a parent """ - response = self.client.get(reverse('bom-item-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - def test_create_invalid_parent(self): - """ Create a BomItem with an invalid parent """ - response = self.client.get(reverse('bom-item-create'), {'parent': 99999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200)