From 4bd0872b2c54be7377a2b03e1234af80fd604303 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 May 2020 23:33:27 +1000 Subject: [PATCH 1/6] Expose some more stock item data over API --- InvenTree/part/serializers.py | 2 ++ InvenTree/stock/api.py | 19 ++++--------------- InvenTree/stock/models.py | 4 ++++ InvenTree/stock/serializers.py | 7 +++++++ 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 64cf656f36..2cb893b304 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -105,9 +105,11 @@ class PartBriefSerializer(InvenTreeModelSerializer): 'thumbnail', 'active', 'assembly', + 'is_template', 'purchaseable', 'salable', 'stock', + 'trackable', 'virtual', ] diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 93cf9067f3..9972b1c982 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -80,21 +80,10 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView): def get_serializer(self, *args, **kwargs): - try: - kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', False)) - except AttributeError: - pass - - try: - kwargs['location_detail'] = str2bool(self.request.query_params.get('location_detail', False)) - except AttributeError: - pass - - try: - kwargs['supplier_part_detail'] = str2bool(self.request.query_params.get('supplier_detail', False)) - except AttributeError: - pass - + kwargs['part_detail'] = True + kwargs['location_detail'] = True + kwargs['supplier_part_detail'] = True + kwargs['test_detail'] = True kwargs['context'] = self.get_serializer_context() return self.serializer_class(*args, **kwargs) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index f34344383a..468f72df37 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1007,6 +1007,10 @@ class StockItem(MPTTModel): 'failed': failed, } + @property + def required_test_count(self): + return self.part.getRequiredTests().count() + def hasRequiredTests(self): return self.part.getRequiredTests().count() > 0 diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 865a63a2c2..a84ea92540 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -108,11 +108,14 @@ class StockItemSerializer(InvenTreeModelSerializer): quantity = serializers.FloatField() allocated = serializers.FloatField() + required_tests = serializers.IntegerField(source='required_test_count', read_only=True) + def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) location_detail = kwargs.pop('location_detail', False) supplier_part_detail = kwargs.pop('supplier_part_detail', False) + test_detail = kwargs.pop('test_detail', False) super(StockItemSerializer, self).__init__(*args, **kwargs) @@ -125,6 +128,9 @@ class StockItemSerializer(InvenTreeModelSerializer): if supplier_part_detail is not True: self.fields.pop('supplier_part_detail') + if test_detail is not True: + self.fields.pop('required_tests') + class Meta: model = StockItem fields = [ @@ -141,6 +147,7 @@ class StockItemSerializer(InvenTreeModelSerializer): 'part_detail', 'pk', 'quantity', + 'required_tests', 'sales_order', 'serial', 'supplier_part', From 50987f47b07092761c419d096c3f233cdff02cf3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 22 May 2020 23:37:26 +1000 Subject: [PATCH 2/6] Add a buttony thing --- InvenTree/stock/templates/stock/item_tests.html | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/templates/stock/item_tests.html b/InvenTree/stock/templates/stock/item_tests.html index b311f13584..c79068349d 100644 --- a/InvenTree/stock/templates/stock/item_tests.html +++ b/InvenTree/stock/templates/stock/item_tests.html @@ -13,10 +13,13 @@
- {% if user.is_staff %} {% endif %} + + {% if item.part.has_test_report_templates %} + + {% endif %}
@@ -43,6 +46,17 @@ function reloadTable() { //$("#test-result-table").bootstrapTable("refresh"); } +{% if item.part.has_test_report_templates %} +$("#test-report").click(function() { + launchModalForm( + "{% url 'stock-item-test-report-select' item.id %}", + { + follow: true, + } + ); +}); +{% endif %} + {% if user.is_staff %} $("#delete-test-results").click(function() { launchModalForm( From e63342418f6fa5d6e2c2ebf42d1070fd1ca6c4dc Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 23 May 2020 11:30:42 +1000 Subject: [PATCH 3/6] Improve / simplify logic for file attachments against test result object --- InvenTree/stock/api.py | 22 ---------------- .../migrations/0042_auto_20200523_0121.py | 19 ++++++++++++++ InvenTree/stock/models.py | 25 ++++++------------- InvenTree/stock/serializers.py | 6 ----- InvenTree/stock/views.py | 13 ---------- InvenTree/templates/js/stock.html | 4 +-- 6 files changed, 29 insertions(+), 60 deletions(-) create mode 100644 InvenTree/stock/migrations/0042_auto_20200523_0121.py diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 9972b1c982..9abbd8cda2 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -693,11 +693,6 @@ class StockItemTestResultList(generics.ListCreateAPIView): except: pass - try: - kwargs['attachment_detail'] = str2bool(self.request.query_params.get('attachment_detail', False)) - except: - pass - kwargs['context'] = self.get_serializer_context() return self.serializer_class(*args, **kwargs) @@ -713,23 +708,6 @@ class StockItemTestResultList(generics.ListCreateAPIView): # Capture the user information test_result = serializer.save() test_result.user = self.request.user - - # Check if a file has been attached to the request - attachment_file = self.request.FILES.get('attachment', None) - - if attachment_file: - # Create a new attachment associated with the stock item - attachment = StockItemAttachment( - attachment=attachment_file, - stock_item=test_result.stock_item, - user=test_result.user - ) - - attachment.save() - - # Link the attachment back to the test result - test_result.attachment = attachment - test_result.save() diff --git a/InvenTree/stock/migrations/0042_auto_20200523_0121.py b/InvenTree/stock/migrations/0042_auto_20200523_0121.py new file mode 100644 index 0000000000..66db1441e3 --- /dev/null +++ b/InvenTree/stock/migrations/0042_auto_20200523_0121.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.5 on 2020-05-23 01:21 + +from django.db import migrations, models +import stock.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0041_stockitemtestresult_notes'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitemtestresult', + name='attachment', + field=models.FileField(blank=True, help_text='Test result attachment', null=True, upload_to=stock.models.rename_stock_item_test_result_attachment, verbose_name='Attachment'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 468f72df37..9ed2a55d4a 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1094,6 +1094,11 @@ class StockItemTracking(models.Model): # file = models.FileField() +def rename_stock_item_test_result_attachment(instance, filename): + + return os.path.join('stock_files', str(instance.stock_item.pk), os.path.basename(filename)) + + class StockItemTestResult(models.Model): """ A StockItemTestResult records results of custom tests against individual StockItem objects. @@ -1123,13 +1128,11 @@ class StockItemTestResult(models.Model): super().clean() - """ # If this test result corresponds to a template, check the requirements of the template key = helpers.generateTestKey(self.test) templates = self.stock_item.part.getTestTemplates() - TODO: Re-introduce this at a later stage, it is buggy when uplaoding an attachment via the API for template in templates: if key == template.key: @@ -1146,17 +1149,6 @@ class StockItemTestResult(models.Model): }) break - """ - - # If an attachment is linked to this result, the attachment must also point to the item - try: - if self.attachment: - if not self.attachment.stock_item == self.stock_item: - raise ValidationError({ - 'attachment': _("Test result attachment must be linked to the same StockItem"), - }) - except (StockItem.DoesNotExist, StockItemAttachment.DoesNotExist): - pass stock_item = models.ForeignKey( StockItem, @@ -1182,10 +1174,9 @@ class StockItemTestResult(models.Model): help_text=_('Test output value') ) - attachment = models.ForeignKey( - StockItemAttachment, - on_delete=models.SET_NULL, - blank=True, null=True, + attachment = models.FileField( + null=True, blank=True, + upload_to=rename_stock_item_test_result_attachment, verbose_name=_('Attachment'), help_text=_('Test result attachment'), ) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index a84ea92540..d0d7162370 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -229,20 +229,15 @@ class StockItemTestResultSerializer(InvenTreeModelSerializer): """ Serializer for the StockItemTestResult model """ user_detail = UserSerializerBrief(source='user', read_only=True) - attachment_detail = StockItemAttachmentSerializer(source='attachment', read_only=True) def __init__(self, *args, **kwargs): user_detail = kwargs.pop('user_detail', False) - attachment_detail = kwargs.pop('attachment_detail', False) super().__init__(*args, **kwargs) if user_detail is not True: self.fields.pop('user_detail') - if attachment_detail is not True: - self.fields.pop('attachment_detail') - class Meta: model = StockItemTestResult @@ -253,7 +248,6 @@ class StockItemTestResultSerializer(InvenTreeModelSerializer): 'result', 'value', 'attachment', - 'attachment_detail', 'notes', 'user', 'user_detail', diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index d0a57d3d1f..02a7ee9719 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -292,17 +292,6 @@ class StockItemTestResultCreate(AjaxCreateView): form = super().get_form() form.fields['stock_item'].widget = HiddenInput() - # Extract the StockItem object - item_id = form['stock_item'].value() - - # Limit the options for the file attachments - try: - stock_item = StockItem.objects.get(pk=item_id) - form.fields['attachment'].queryset = stock_item.attachments.all() - except (ValueError, StockItem.DoesNotExist): - # Hide the attachments field - form.fields['attachment'].widget = HiddenInput() - return form @@ -320,8 +309,6 @@ class StockItemTestResultEdit(AjaxUpdateView): form = super().get_form() form.fields['stock_item'].widget = HiddenInput() - - form.fields['attachment'].queryset = self.object.stock_item.attachments.all() return form diff --git a/InvenTree/templates/js/stock.html b/InvenTree/templates/js/stock.html index 49c3151ff0..68f1a0c5c5 100644 --- a/InvenTree/templates/js/stock.html +++ b/InvenTree/templates/js/stock.html @@ -56,8 +56,8 @@ function loadStockTestResultsTable(table, options) { html += `${row.user_detail.username}`; } - if (row.attachment_detail) { - html += ``; + if (row.attachment) { + html += ``; } return html; From 01481ef5c9f965a88aee0e0c400794d82f4395ab Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 23 May 2020 14:28:25 +1000 Subject: [PATCH 4/6] Add function to get the number of required tests for a part --- InvenTree/part/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 735a6de574..451c916542 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1033,6 +1033,9 @@ class Part(MPTTModel): # Return the tests which are required by this part return self.getTestTemplates(required=True) + def requiredTestCount(self): + return self.getRequiredTests().count() + @property def attachment_count(self): """ Count the number of attachments for this part. From e4d10279fa6ceea68e043560445ff26847a54947 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 24 May 2020 20:04:34 +1000 Subject: [PATCH 5/6] Include 'key' field in StockItemTestResult serializer --- InvenTree/stock/api.py | 2 ++ InvenTree/stock/models.py | 6 +++++- InvenTree/stock/serializers.py | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 9abbd8cda2..516edc5040 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -687,6 +687,8 @@ class StockItemTestResultList(generics.ListCreateAPIView): 'value', ] + ordering = 'date' + def get_serializer(self, *args, **kwargs): try: kwargs['user_detail'] = str2bool(self.request.query_params.get('user_detail', False)) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 9ed2a55d4a..02393023b9 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1129,7 +1129,7 @@ class StockItemTestResult(models.Model): super().clean() # If this test result corresponds to a template, check the requirements of the template - key = helpers.generateTestKey(self.test) + key = self.key templates = self.stock_item.part.getTestTemplates() @@ -1150,6 +1150,10 @@ class StockItemTestResult(models.Model): break + @property + def key(self): + return helpers.generateTestKey(self.test) + stock_item = models.ForeignKey( StockItem, on_delete=models.CASCADE, diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index d0d7162370..19d05d7b7b 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -230,6 +230,8 @@ class StockItemTestResultSerializer(InvenTreeModelSerializer): user_detail = UserSerializerBrief(source='user', read_only=True) + key = serializers.CharField(read_only=True) + def __init__(self, *args, **kwargs): user_detail = kwargs.pop('user_detail', False) @@ -244,6 +246,7 @@ class StockItemTestResultSerializer(InvenTreeModelSerializer): fields = [ 'pk', 'stock_item', + 'key', 'test', 'result', 'value', From c44205273c7a25377008b39503076296766aa75c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 24 May 2020 20:05:34 +1000 Subject: [PATCH 6/6] Simplify javascript --- InvenTree/templates/js/stock.html | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/InvenTree/templates/js/stock.html b/InvenTree/templates/js/stock.html index 68f1a0c5c5..fd2dd61746 100644 --- a/InvenTree/templates/js/stock.html +++ b/InvenTree/templates/js/stock.html @@ -32,17 +32,6 @@ function noResultBadge() { return `{% trans "NO RESULT" %}`; } -function testKey(test_name) { - // Convert test name to a unique key without any illegal chars - - test_name = test_name.trim().toLowerCase(); - test_name = test_name.replace(' ', ''); - - test_name = test_name.replace(/[^0-9a-z]/gi, ''); - - return test_name; -} - function loadStockTestResultsTable(table, options) { /* * Load StockItemTestResult table @@ -177,14 +166,14 @@ function loadStockTestResultsTable(table, options) { var match = false; var override = false; - var key = testKey(item.test); + var key = item.key; // Try to associate this result with a test row tableData.forEach(function(row, index) { // The result matches the test template row - if (key == testKey(row.test_name)) { + if (key == row.key) { // Force the names to be the same! item.test_name = row.test_name;