From f1f31a1338a153bfa2ffef6e4ec1a77509aa043e Mon Sep 17 00:00:00 2001 From: mpdgraev Date: Fri, 9 Oct 2020 16:15:32 +0200 Subject: [PATCH 1/5] fix error caused by assumption that json.loads() returns an object with a .keys() function --- .../barcode/plugins/inventree_barcode.py | 2 ++ InvenTree/barcode/tests.py | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/InvenTree/barcode/plugins/inventree_barcode.py b/InvenTree/barcode/plugins/inventree_barcode.py index 6e4e6937a0..93fb58cbc0 100644 --- a/InvenTree/barcode/plugins/inventree_barcode.py +++ b/InvenTree/barcode/plugins/inventree_barcode.py @@ -42,6 +42,8 @@ class InvenTreeBarcodePlugin(BarcodePlugin): elif type(self.data) is str: try: self.data = json.loads(self.data) + if type(self.data) is not dict: + return False except json.JSONDecodeError: return False else: diff --git a/InvenTree/barcode/tests.py b/InvenTree/barcode/tests.py index 98e126eba1..4b7356aead 100644 --- a/InvenTree/barcode/tests.py +++ b/InvenTree/barcode/tests.py @@ -56,6 +56,34 @@ class BarcodeAPITest(APITestCase): self.assertIn('plugin', data) self.assertIsNone(data['plugin']) + def test_integer_barcode(self): + + response = self.postBarcode(self.scan_url, '123456789') + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.data + self.assertIn('error', data) + + self.assertIn('barcode_data', data) + self.assertIn('hash', data) + self.assertIn('plugin', data) + self.assertIsNone(data['plugin']) + + def test_array_barcode(self): + + response = self.postBarcode(self.scan_url, "['foo', 'bar']") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.data + self.assertIn('error', data) + + self.assertIn('barcode_data', data) + self.assertIn('hash', data) + self.assertIn('plugin', data) + self.assertIsNone(data['plugin']) + def test_barcode_generation(self): item = StockItem.objects.get(pk=522) From eb98496a7963982f344dc27dd642e9a8ae5ded28 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 12 Oct 2020 07:25:32 -0500 Subject: [PATCH 2/5] Added part 'default_location' to serializer --- InvenTree/part/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 847216e957..6cadb2c724 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -304,6 +304,7 @@ class PartSerializer(InvenTreeModelSerializer): 'category_detail', 'component', 'description', + 'default_location', 'full_name', 'image', 'in_stock', From 3143242d1349525e1f4a32269b3ba2e186d7e94a Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 12 Oct 2020 13:14:24 -0500 Subject: [PATCH 3/5] Added category 'default_location' to serializer --- InvenTree/part/serializers.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 6cadb2c724..f6eb8dc95b 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -2,30 +2,20 @@ JSON serializers for Part app """ import imghdr - -from rest_framework import serializers - -from .models import Part, PartStar - -from .models import PartCategory -from .models import BomItem -from .models import PartParameter, PartParameterTemplate -from .models import PartAttachment -from .models import PartTestTemplate -from .models import PartSellPriceBreak - -from stock.models import StockItem - from decimal import Decimal -from sql_util.utils import SubquerySum, SubqueryCount - from django.db.models import Q from django.db.models.functions import Coalesce +from InvenTree.serializers import (InvenTreeAttachmentSerializerField, + InvenTreeModelSerializer) +from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus +from rest_framework import serializers +from sql_util.utils import SubqueryCount, SubquerySum +from stock.models import StockItem -from InvenTree.status_codes import PurchaseOrderStatus, BuildStatus -from InvenTree.serializers import InvenTreeModelSerializer -from InvenTree.serializers import InvenTreeAttachmentSerializerField +from .models import (BomItem, Part, PartAttachment, PartCategory, + PartParameter, PartParameterTemplate, PartSellPriceBreak, + PartStar, PartTestTemplate) class CategorySerializer(InvenTreeModelSerializer): @@ -41,6 +31,7 @@ class CategorySerializer(InvenTreeModelSerializer): 'pk', 'name', 'description', + 'default_location', 'pathstring', 'url', 'parent', From dbee26aaad730715b994a9b88da1b1014fec79cc Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 13 Oct 2020 10:29:34 -0500 Subject: [PATCH 4/5] Fixed insertion of backslash in the barcode button group --- InvenTree/stock/templates/stock/item_base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 0b1d8f0d09..5197285379 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -93,7 +93,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% if item.uid %}
  • {% trans "Unlink Barcode" %}
  • {% else %} -
  • {% trans "Link Barcode" %}
  • \ +
  • {% trans "Link Barcode" %}
  • {% endif %} {% endif %} From 3c175a6c8d6bb086a79dd08fe5475d714a668a48 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 18 Oct 2020 22:24:45 +1100 Subject: [PATCH 5/5] Update creation of StockItem via API - If no location is specified, but a default location exists for the part, use that - If a location is specified (even if it is null) then the specified value is used instead --- InvenTree/stock/api.py | 27 +++++++++++++++++++-- InvenTree/stock/test_api.py | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index ba802b75d9..ab70c98322 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -324,8 +324,31 @@ class StockList(generics.ListCreateAPIView): serializer_class = StockItemSerializer queryset = StockItem.objects.all() - # TODO - Override the 'create' method for this view, - # to allow the user to be recorded when a new StockItem object is created + def create(self, request, *args, **kwargs): + """ + Create a new StockItem object via the API. + + We override the default 'create' implementation. + + If a location is *not* specified, but the linked *part* has a default location, + we can pre-fill the location automatically. + """ + + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + item = serializer.save() + + # A location was *not* specified - try to infer it + if 'location' not in request.data: + location = item.part.get_default_location() + if location is not None: + item.location = location + item.save() + + # Return a response + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def list(self, request, *args, **kwargs): """ diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 8348a3e331..45a1669535 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -97,6 +97,53 @@ class StockItemTest(StockAPITestCase): response = self.client.get(self.list_url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_create_default_location(self): + """ + Test the default location functionality, + if a 'location' is not specified in the creation request. + """ + + # The part 'R_4K7_0603' (pk=4) has a default location specified + + response = self.client.post( + self.list_url, + data={ + 'part': 4, + 'quantity': 10 + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['location'], 2) + + # What if we explicitly set the location to a different value? + + response = self.client.post( + self.list_url, + data={ + 'part': 4, + 'quantity': 20, + 'location': 1, + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['location'], 1) + + # And finally, what if we set the location explicitly to None? + + response = self.client.post( + self.list_url, + data={ + 'part': 4, + 'quantity': 20, + 'location': '', + } + ) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['location'], None) + def test_stock_item_create(self): """ Test creation of a StockItem via the API