diff --git a/InvenTree/label/admin.py b/InvenTree/label/admin.py index bc03e122b6..71399efabb 100644 --- a/InvenTree/label/admin.py +++ b/InvenTree/label/admin.py @@ -8,7 +8,7 @@ from .models import StockItemLabel class StockItemLabelAdmin(admin.ModelAdmin): - list_display = ('name', 'description', 'label') + list_display = ('name', 'description', 'label', 'filters', 'enabled') admin.site.register(StockItemLabel, StockItemLabelAdmin) diff --git a/InvenTree/label/migrations/0002_stockitemlabel_enabled.py b/InvenTree/label/migrations/0002_stockitemlabel_enabled.py new file mode 100644 index 0000000000..684299e184 --- /dev/null +++ b/InvenTree/label/migrations/0002_stockitemlabel_enabled.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-08-22 23:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('label', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='stockitemlabel', + name='enabled', + field=models.BooleanField(default=True, help_text='Label template is enabled', verbose_name='Enabled'), + ), + ] diff --git a/InvenTree/label/models.py b/InvenTree/label/models.py index 7d481327a9..49b07572a8 100644 --- a/InvenTree/label/models.py +++ b/InvenTree/label/models.py @@ -70,6 +70,12 @@ class LabelTemplate(models.Model): validators=[validateFilterString] ) + enabled = models.BooleanField( + default=True, + help_text=_('Label template is enabled'), + verbose_name=_('Enabled') + ) + def get_record_data(self, items): """ Return a list of dict objects, one for each item. diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index fba217e0d9..b8e9c859bb 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -43,7 +43,6 @@ from InvenTree.helpers import decimal2string, normalize from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus -from report import models as ReportModels from build import models as BuildModels from order import models as OrderModels from company.models import SupplierPart @@ -406,24 +405,6 @@ class Part(MPTTModel): self.category = category self.save() - def get_test_report_templates(self): - """ - Return all the TestReport template objects which map to this Part. - """ - - templates = [] - - for report in ReportModels.TestReport.objects.all(): - if report.matches_part(self): - templates.append(report) - - return templates - - def has_test_report_templates(self): - """ Return True if this part has a TestReport defined """ - - return len(self.get_test_report_templates()) > 0 - def get_absolute_url(self): """ Return the web URL for viewing this part """ return reverse('part-detail', kwargs={'pk': self.id}) diff --git a/InvenTree/report/admin.py b/InvenTree/report/admin.py index 4183e8ee83..7d6403f5d9 100644 --- a/InvenTree/report/admin.py +++ b/InvenTree/report/admin.py @@ -3,13 +3,12 @@ from __future__ import unicode_literals from django.contrib import admin -from .models import ReportTemplate, ReportAsset -from .models import TestReport +from .models import TestReport, ReportAsset class ReportTemplateAdmin(admin.ModelAdmin): - list_display = ('name', 'description', 'template') + list_display = ('name', 'description', 'template', 'filters', 'enabled') class ReportAssetAdmin(admin.ModelAdmin): @@ -17,6 +16,5 @@ class ReportAssetAdmin(admin.ModelAdmin): list_display = ('asset', 'description') -admin.site.register(ReportTemplate, ReportTemplateAdmin) admin.site.register(TestReport, ReportTemplateAdmin) admin.site.register(ReportAsset, ReportAssetAdmin) diff --git a/InvenTree/report/migrations/0002_delete_reporttemplate.py b/InvenTree/report/migrations/0002_delete_reporttemplate.py new file mode 100644 index 0000000000..7abfe72c59 --- /dev/null +++ b/InvenTree/report/migrations/0002_delete_reporttemplate.py @@ -0,0 +1,16 @@ +# Generated by Django 3.0.7 on 2020-08-22 23:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0001_initial'), + ] + + operations = [ + migrations.DeleteModel( + name='ReportTemplate', + ), + ] diff --git a/InvenTree/report/migrations/0003_testreport_enabled.py b/InvenTree/report/migrations/0003_testreport_enabled.py new file mode 100644 index 0000000000..74de252435 --- /dev/null +++ b/InvenTree/report/migrations/0003_testreport_enabled.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-08-23 10:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0002_delete_reporttemplate'), + ] + + operations = [ + migrations.AddField( + model_name='testreport', + name='enabled', + field=models.BooleanField(default=True, help_text='Report template is enabled', verbose_name='Enabled'), + ), + ] diff --git a/InvenTree/report/migrations/0004_auto_20200823_1104.py b/InvenTree/report/migrations/0004_auto_20200823_1104.py new file mode 100644 index 0000000000..ab9e961ad9 --- /dev/null +++ b/InvenTree/report/migrations/0004_auto_20200823_1104.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-08-23 11:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0003_testreport_enabled'), + ] + + operations = [ + migrations.RenameField( + model_name='testreport', + old_name='part_filters', + new_name='filters', + ), + ] diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 697f5691d5..05507099a7 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -16,9 +16,11 @@ from django.conf import settings from django.core.validators import FileExtensionValidator from django.core.exceptions import ValidationError -from django.utils.translation import gettext_lazy as _ +from stock.models import StockItem -from part import models as PartModels +from InvenTree.helpers import validateFilterString + +from django.utils.translation import gettext_lazy as _ try: from django_weasyprint import WeasyTemplateResponseMixin @@ -55,59 +57,6 @@ def rename_template(instance, filename): return os.path.join('report', 'report_template', instance.getSubdir(), filename) -def validateFilterString(value): - """ - Validate that a provided filter string looks like a list of comma-separated key=value pairs - - These should nominally match to a valid database filter based on the model being filtered. - - e.g. "category=6, IPN=12" - e.g. "part__name=widget" - - The ReportTemplate class uses the filter string to work out which items a given report applies to. - For example, an acceptance test report template might only apply to stock items with a given IPN, - so the string could be set to: - - filters = "IPN = ACME0001" - - Returns a map of key:value pairs - """ - - # Empty results map - results = {} - - value = str(value).strip() - - if not value or len(value) == 0: - return results - - groups = value.split(',') - - for group in groups: - group = group.strip() - - pair = group.split('=') - - if not len(pair) == 2: - raise ValidationError( - "Invalid group: {g}".format(g=group) - ) - - k, v = pair - - k = k.strip() - v = v.strip() - - if not k or not v: - raise ValidationError( - "Invalid group: {g}".format(g=group) - ) - - results[k] = v - - return results - - class WeasyprintReportMixin(WeasyTemplateResponseMixin): """ Class for rendering a HTML template to a PDF. @@ -198,54 +147,24 @@ class ReportTemplateBase(models.Model): description = models.CharField(max_length=250, help_text=_("Report template description")) - class Meta: - abstract = True + enabled = models.BooleanField( + default=True, + help_text=_('Report template is enabled'), + verbose_name=_('Enabled') + ) - -class ReportTemplate(ReportTemplateBase): - """ - A simple reporting template which is used to upload template files, - which can then be used in other concrete template classes. - """ - - pass - - -class PartFilterMixin(models.Model): - """ - A model mixin used for matching a report type against a Part object. - Used to assign a report to a given part using custom filters. - """ - - class Meta: - abstract = True - - def matches_part(self, part): - """ - Test if this report matches a given part. - """ - - filters = self.get_part_filters() - - parts = PartModels.Part.objects.filter(**filters) - - parts = parts.filter(pk=part.pk) - - return parts.exists() - - def get_part_filters(self): - """ Return a map of filters to be used for Part filtering """ - return validateFilterString(self.part_filters) - - part_filters = models.CharField( + filters = models.CharField( blank=True, max_length=250, help_text=_("Part query filters (comma-separated list of key=value pairs)"), validators=[validateFilterString] ) + class Meta: + abstract = True -class TestReport(ReportTemplateBase, PartFilterMixin): + +class TestReport(ReportTemplateBase): """ Render a TestReport against a StockItem object. """ @@ -256,6 +175,17 @@ class TestReport(ReportTemplateBase, PartFilterMixin): # Requires a stock_item object to be given to it before rendering stock_item = None + def matches_stock_item(self, item): + """ + Test if this report template matches a given StockItem objects + """ + + filters = validateFilterString(self.part_filters) + + items = StockItem.objects.filter(**filters) + + return items.exists() + def get_context_data(self, request): return { 'stock_item': self.stock_item, diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 234da6d53b..97d028ec2b 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -15,6 +15,8 @@ from InvenTree.helpers import GetExportFormats from InvenTree.forms import HelperForm from InvenTree.fields import RoundingDecimalFormField +from report.models import TestReport + from .models import StockLocation, StockItem, StockItemTracking from .models import StockItemAttachment from .models import StockItemTestResult @@ -225,12 +227,17 @@ class TestReportFormatForm(HelperForm): self.fields['template'].choices = self.get_template_choices() def get_template_choices(self): - """ Available choices """ + """ + Generate a list of of TestReport options for the StockItem + """ choices = [] - for report in self.stock_item.part.get_test_report_templates(): - choices.append((report.pk, report)) + templates = TestReport.objects.filter(enabled=True) + + for template in templates: + if template.matches_stock_item(self.stock_item): + choices.append(template) return choices diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 1b4171297e..bcc2087b25 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -124,11 +124,9 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% endif %} - {% if item.part.has_test_report_templates %} - {% endif %} {% endblock %} @@ -303,7 +301,6 @@ $("#stock-serialize").click(function() { ); }); -{% if item.part.has_test_report_templates %} $("#stock-test-report").click(function() { launchModalForm( "{% url 'stock-item-test-report-select' item.id %}", @@ -312,7 +309,6 @@ $("#stock-test-report").click(function() { } ); }); -{% endif %} $("#print-label").click(function() { launchModalForm( diff --git a/InvenTree/stock/templates/stock/item_tests.html b/InvenTree/stock/templates/stock/item_tests.html index c79068349d..10bb0950e4 100644 --- a/InvenTree/stock/templates/stock/item_tests.html +++ b/InvenTree/stock/templates/stock/item_tests.html @@ -17,9 +17,7 @@ {% endif %} - {% if item.part.has_test_report_templates %} - {% endif %}
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 1c9b78a3f0..faad9db324 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -310,7 +310,8 @@ class StockItemSelectLabels(AjaxView): labels = [] - for label in StockItemLabel.objects.all(): + # Construct a list of StockItemLabel objects which are enabled, and the filters match the selected StockItem + for label in StockItemLabel.objects.filter(enabled=True): if label.matches_stock_item(item): labels.append(label) diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index 0a231044cd..3d6bcd0734 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -1,56 +1,65 @@ {% load static %} {% load i18n %} - -
\ No newline at end of file