From 7215a563b19cf470d77adc299109861e0166a098 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 May 2020 21:22:43 +1000 Subject: [PATCH] Add PartFilterMixin --- InvenTree/report/models.py | 100 ++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 46fdd965e7..ffd77a09b1 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -9,9 +9,12 @@ import os from django.db import models from django.core.validators import FileExtensionValidator +from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +from part.models import Part + from django_tex.shortcuts import render_to_pdf from django_weasyprint import WeasyTemplateResponseMixin @@ -23,6 +26,59 @@ 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. @@ -101,13 +157,6 @@ class ReportTemplateBase(models.Model): description = models.CharField(max_length=250, help_text=_("Report template description")) - filters = models.CharField( - blank=True, - max_length=250, - help_text=_("Query filters (comma-separated list of key=value pairs)"), - validators=[validate_filter_string] - ) - class Meta: abstract = True @@ -121,7 +170,42 @@ class ReportTemplate(ReportTemplateBase): pass -class TestReport(ReportTemplateBase): +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 = 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( + blank=True, + max_length=250, + help_text=_("Part query filters (comma-separated list of key=value pairs)"), + validators=[validateFilterString] + ) + + +class TestReport(ReportTemplateBase, PartFilterMixin): """ Render a TestReport against a StockItem object. """