From e6f56cb05681bf345603dac9e45a7c48fa8e33ce Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 May 2020 22:25:05 +1000 Subject: [PATCH] where one or more test report templates exist for a part, provide a button for all stock-items of that part, allowing the user to generate and download a test repotr --- InvenTree/InvenTree/views.py | 3 + InvenTree/report/models.py | 5 +- InvenTree/stock/forms.py | 29 +++++ InvenTree/stock/models.py | 7 ++ .../stock/templates/stock/item_base.html | 11 ++ InvenTree/stock/urls.py | 4 + InvenTree/stock/views.py | 106 ++++++++++++++---- 7 files changed, 141 insertions(+), 24 deletions(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index c988859042..09b73972c9 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -166,6 +166,9 @@ class AjaxMixin(object): except AttributeError: context = {} + if form is None: + form = self.get_form() + if form: context['form'] = form else: diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 9cf7778641..b828823b9d 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -100,7 +100,7 @@ class ReportTemplateBase(models.Model): """ def __str__(self): - return os.path.basename(self.template.name) + return "{n} - {d}".format(n=self.name, d=self.description) def getSubdir(self): return '' @@ -218,7 +218,8 @@ class TestReport(ReportTemplateBase, PartFilterMixin): def get_context_data(self, request): return { 'stock_item': self.stock_item, - 'results': self.stock_item.testResultMap() + 'results': self.stock_item.testResultMap(), + 'result_list': self.stock_item.testResultList() } diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 9576447997..31ca2a3aa0 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -142,6 +142,35 @@ class SerializeStockForm(HelperForm): ] +class TestReportFormatForm(HelperForm): + """ Form for selection a test report template """ + + class Meta: + model = StockItem + fields = [ + 'template', + ] + + + def __init__(self, stock_item, *args, **kwargs): + self.stock_item = stock_item + + super().__init__(*args, **kwargs) + self.fields['template'].choices = self.get_template_choices() + + def get_template_choices(self): + """ Available choices """ + + choices = [] + + for report in self.stock_item.part.get_test_report_templates(): + choices.append((report.pk, report)) + + return choices + + template = forms.ChoiceField(label=_('Template'), help_text=_('Select test report template')) + + class ExportOptionsForm(HelperForm): """ Form for selecting stock export options """ diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 512a156b08..f34344383a 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -963,6 +963,13 @@ class StockItem(MPTTModel): return result_map + def testResultList(self, **kwargs): + """ + Return a list of test-result objects for this StockItem + """ + + return self.testResultMap(**kwargs).values() + def requiredTestStatus(self): """ Return the status of the tests required for this StockItem. diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index b01442f3b5..819c138988 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -269,6 +269,17 @@ $("#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 %}", + { + follow: true, + } + ); +}); +{% endif %} + $("#stock-duplicate").click(function() { launchModalForm( "{% url 'stock-item-create' %}", diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index b415381061..e28d15b97d 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -25,6 +25,8 @@ stock_item_detail_urls = [ url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'), + url(r'^test-report-select/', views.StockItemTestReportSelect.as_view(), name='stock-item-test-report-select'), + url(r'^test/', views.StockItemDetail.as_view(template_name='stock/item_tests.html'), name='stock-item-test-results'), url(r'^children/', views.StockItemDetail.as_view(template_name='stock/item_childs.html'), name='stock-item-children'), url(r'^attachments/', views.StockItemDetail.as_view(template_name='stock/item_attachments.html'), name='stock-item-attachments'), @@ -53,6 +55,8 @@ stock_urls = [ url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'), + url(r'^item/test-report-download/', views.StockItemTestReportDownload.as_view(), name='stock-item-test-report-download'), + # URLs for StockItem attachments url(r'^item/attachment/', include([ url(r'^new/', views.StockItemAttachmentCreate.as_view(), name='stock-item-attachment-create'), diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index f83cfe8f96..6cdb20851a 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -27,19 +27,12 @@ from datetime import datetime from company.models import Company, SupplierPart from part.models import Part +from report.models import TestReport from .models import StockItem, StockLocation, StockItemTracking, StockItemAttachment, StockItemTestResult from .admin import StockItemResource -from .forms import EditStockLocationForm -from .forms import CreateStockItemForm -from .forms import EditStockItemForm -from .forms import AdjustStockForm -from .forms import TrackingEntryForm -from .forms import SerializeStockForm -from .forms import ExportOptionsForm -from .forms import EditStockItemAttachmentForm -from .forms import EditStockItemTestResultForm +from . import forms as StockForms class StockIndex(ListView): @@ -114,7 +107,7 @@ class StockLocationEdit(AjaxUpdateView): """ model = StockLocation - form_class = EditStockLocationForm + form_class = StockForms.EditStockLocationForm context_object_name = 'location' ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Stock Location') @@ -158,7 +151,7 @@ class StockItemAttachmentCreate(AjaxCreateView): """ model = StockItemAttachment - form_class = EditStockItemAttachmentForm + form_class = StockForms.EditStockItemAttachmentForm ajax_form_title = _("Add Stock Item Attachment") ajax_template_name = "modal_form.html" @@ -203,7 +196,7 @@ class StockItemAttachmentEdit(AjaxUpdateView): """ model = StockItemAttachment - form_class = EditStockItemAttachmentForm + form_class = StockForms.EditStockItemAttachmentForm ajax_form_title = _("Edit Stock Item Attachment") def get_form(self): @@ -271,7 +264,7 @@ class StockItemTestResultCreate(AjaxCreateView): """ model = StockItemTestResult - form_class = EditStockItemTestResultForm + form_class = StockForms.EditStockItemTestResultForm ajax_form_title = _("Add Test Result") def post_save(self, **kwargs): @@ -319,7 +312,7 @@ class StockItemTestResultEdit(AjaxUpdateView): """ model = StockItemTestResult - form_class = EditStockItemTestResultForm + form_class = StockForms.EditStockItemTestResultForm ajax_form_title = _("Edit Test Result") def get_form(self): @@ -343,12 +336,81 @@ class StockItemTestResultDelete(AjaxDeleteView): context_object_name = "result" +class StockItemTestReportSelect(AjaxView): + """ + View for selecting a TestReport template, + and generating a TestReport as a PDF. + """ + + model = StockItem + ajax_form_title = _("Select Test Report Template") + + def get_form(self): + + stock_item = StockItem.objects.get(pk=self.kwargs['pk']) + return StockForms.TestReportFormatForm(stock_item) + + def post(self, request, *args, **kwargs): + + template_id = request.POST.get('template', None) + + try: + template = TestReport.objects.get(pk=template_id) + except (ValueError, TestReport.DoesNoteExist): + raise ValidationError({'template': _("Select valid template")}) + + stock_item = StockItem.objects.get(pk=self.kwargs['pk']) + + url = reverse('stock-item-test-report-download') + + url += '?stock_item={id}'.format(id=stock_item.pk) + url += '&template={id}'.format(id=template.pk) + + data = { + 'form_valid': True, + 'url': url, + } + + return self.renderJsonResponse(request, self.get_form(), data=data) + + +class StockItemTestReportDownload(AjaxView): + """ + Download a TestReport against a StockItem. + + Requires the following arguments to be passed as URL params: + + stock_item - Valid PK of a StockItem object + template - Valid PK of a TestReport template object + + """ + + def get(self, request, *args, **kwargs): + + template = request.GET.get('template', None) + stock_item = request.GET.get('stock_item', None) + + try: + template = TestReport.objects.get(pk=template) + except (ValueError, TestReport.DoesNotExist): + raise ValidationError({'template': 'Invalid template ID'}) + + try: + stock_item = StockItem.objects.get(pk=stock_item) + except (ValueError, StockItem.DoesNotExist): + raise ValidationError({'stock_item': 'Invalid StockItem ID'}) + + template.stock_item = stock_item + + return template.render(request) + + class StockExportOptions(AjaxView): """ Form for selecting StockExport options """ model = StockLocation ajax_form_title = _('Stock Export Options') - form_class = ExportOptionsForm + form_class = StockForms.ExportOptionsForm def post(self, request, *args, **kwargs): @@ -491,7 +553,7 @@ class StockAdjust(AjaxView, FormMixin): ajax_template_name = 'stock/stock_adjust.html' ajax_form_title = _('Adjust Stock') - form_class = AdjustStockForm + form_class = StockForms.AdjustStockForm stock_items = [] def get_GET_items(self): @@ -809,7 +871,7 @@ class StockItemEdit(AjaxUpdateView): """ model = StockItem - form_class = EditStockItemForm + form_class = StockForms.EditStockItemForm context_object_name = 'item' ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Stock Item') @@ -845,7 +907,7 @@ class StockLocationCreate(AjaxCreateView): """ model = StockLocation - form_class = EditStockLocationForm + form_class = StockForms.EditStockLocationForm context_object_name = 'location' ajax_template_name = 'modal_form.html' ajax_form_title = _('Create new Stock Location') @@ -870,7 +932,7 @@ class StockItemSerialize(AjaxUpdateView): model = StockItem ajax_template_name = 'stock/item_serialize.html' ajax_form_title = _('Serialize Stock') - form_class = SerializeStockForm + form_class = StockForms.SerializeStockForm def get_form(self): @@ -958,7 +1020,7 @@ class StockItemCreate(AjaxCreateView): """ model = StockItem - form_class = CreateStockItemForm + form_class = StockForms.CreateStockItemForm context_object_name = 'item' ajax_template_name = 'modal_form.html' ajax_form_title = _('Create new Stock Item') @@ -1265,7 +1327,7 @@ class StockItemTrackingEdit(AjaxUpdateView): model = StockItemTracking ajax_form_title = _('Edit Stock Tracking Entry') - form_class = TrackingEntryForm + form_class = StockForms.TrackingEntryForm class StockItemTrackingCreate(AjaxCreateView): @@ -1274,7 +1336,7 @@ class StockItemTrackingCreate(AjaxCreateView): model = StockItemTracking ajax_form_title = _("Add Stock Tracking Entry") - form_class = TrackingEntryForm + form_class = StockForms.TrackingEntryForm def post(self, request, *args, **kwargs):