diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 5161bee6a1..a47ef3b667 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -20,9 +20,12 @@ v4 -> 2021-06-01 - BOM items can now accept "variant stock" to be assigned against them - Many slight API tweaks were needed to get this to work properly! +v5 -> 2021-06-21 + - Adds API interface for manufacturer part parameters + """ -INVENTREE_API_VERSION = 4 +INVENTREE_API_VERSION = 5 def inventreeInstanceName(): diff --git a/InvenTree/company/admin.py b/InvenTree/company/admin.py index b339a65c0e..2c3be87c84 100644 --- a/InvenTree/company/admin.py +++ b/InvenTree/company/admin.py @@ -11,6 +11,7 @@ import import_export.widgets as widgets from .models import Company from .models import SupplierPart from .models import SupplierPriceBreak +from .models import ManufacturerPart, ManufacturerPartParameter from part.models import Part @@ -71,6 +72,92 @@ class SupplierPartAdmin(ImportExportModelAdmin): ] +class ManufacturerPartResource(ModelResource): + """ + Class for managing ManufacturerPart data import/export + """ + + part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part)) + + part_name = Field(attribute='part__full_name', readonly=True) + + manufacturer = Field(attribute='manufacturer', widget=widgets.ForeignKeyWidget(Company)) + + manufacturer_name = Field(attribute='manufacturer__name', readonly=True) + + class Meta: + model = ManufacturerPart + skip_unchanged = True + report_skipped = True + clean_model_instances = True + + +class ManufacturerPartParameterInline(admin.TabularInline): + """ + Inline for editing ManufacturerPartParameter objects, + directly from the ManufacturerPart admin view. + """ + + model = ManufacturerPartParameter + + +class SupplierPartInline(admin.TabularInline): + """ + Inline for the SupplierPart model + """ + + model = SupplierPart + + +class ManufacturerPartAdmin(ImportExportModelAdmin): + """ + Admin class for ManufacturerPart model + """ + + resource_class = ManufacturerPartResource + + list_display = ('part', 'manufacturer', 'MPN') + + search_fields = [ + 'manufacturer__name', + 'part__name', + 'MPN', + ] + + inlines = [ + SupplierPartInline, + ManufacturerPartParameterInline, + ] + + +class ManufacturerPartParameterResource(ModelResource): + """ + Class for managing ManufacturerPartParameter data import/export + """ + + class Meta: + model = ManufacturerPartParameter + skip_unchanged = True + report_skipped = True + clean_model_instance = True + + +class ManufacturerPartParameterAdmin(ImportExportModelAdmin): + """ + Admin class for ManufacturerPartParameter model + """ + + resource_class = ManufacturerPartParameterResource + + list_display = ('manufacturer_part', 'name', 'value') + + search_fields = [ + 'manufacturer_part__manufacturer__name', + 'name', + 'value' + ] + + class SupplierPriceBreakResource(ModelResource): """ Class for managing SupplierPriceBreak data import/export """ @@ -103,3 +190,6 @@ class SupplierPriceBreakAdmin(ImportExportModelAdmin): admin.site.register(Company, CompanyAdmin) admin.site.register(SupplierPart, SupplierPartAdmin) admin.site.register(SupplierPriceBreak, SupplierPriceBreakAdmin) + +admin.site.register(ManufacturerPart, ManufacturerPartAdmin) +admin.site.register(ManufacturerPartParameter, ManufacturerPartParameterAdmin) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 83aef7531b..6cd1e83dfa 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -15,11 +15,11 @@ from django.db.models import Q from InvenTree.helpers import str2bool from .models import Company -from .models import ManufacturerPart +from .models import ManufacturerPart, ManufacturerPartParameter from .models import SupplierPart, SupplierPriceBreak from .serializers import CompanySerializer -from .serializers import ManufacturerPartSerializer +from .serializers import ManufacturerPartSerializer, ManufacturerPartParameterSerializer from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer @@ -175,6 +175,86 @@ class ManufacturerPartDetail(generics.RetrieveUpdateDestroyAPIView): serializer_class = ManufacturerPartSerializer +class ManufacturerPartParameterList(generics.ListCreateAPIView): + """ + API endpoint for list view of ManufacturerPartParamater model. + """ + + queryset = ManufacturerPartParameter.objects.all() + serializer_class = ManufacturerPartParameterSerializer + + def get_serializer(self, *args, **kwargs): + + # Do we wish to include any extra detail? + try: + params = self.request.query_params + + optional_fields = [ + 'manufacturer_part_detail', + ] + + for key in optional_fields: + kwargs[key] = str2bool(params.get(key, None)) + + except AttributeError: + pass + + kwargs['context'] = self.get_serializer_context() + + return self.serializer_class(*args, **kwargs) + + def filter_queryset(self, queryset): + """ + Custom filtering for the queryset + """ + + queryset = super().filter_queryset(queryset) + + params = self.request.query_params + + # Filter by manufacturer? + manufacturer = params.get('manufacturer', None) + + if manufacturer is not None: + queryset = queryset.filter(manufacturer_part__manufacturer=manufacturer) + + # Filter by part? + part = params.get('part', None) + + if part is not None: + queryset = queryset.filter(manufacturer_part__part=part) + + return queryset + + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + filters.OrderingFilter, + ] + + filter_fields = [ + 'name', + 'value', + 'units', + 'manufacturer_part', + ] + + search_fields = [ + 'name', + 'value', + 'units', + ] + + +class ManufacturerPartParameterDetail(generics.RetrieveUpdateDestroyAPIView): + """ + API endpoint for detail view of ManufacturerPartParameter model + """ + + queryset = ManufacturerPartParameter.objects.all() + serializer_class = ManufacturerPartParameterSerializer + + class SupplierPartList(generics.ListCreateAPIView): """ API endpoint for list view of SupplierPart object @@ -249,7 +329,7 @@ class SupplierPartList(generics.ListCreateAPIView): params = self.request.query_params kwargs['part_detail'] = str2bool(params.get('part_detail', None)) kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', None)) - kwargs['manufacturer_detail'] = str2bool(self.params.get('manufacturer_detail', None)) + kwargs['manufacturer_detail'] = str2bool(params.get('manufacturer_detail', None)) kwargs['pretty'] = str2bool(params.get('pretty', None)) except AttributeError: pass @@ -316,6 +396,13 @@ class SupplierPriceBreakList(generics.ListCreateAPIView): manufacturer_part_api_urls = [ + url(r'^parameter/', include([ + url(r'^(?P\d+)/', ManufacturerPartParameterDetail.as_view(), name='api-manufacturer-part-parameter-detail'), + + # Catch anything else + url(r'^.*$', ManufacturerPartParameterList.as_view(), name='api-manufacturer-part-parameter-list'), + ])), + url(r'^(?P\d+)/?', ManufacturerPartDetail.as_view(), name='api-manufacturer-part-detail'), # Catch anything else diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 6ffa94b746..80673b4fa4 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -16,7 +16,7 @@ from djmoney.forms.fields import MoneyField from common.settings import currency_code_default -from .models import Company +from .models import Company, ManufacturerPartParameter from .models import ManufacturerPart from .models import SupplierPart from .models import SupplierPriceBreak @@ -105,6 +105,21 @@ class EditManufacturerPartForm(HelperForm): ] +class EditManufacturerPartParameterForm(HelperForm): + """ + Form for creating / editing a ManufacturerPartParameter object + """ + + class Meta: + model = ManufacturerPartParameter + fields = [ + 'manufacturer_part', + 'name', + 'value', + 'units', + ] + + class EditSupplierPartForm(HelperForm): """ Form for editing a SupplierPart object """ diff --git a/InvenTree/company/migrations/0038_manufacturerpartparameter.py b/InvenTree/company/migrations/0038_manufacturerpartparameter.py new file mode 100644 index 0000000000..dccfa715e8 --- /dev/null +++ b/InvenTree/company/migrations/0038_manufacturerpartparameter.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.4 on 2021-06-20 07:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0037_supplierpart_update_3'), + ] + + operations = [ + migrations.CreateModel( + name='ManufacturerPartParameter', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Parameter name', max_length=500, verbose_name='Name')), + ('value', models.CharField(help_text='Parameter value', max_length=500, verbose_name='Value')), + ('units', models.CharField(blank=True, help_text='Parameter units', max_length=64, null=True, verbose_name='Units')), + ('manufacturer_part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parameters', to='company.manufacturerpart', verbose_name='Manufacturer Part')), + ], + options={ + 'unique_together': {('manufacturer_part', 'name')}, + }, + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index a5fac00e7d..093d545f78 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -371,6 +371,47 @@ class ManufacturerPart(models.Model): return s +class ManufacturerPartParameter(models.Model): + """ + A ManufacturerPartParameter represents a key:value parameter for a MnaufacturerPart. + + This is used to represent parmeters / properties for a particular manufacturer part. + + Each parameter is a simple string (text) value. + """ + + class Meta: + unique_together = ('manufacturer_part', 'name') + + manufacturer_part = models.ForeignKey( + ManufacturerPart, + on_delete=models.CASCADE, + related_name='parameters', + verbose_name=_('Manufacturer Part'), + ) + + name = models.CharField( + max_length=500, + blank=False, + verbose_name=_('Name'), + help_text=_('Parameter name') + ) + + value = models.CharField( + max_length=500, + blank=False, + verbose_name=_('Value'), + help_text=_('Parameter value') + ) + + units = models.CharField( + max_length=64, + blank=True, null=True, + verbose_name=_('Units'), + help_text=_('Parameter units') + ) + + class SupplierPart(models.Model): """ Represents a unique part as provided by a Supplier Each SupplierPart is identified by a SKU (Supplier Part Number) diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 4b1019656e..471d26bd3f 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -7,7 +7,7 @@ from rest_framework import serializers from sql_util.utils import SubqueryCount from .models import Company -from .models import ManufacturerPart +from .models import ManufacturerPart, ManufacturerPartParameter from .models import SupplierPart, SupplierPriceBreak from InvenTree.serializers import InvenTreeModelSerializer @@ -124,6 +124,35 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer): ] +class ManufacturerPartParameterSerializer(InvenTreeModelSerializer): + """ + Serializer for the ManufacturerPartParameter model + """ + + manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', many=False, read_only=True) + + def __init__(self, *args, **kwargs): + + man_detail = kwargs.pop('manufacturer_part_detail', False) + + super(ManufacturerPartParameterSerializer, self).__init__(*args, **kwargs) + + if not man_detail: + self.fields.pop('manufacturer_part_detail') + + class Meta: + model = ManufacturerPartParameter + + fields = [ + 'pk', + 'manufacturer_part', + 'manufacturer_part_detail', + 'name', + 'value', + 'units', + ] + + class SupplierPartSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPart object """ diff --git a/InvenTree/company/templates/company/manufacturer_part_suppliers.html b/InvenTree/company/templates/company/manufacturer_part_suppliers.html index 647bf4750d..9f445ec215 100644 --- a/InvenTree/company/templates/company/manufacturer_part_suppliers.html +++ b/InvenTree/company/templates/company/manufacturer_part_suppliers.html @@ -7,7 +7,7 @@ {% endblock %} {% block heading %} -{% trans "Supplier Parts" %} +{% trans "Suppliers" %} {% endblock %} {% block details %} @@ -30,9 +30,44 @@ {% endblock %} +{% block post_content_panels %} + +
+
+

{% trans "Parameters" %}

+
+
+
+
+ +
+ +
+
+
+ +
+
+
+ +{% endblock %} + {% block js_ready %} {{ block.super }} +$('#parameter-create').click(function() { + launchModalForm( + "{% url 'manufacturer-part-parameter-create' %}", + { + data: { + manufacturer_part: {{ part.id }}, + } + } + ); +}); + $('#supplier-create').click(function () { launchModalForm( "{% url 'supplier-part-create' %}", @@ -84,6 +119,16 @@ loadSupplierPartTable( } ); +loadManufacturerPartParameterTable( + "#parameter-table", + "{% url 'api-manufacturer-part-parameter-list' %}", + { + params: { + manufacturer_part: {{ part.id }}, + } + } +); + linkButtonsToSelection($("#supplier-table"), ['#supplier-part-options']) {% endblock %} \ No newline at end of file diff --git a/InvenTree/company/urls.py b/InvenTree/company/urls.py index 11f1be5339..51aa81f1c7 100644 --- a/InvenTree/company/urls.py +++ b/InvenTree/company/urls.py @@ -53,20 +53,25 @@ price_break_urls = [ url(r'^(?P\d+)/delete/', views.PriceBreakDelete.as_view(), name='price-break-delete'), ] -manufacturer_part_detail_urls = [ - url(r'^edit/?', views.ManufacturerPartEdit.as_view(), name='manufacturer-part-edit'), - - url(r'^suppliers/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-suppliers'), - - url('^.*$', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-detail'), -] - manufacturer_part_urls = [ url(r'^new/?', views.ManufacturerPartCreate.as_view(), name='manufacturer-part-create'), - url(r'delete/', views.ManufacturerPartDelete.as_view(), name='manufacturer-part-delete'), + url(r'^delete/', views.ManufacturerPartDelete.as_view(), name='manufacturer-part-delete'), - url(r'^(?P\d+)/', include(manufacturer_part_detail_urls)), + # URLs for ManufacturerPartParameter views (create / edit / delete) + url(r'^parameter/', include([ + url(r'^new/', views.ManufacturerPartParameterCreate.as_view(), name='manufacturer-part-parameter-create'), + url(r'^(?P\d)/', include([ + url(r'^edit/', views.ManufacturerPartParameterEdit.as_view(), name='manufacturer-part-parameter-edit'), + url(r'^delete/', views.ManufacturerPartParameterDelete.as_view(), name='manufacturer-part-parameter-delete'), + ])), + ])), + + url(r'^(?P\d+)/', include([ + url(r'^edit/?', views.ManufacturerPartEdit.as_view(), name='manufacturer-part-edit'), + url(r'^suppliers/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-suppliers'), + url('^.*$', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part_suppliers.html'), name='manufacturer-part-detail'), + ])), ] supplier_part_detail_urls = [ diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index 6de1439823..b2f655737f 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -23,14 +23,14 @@ from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.helpers import str2bool from InvenTree.views import InvenTreeRoleMixin -from .models import Company +from .models import Company, ManufacturerPartParameter from .models import ManufacturerPart from .models import SupplierPart from .models import SupplierPriceBreak from part.models import Part -from .forms import EditCompanyForm +from .forms import EditCompanyForm, EditManufacturerPartParameterForm from .forms import CompanyImageForm from .forms import EditManufacturerPartForm from .forms import EditSupplierPartForm @@ -504,6 +504,66 @@ class ManufacturerPartDelete(AjaxDeleteView): return self.renderJsonResponse(self.request, data=data, form=self.get_form()) +class ManufacturerPartParameterCreate(AjaxCreateView): + """ + View for creating a new ManufacturerPartParameter object + """ + + model = ManufacturerPartParameter + form_class = EditManufacturerPartParameterForm + ajax_form_title = _('Add Manufacturer Part Parameter') + + def get_form(self): + + form = super().get_form() + + # Hide the manufacturer_part field if specified + if form.initial.get('manufacturer_part', None): + form.fields['manufacturer_part'].widget = HiddenInput() + + return form + + def get_initial(self): + + initials = super().get_initial().copy() + + manufacturer_part = self.get_param('manufacturer_part') + + if manufacturer_part: + try: + initials['manufacturer_part'] = ManufacturerPartParameter.objects.get(pk=manufacturer_part) + except (ValueError, ManufacturerPartParameter.DoesNotExist): + pass + + return initials + + +class ManufacturerPartParameterEdit(AjaxUpdateView): + """ + View for editing a ManufacturerPartParameter object + """ + + model = ManufacturerPartParameter + form_class = EditManufacturerPartParameterForm + ajax_form_title = _('Edit Manufacturer Part Parameter') + + def get_form(self): + + form = super().get_form() + + form.fields['manufacturer_part'].widget = HiddenInput() + + return form + + +class ManufacturerPartParameterDelete(AjaxDeleteView): + """ + View for deleting a ManufacturerPartParameter object + """ + + model = ManufacturerPartParameter + + class SupplierPartDetail(DetailView): """ Detail view for SupplierPart """ model = SupplierPart diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 637dbbf2cf..2e434d928d 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -111,6 +111,13 @@ class PartCategoryResource(ModelResource): PartCategory.objects.rebuild() +class PartCategoryInline(admin.TabularInline): + """ + Inline for PartCategory model + """ + model = PartCategory + + class PartCategoryAdmin(ImportExportModelAdmin): resource_class = PartCategoryResource @@ -119,6 +126,10 @@ class PartCategoryAdmin(ImportExportModelAdmin): search_fields = ('name', 'description') + inlines = [ + PartCategoryInline, + ] + class PartRelatedAdmin(admin.ModelAdmin): ''' Class to manage PartRelated objects ''' diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 6c05d62a7e..8aa370a7ae 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -380,7 +380,6 @@ class Part(MPTTModel): previous.image.delete(save=False) self.clean() - self.validate_unique() super().save(*args, **kwargs) @@ -672,6 +671,8 @@ class Part(MPTTModel): super().clean() + self.validate_unique() + if self.trackable: for part in self.get_used_in().all(): diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 90e91e167f..38689df26b 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -5,6 +5,7 @@ over and above the built-in Django tags. """ import os +import sys from django.utils.translation import ugettext_lazy as _ from django.conf import settings as djangosettings @@ -114,6 +115,14 @@ def inventree_title(*args, **kwargs): return version.inventreeInstanceTitle() +@register.simple_tag() +def python_version(*args, **kwargs): + """ + Return the current python version + """ + return sys.version.split(' ')[0] + + @register.simple_tag() def inventree_version(*args, **kwargs): """ Return InvenTree version string """ diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index f32fa008a0..5f1b134966 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -44,6 +44,13 @@ class LocationResource(ModelResource): StockLocation.objects.rebuild() +class LocationInline(admin.TabularInline): + """ + Inline for sub-locations + """ + model = StockLocation + + class LocationAdmin(ImportExportModelAdmin): resource_class = LocationResource @@ -52,6 +59,10 @@ class LocationAdmin(ImportExportModelAdmin): search_fields = ('name', 'description') + inlines = [ + LocationInline, + ] + class StockItemResource(ModelResource): """ Class for managing StockItem data import/export """ diff --git a/InvenTree/templates/about.html b/InvenTree/templates/about.html index 7b023295d1..358d468ca8 100644 --- a/InvenTree/templates/about.html +++ b/InvenTree/templates/about.html @@ -34,6 +34,11 @@ {% trans "API Version" %} {% inventree_api_version %}{% include "clip.html" %} + + + {% trans "Python Version" %} + {% python_version %} + {% trans "Django Version" %} diff --git a/InvenTree/templates/js/company.js b/InvenTree/templates/js/company.js index be4b707466..078b40f4b9 100644 --- a/InvenTree/templates/js/company.js +++ b/InvenTree/templates/js/company.js @@ -126,7 +126,7 @@ function loadManufacturerPartTable(table, url, options) { queryParams: filters, name: 'manufacturerparts', groupBy: false, - formatNoMatches: function() { return "{% trans "No manufacturer parts found" %}"; }, + formatNoMatches: function() { return '{% trans "No manufacturer parts found" %}'; }, columns: [ { checkbox: true, @@ -199,6 +199,107 @@ function loadManufacturerPartTable(table, url, options) { } +function loadManufacturerPartParameterTable(table, url, options) { + /* + * Load table of ManufacturerPartParameter objects + */ + + var params = options.params || {}; + + // Load filters + var filters = loadTableFilters("manufacturer-part-parameters"); + + // Overwrite explicit parameters + for (var key in params) { + filters[key] = params[key]; + } + + // setupFilterList("manufacturer-part-parameters", $(table)); + + $(table).inventreeTable({ + url: url, + method: 'get', + original: params, + queryParams: filters, + name: 'manufacturerpartparameters', + groupBy: false, + formatNoMatches: function() { return '{% trans "No parameters found" %}'; }, + columns: [ + { + checkbox: true, + switchable: false, + visible: false, + }, + { + field: 'name', + title: '{% trans "Name" %}', + switchable: false, + sortable: true, + }, + { + field: 'value', + title: '{% trans "Value" %}', + switchable: false, + sortable: true, + }, + { + field: 'units', + title: '{% trans "Units" %}', + switchable: true, + sortable: true, + }, + { + field: 'actions', + title: '', + switchable: false, + sortable: false, + formatter: function(value, row) { + + var pk = row.pk; + + var html = `
`; + + html += makeIconButton('fa-edit icon-blue', 'button-parameter-edit', pk, '{% trans "Edit parameter" %}'); + html += makeIconButton('fa-trash-alt icon-red', 'button-parameter-delete', pk, '{% trans "Delete parameter" %}'); + + html += `
`; + + return html; + } + } + ], + onPostBody: function() { + // Setup callback functions + $(table).find('.button-parameter-edit').click(function() { + var pk = $(this).attr('pk'); + + launchModalForm( + `/manufacturer-part/parameter/${pk}/edit/`, + { + success: function() { + $(table).bootstrapTable('refresh'); + } + } + ); + + }); + $(table).find('.button-parameter-delete').click(function() { + var pk = $(this).attr('pk'); + + launchModalForm( + `/manufacturer-part/parameter/${pk}/delete/`, + { + success: function() { + $(table).bootstrapTable('refresh'); + } + } + ); + }); + } + }); +} + + function loadSupplierPartTable(table, url, options) { /* * Load supplier part table @@ -224,7 +325,7 @@ function loadSupplierPartTable(table, url, options) { queryParams: filters, name: 'supplierparts', groupBy: false, - formatNoMatches: function() { return "{% trans "No supplier parts found" %}"; }, + formatNoMatches: function() { return '{% trans "No supplier parts found" %}'; }, columns: [ { checkbox: true, @@ -260,7 +361,7 @@ function loadSupplierPartTable(table, url, options) { { sortable: true, field: 'supplier', - title: "{% trans "Supplier" %}", + title: '{% trans "Supplier" %}', formatter: function(value, row, index, field) { if (value) { var name = row.supplier_detail.name; @@ -276,7 +377,7 @@ function loadSupplierPartTable(table, url, options) { { sortable: true, field: 'SKU', - title: "{% trans "Supplier Part" %}", + title: '{% trans "Supplier Part" %}', formatter: function(value, row, index, field) { return renderLink(value, `/supplier-part/${row.pk}/`); } diff --git a/InvenTree/templates/two_column.html b/InvenTree/templates/two_column.html index 1eb1b5388e..c942bef1fa 100644 --- a/InvenTree/templates/two_column.html +++ b/InvenTree/templates/two_column.html @@ -43,6 +43,9 @@ {% endblock %} +{% block pre_content_panels %} +{% endblock %} + {% block content_panels %}
@@ -63,6 +66,9 @@
{% endblock %} +{% block post_content_panels %} +{% endblock %} + {% endblock %} {% block js_ready %} diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 0902b04ccd..09d70c3501 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -85,6 +85,7 @@ class RuleSet(models.Model): 'part_partstar', 'company_supplierpart', 'company_manufacturerpart', + 'company_manufacturerpartparameter', ], 'stock_location': [ 'stock_stocklocation', @@ -116,6 +117,8 @@ class RuleSet(models.Model): 'order_purchaseorderattachment', 'order_purchaseorderlineitem', 'company_supplierpart', + 'company_manufacturerpart', + 'company_manufacturerpartparameter', ], 'sales_order': [ 'company_company', diff --git a/tasks.py b/tasks.py index 4522629e25..b78a135b08 100644 --- a/tasks.py +++ b/tasks.py @@ -365,6 +365,21 @@ def import_records(c, filename='data.json'): print("Data import completed") + +@task +def delete_data(c, force=False): + """ + Delete all database records! + + Warning: This will REALLY delete all records in the database!! + """ + + if force: + manage(c, 'flush --noinput') + else: + manage(c, 'flush') + + @task(post=[rebuild]) def import_fixtures(c): """