From 56fa6c512be0e505a1d881a3d1b125950ab46bbd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 18 Jul 2021 22:21:11 +1000 Subject: [PATCH] Refactor SupplierPartCreate form --- InvenTree/company/models.py | 51 ------- InvenTree/company/serializers.py | 8 +- .../company/templates/company/detail.html | 30 ++-- .../templates/company/manufacturer_part.html | 24 ++- .../company/templates/company/navbar.html | 2 +- InvenTree/company/test_views.py | 135 ----------------- InvenTree/company/urls.py | 2 - InvenTree/company/views.py | 138 ------------------ InvenTree/part/templates/part/detail.html | 29 ++-- InvenTree/templates/js/company.js | 57 ++++++++ InvenTree/templates/js/forms.js | 5 +- InvenTree/templates/js/model_renderers.js | 30 +++- InvenTree/templates/js/stock.js | 21 --- InvenTree/templates/js/tables.js | 6 + 14 files changed, 129 insertions(+), 409 deletions(-) diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 778e00cde1..d8ea32ee15 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -475,57 +475,6 @@ class SupplierPart(models.Model): def get_absolute_url(self): return reverse('supplier-part-detail', kwargs={'pk': self.id}) - def save(self, *args, **kwargs): - """ Overriding save method to process the linked ManufacturerPart - """ - - if 'manufacturer' in kwargs: - manufacturer_id = kwargs.pop('manufacturer') - - try: - manufacturer = Company.objects.get(pk=int(manufacturer_id)) - except (ValueError, Company.DoesNotExist): - manufacturer = None - else: - manufacturer = None - if 'MPN' in kwargs: - MPN = kwargs.pop('MPN') - else: - MPN = None - - if manufacturer or MPN: - if not self.manufacturer_part: - # Create ManufacturerPart - manufacturer_part = ManufacturerPart.create(part=self.part, - manufacturer=manufacturer, - mpn=MPN, - description=self.description) - self.manufacturer_part = manufacturer_part - else: - # Update ManufacturerPart (if ID exists) - try: - manufacturer_part_id = self.manufacturer_part.id - except AttributeError: - manufacturer_part_id = None - - if manufacturer_part_id: - try: - (manufacturer_part, created) = ManufacturerPart.objects.update_or_create(part=self.part, - manufacturer=manufacturer, - MPN=MPN) - except IntegrityError: - manufacturer_part = None - raise ValidationError(f'ManufacturerPart linked to {self.part} from manufacturer {manufacturer.name}' - f'with part number {MPN} already exists!') - - if manufacturer_part: - self.manufacturer_part = manufacturer_part - - self.clean() - self.validate_unique() - - super().save(*args, **kwargs) - class Meta: unique_together = ('part', 'supplier', 'SKU') diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 6e5ef08d6e..721fc54c6e 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -96,7 +96,9 @@ class CompanySerializer(InvenTreeModelSerializer): class ManufacturerPartSerializer(InvenTreeModelSerializer): - """ Serializer for ManufacturerPart object """ + """ + Serializer for ManufacturerPart object + """ part_detail = PartBriefSerializer(source='part', many=False, read_only=True) @@ -106,8 +108,8 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer): def __init__(self, *args, **kwargs): - part_detail = kwargs.pop('part_detail', False) - manufacturer_detail = kwargs.pop('manufacturer_detail', False) + part_detail = kwargs.pop('part_detail', True) + manufacturer_detail = kwargs.pop('manufacturer_detail', True) prettify = kwargs.pop('pretty', False) super(ManufacturerPartSerializer, self).__init__(*args, **kwargs) diff --git a/InvenTree/company/templates/company/detail.html b/InvenTree/company/templates/company/detail.html index 105e0f82ae..9c9e836c70 100644 --- a/InvenTree/company/templates/company/detail.html +++ b/InvenTree/company/templates/company/detail.html @@ -350,28 +350,16 @@ {% if company.is_supplier %} + function reloadSupplierPartTable() { + $('#supplier-part-table').bootstrapTable('refresh'); + } + $("#supplier-part-create").click(function () { - launchModalForm( - "{% url 'supplier-part-create' %}", - { - data: { - supplier: {{ company.id }}, - }, - reload: true, - secondary: [ - { - field: 'part', - label: '{% trans "New Part" %}', - title: '{% trans "Create new Part" %}', - url: "{% url 'part-create' %}" - }, - { - field: 'supplier', - label: "{% trans 'New Supplier' %}", - title: "{% trans 'Create new Supplier' %}", - }, - ] - }); + + createSupplierPart({ + supplier: {{ company.pk }}, + onSuccess: reloadSupplierPartTable, + }); }); loadSupplierPartTable( diff --git a/InvenTree/company/templates/company/manufacturer_part.html b/InvenTree/company/templates/company/manufacturer_part.html index da5ea36173..12b869ca72 100644 --- a/InvenTree/company/templates/company/manufacturer_part.html +++ b/InvenTree/company/templates/company/manufacturer_part.html @@ -178,22 +178,16 @@ $('#parameter-create').click(function() { }); }); +function reloadSupplierPartTable() { + $('#supplier-table').bootstrapTable('refresh'); +} + $('#supplier-create').click(function () { - launchModalForm( - "{% url 'supplier-part-create' %}", - { - reload: true, - data: { - manufacturer_part: {{ part.id }} - }, - secondary: [ - { - field: 'supplier', - label: '{% trans "New Supplier" %}', - title: '{% trans "Create new supplier" %}', - }, - ] - }); + createSupplierPart({ + manufacturer_part: {{ part.pk }}, + part: {{ part.part.pk }}, + onSuccess: reloadSupplierPartTable, + }); }); $("#supplier-part-delete").click(function() { diff --git a/InvenTree/company/templates/company/navbar.html b/InvenTree/company/templates/company/navbar.html index 025b1c6b4a..b652d6b603 100644 --- a/InvenTree/company/templates/company/navbar.html +++ b/InvenTree/company/templates/company/navbar.html @@ -18,7 +18,7 @@ {% endif %} - {% if company.is_supplier or company.is_manufacturer %} + {% if company.is_supplier %}
  • diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index 6fc4281f2b..bb796a0763 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -75,108 +75,6 @@ class CompanyViewTestBase(TestCase): return json_data, form_errors -class SupplierPartViewTests(CompanyViewTestBase): - """ - Tests for the SupplierPart views. - """ - - def test_supplier_part_create(self): - """ - Test the SupplierPartCreate view. - - This view allows some additional functionality, - specifically it allows the user to create a single-quantity price break - automatically, when saving the new SupplierPart model. - """ - - url = reverse('supplier-part-create') - - # First check that we can GET the form - response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # How many supplier parts are already in the database? - n = SupplierPart.objects.all().count() - - data = { - 'part': 1, - 'supplier': 1, - } - - # SKU is required! (form should fail) - (response, errors) = self.post(url, data, valid=False) - - self.assertIsNotNone(errors.get('SKU', None)) - - data['SKU'] = 'TEST-ME-123' - - (response, errors) = self.post(url, data, valid=True) - - # Check that the SupplierPart was created! - self.assertEqual(n + 1, SupplierPart.objects.all().count()) - - # Check that it was created *without* a price-break - supplier_part = SupplierPart.objects.get(pk=response['pk']) - - self.assertEqual(supplier_part.price_breaks.count(), 0) - - # Duplicate SKU is prohibited - (response, errors) = self.post(url, data, valid=False) - - self.assertIsNotNone(errors.get('__all__', None)) - - # Add with a different SKU, *and* a single-quantity price - data['SKU'] = 'TEST-ME-1234' - data['single_pricing_0'] = '123.4' - data['single_pricing_1'] = 'CAD' - - (response, errors) = self.post(url, data, valid=True) - - pk = response.get('pk') - - # Check that *another* SupplierPart was created - self.assertEqual(n + 2, SupplierPart.objects.all().count()) - - supplier_part = SupplierPart.objects.get(pk=pk) - - # Check that a price-break has been created! - self.assertEqual(supplier_part.price_breaks.count(), 1) - - price_break = supplier_part.price_breaks.first() - - self.assertEqual(price_break.quantity, 1) - - def test_supplier_part_delete(self): - """ - Test the SupplierPartDelete view - """ - - url = reverse('supplier-part-delete') - - # Get form using 'part' argument - response = self.client.get(url, {'part': '1'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # Get form using 'parts' argument - response = self.client.get(url + '?parts[]=1&parts[]=2', HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # POST to delete two parts - n = SupplierPart.objects.count() - response = self.client.post( - url, - { - 'supplier-part-2': 'supplier-part-2', - 'supplier-part-3': 'supplier-part-3', - 'confirm_delete': True - }, - HTTP_X_REQUESTED_WITH='XMLHttpRequest') - - self.assertEqual(response.status_code, 200) - - self.assertEqual(n - 2, SupplierPart.objects.count()) - - class CompanyViewTest(CompanyViewTestBase): """ Tests for various 'Company' views @@ -187,36 +85,3 @@ class CompanyViewTest(CompanyViewTestBase): response = self.client.get(reverse('company-index')) self.assertEqual(response.status_code, 200) - - -class ManufacturerPartViewTests(CompanyViewTestBase): - """ - Tests for the ManufacturerPart views. - """ - - def test_supplier_part_create(self): - """ - Test that the SupplierPartCreate view creates Manufacturer Part. - """ - - url = reverse('supplier-part-create') - - # First check that we can GET the form - response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - - # How many manufacturer parts are already in the database? - n = ManufacturerPart.objects.all().count() - - data = { - 'part': 1, - 'supplier': 1, - 'SKU': 'SKU_TEST', - 'manufacturer': 6, - 'MPN': 'MPN_TEST', - } - - (response, errors) = self.post(url, data, valid=True) - - # Check that the ManufacturerPart was created! - self.assertEqual(n + 1, ManufacturerPart.objects.all().count()) diff --git a/InvenTree/company/urls.py b/InvenTree/company/urls.py index 7d2b6fb609..b983a6483b 100644 --- a/InvenTree/company/urls.py +++ b/InvenTree/company/urls.py @@ -42,8 +42,6 @@ supplier_part_detail_urls = [ ] supplier_part_urls = [ - url(r'^new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'), - url(r'delete/', views.SupplierPartDelete.as_view(), name='supplier-part-delete'), url(r'^(?P\d+)/', include(supplier_part_detail_urls)), diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index ab6344e810..cf683ffb90 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -285,144 +285,6 @@ class SupplierPartEdit(AjaxUpdateView): return initials -class SupplierPartCreate(AjaxCreateView): - """ Create view for making new SupplierPart """ - - model = SupplierPart - form_class = EditSupplierPartForm - ajax_template_name = 'company/supplier_part_create.html' - ajax_form_title = _('Create new Supplier Part') - context_object_name = 'part' - - def validate(self, part, form): - - single_pricing = form.cleaned_data.get('single_pricing', None) - - if single_pricing: - # TODO - What validation steps can be performed on the single_pricing field? - pass - - def get_context_data(self): - """ - Supply context data to the form - """ - - ctx = super().get_context_data() - - # Add 'part' object - form = self.get_form() - - part = form['part'].value() - - try: - part = Part.objects.get(pk=part) - except (ValueError, Part.DoesNotExist): - part = None - - ctx['part'] = part - - return ctx - - def save(self, form): - """ - If single_pricing is defined, add a price break for quantity=1 - """ - - # Save the supplier part object - supplier_part = super().save(form) - - # Process manufacturer data - manufacturer = form.cleaned_data.get('manufacturer', None) - MPN = form.cleaned_data.get('MPN', None) - kwargs = {'manufacturer': manufacturer, - 'MPN': MPN, - } - supplier_part.save(**kwargs) - - single_pricing = form.cleaned_data.get('single_pricing', None) - - if single_pricing: - - supplier_part.add_price_break(1, single_pricing) - - return supplier_part - - def get_form(self): - """ Create Form instance to create a new SupplierPart object. - Hide some fields if they are not appropriate in context - """ - form = super(AjaxCreateView, self).get_form() - - if form.initial.get('part', None): - # Hide the part field - form.fields['part'].widget = HiddenInput() - - if form.initial.get('manufacturer', None): - # Hide the manufacturer field - form.fields['manufacturer'].widget = HiddenInput() - # Hide the MPN field - form.fields['MPN'].widget = HiddenInput() - - return form - - def get_initial(self): - """ Provide initial data for new SupplierPart: - - - If 'supplier_id' provided, pre-fill supplier field - - If 'part_id' provided, pre-fill part field - """ - initials = super(SupplierPartCreate, self).get_initial().copy() - - manufacturer_id = self.get_param('manufacturer') - supplier_id = self.get_param('supplier') - part_id = self.get_param('part') - manufacturer_part_id = self.get_param('manufacturer_part') - - supplier = None - - if supplier_id: - try: - supplier = Company.objects.get(pk=supplier_id) - initials['supplier'] = supplier - except (ValueError, Company.DoesNotExist): - supplier = None - - if manufacturer_id: - try: - initials['manufacturer'] = Company.objects.get(pk=manufacturer_id) - except (ValueError, Company.DoesNotExist): - pass - - if manufacturer_part_id: - try: - # Get ManufacturerPart instance information - manufacturer_part_obj = ManufacturerPart.objects.get(pk=manufacturer_part_id) - initials['part'] = Part.objects.get(pk=manufacturer_part_obj.part.id) - initials['manufacturer'] = manufacturer_part_obj.manufacturer.id - initials['MPN'] = manufacturer_part_obj.MPN - except (ValueError, ManufacturerPart.DoesNotExist, Part.DoesNotExist, Company.DoesNotExist): - pass - - if part_id: - try: - initials['part'] = Part.objects.get(pk=part_id) - except (ValueError, Part.DoesNotExist): - pass - - # Initial value for single pricing - if supplier: - currency_code = supplier.currency_code - else: - currency_code = common.settings.currency_code_default() - - currency = CURRENCIES.get(currency_code, None) - - if currency_code: - initials['single_pricing'] = ('', currency) - - return initials - - class SupplierPartDelete(AjaxDeleteView): """ Delete view for removing a SupplierPart. diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 0ba539baea..77de5deceb 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -801,27 +801,16 @@ ) }); + function reloadSupplierPartTable() { + $('#supplier-part-table').bootstrapTable('refresh'); + } + $('#supplier-create').click(function () { - launchModalForm( - "{% url 'supplier-part-create' %}", - { - reload: true, - data: { - part: {{ part.id }} - }, - secondary: [ - { - field: 'supplier', - label: '{% trans "New Supplier" %}', - title: '{% trans "Create new supplier" %}', - }, - { - field: 'manufacturer', - label: '{% trans "New Manufacturer" %}', - title: '{% trans "Create new manufacturer" %}', - } - ] - }); + + createSupplierPart({ + part: {{ part.pk }}, + onSuccess: reloadSupplierPartTable, + }); }); $("#supplier-part-delete").click(function() { diff --git a/InvenTree/templates/js/company.js b/InvenTree/templates/js/company.js index 7daa99e1c6..7b6f145bba 100644 --- a/InvenTree/templates/js/company.js +++ b/InvenTree/templates/js/company.js @@ -1,6 +1,63 @@ {% load i18n %} +function supplierPartFields() { + + return { + part: {}, + supplier: {}, + SKU: { + icon: 'fa-hashtag', + }, + manufacturer_part: { + filters: { + part_detail: true, + manufacturer_detail: true, + } + }, + description: {}, + link: { + icon: 'fa-link', + }, + note: { + icon: 'fa-pencil-alt', + }, + packaging: { + icon: 'fa-box', + } + }; +} + +/* + * Launch a form to create a new ManufacturerPart + */ +function createSupplierPart(options={}) { + + var fields = supplierPartFields(); + + if (options.part) { + fields.manufacturer_part.filters.part = options.part; + fields.part.hidden = true; + fields.part.value = options.part; + } + + if (options.supplier) { + fields.supplier.value = options.supplier; + } + + if (options.manufacturer_part) { + fields.manufacturer_part.value = options.manufacturer_part; + } + + constructForm('{% url "api-supplier-part-list" %}', { + fields: fields, + method: 'POST', + title: '{% trans "Add Supplier Part" %}', + onSuccess: options.onSuccess, + }); +} + + // Returns a default form-set for creating / editing a Company object function companyFormFields(options={}) { diff --git a/InvenTree/templates/js/forms.js b/InvenTree/templates/js/forms.js index 6dd7dbd968..568ca5ac58 100644 --- a/InvenTree/templates/js/forms.js +++ b/InvenTree/templates/js/forms.js @@ -1113,7 +1113,7 @@ function initializeRelatedField(name, field, options) { var pk = field.value; var url = `${field.api_url}/${pk}/`.replace('//', '/'); - inventreeGet(url, {}, { + inventreeGet(url, field.filters || {}, { success: function(data) { setRelatedFieldData(name, data, options); } @@ -1211,6 +1211,9 @@ function renderModelData(name, model, data, parameters, options) { case 'partparametertemplate': renderer = renderPartParameterTemplate; break; + case 'manufacturerpart': + renderer = renderManufacturerPart; + break; case 'supplierpart': renderer = renderSupplierPart; break; diff --git a/InvenTree/templates/js/model_renderers.js b/InvenTree/templates/js/model_renderers.js index ddb4897d8d..12e00cab2c 100644 --- a/InvenTree/templates/js/model_renderers.js +++ b/InvenTree/templates/js/model_renderers.js @@ -174,7 +174,35 @@ function renderPartParameterTemplate(name, data, parameters, options) { } -// Rendered for "SupplierPart" model +// Renderer for "ManufacturerPart" model +function renderManufacturerPart(name, data, parameters, options) { + + var manufacturer_image = null; + var part_image = null; + + if (data.manufacturer_detail) { + manufacturer_image = data.manufacturer_detail.image; + } + + if (data.part_detail) { + part_image = data.part_detail.thumbnail || data.part_detail.image; + } + + var html = ''; + + html += select2Thumbnail(manufacturer_image); + html += select2Thumbnail(part_image); + + html += ` ${data.manufacturer_detail.name} - ${data.MPN}`; + html += ` - ${data.part_detail.full_name}`; + + html += `{% trans "Manufacturer Part ID" %}: ${data.pk}`; + + return html; +} + + +// Renderer for "SupplierPart" model function renderSupplierPart(name, data, parameters, options) { var supplier_image = null; diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index 754d3d5b59..1d38b631a5 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -1616,27 +1616,6 @@ function createNewStockItem(options) { }, ]; - options.secondary = [ - { - field: 'part', - label: '{% trans "New Part" %}', - title: '{% trans "Create New Part" %}', - url: "{% url 'part-create' %}", - }, - { - field: 'supplier_part', - label: '{% trans "New Supplier Part" %}', - title: '{% trans "Create new Supplier Part" %}', - url: "{% url 'supplier-part-create' %}" - }, - { - field: 'location', - label: '{% trans "New Location" %}', - title: '{% trans "Create New Location" %}', - url: "{% url 'stock-location-create' %}", - }, - ]; - launchModalForm("{% url 'stock-item-create' %}", options); } diff --git a/InvenTree/templates/js/tables.js b/InvenTree/templates/js/tables.js index 8fedeb8f9e..afe1fefbc9 100644 --- a/InvenTree/templates/js/tables.js +++ b/InvenTree/templates/js/tables.js @@ -1,5 +1,11 @@ {% load i18n %} + +function reloadtable(table) { + $(table).bootstrapTable('refresh'); +} + + function editButton(url, text='Edit') { return ""; }