From f36c5137dd924215bf5b5319305b5dfbb757e2c9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 11 May 2020 23:25:55 +1000 Subject: [PATCH 1/5] Add ability to add part file attachments via the API --- InvenTree/part/api.py | 81 +++++++++++++++++++++-------------- InvenTree/part/serializers.py | 17 ++++++++ 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 6f5327baa4..f53be800b5 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -19,6 +19,7 @@ from django.urls import reverse from .models import Part, PartCategory, BomItem, PartStar from .models import PartParameter, PartParameterTemplate +from .models import PartAttachment from . import serializers as part_serializers @@ -105,6 +106,27 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView): queryset = PartCategory.objects.all() +class PartAttachmentList(generics.ListCreateAPIView): + """ + API endpoint for listing (and creating) a PartAttachment (file upload). + """ + + queryset = PartAttachment.objects.all() + serializer_class = part_serializers.PartAttachmentSerializer + + permission_classes = [permissions.IsAuthenticated] + + filter_backends = [ + DjangoFilterBackend, + filters.OrderingFilter, + filters.SearchFilter, + ] + + filter_fields = [ + 'part', + ] + + class PartThumbs(generics.ListAPIView): """ API endpoint for retrieving information on available Part thumbnails """ @@ -617,34 +639,31 @@ class BomItemValidate(generics.UpdateAPIView): return Response(serializer.data) - -cat_api_urls = [ - - url(r'^(?P\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'), - - url(r'^$', CategoryList.as_view(), name='api-part-category-list'), -] - - -part_star_api_urls = [ - url(r'^(?P\d+)/?', PartStarDetail.as_view(), name='api-part-star-detail'), - - # Catchall - url(r'^.*$', PartStarList.as_view(), name='api-part-star-list'), -] - -part_param_api_urls = [ - url(r'^template/$', PartParameterTemplateList.as_view(), name='api-part-param-template-list'), - - url(r'^.*$', PartParameterList.as_view(), name='api-part-param-list'), -] - part_api_urls = [ url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'), - url(r'^category/', include(cat_api_urls)), - url(r'^star/', include(part_star_api_urls)), - url(r'^parameter/', include(part_param_api_urls)), + # Base URL for PartCategory API endpoints + url(r'^category/', include([ + url(r'^(?P\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'), + url(r'^$', CategoryList.as_view(), name='api-part-category-list'), + ])), + + # Base URL for PartAttachment API endpoints + url(r'attachment/', include([ + url(r'^$', PartAttachmentList.as_view(), name='api-part-attachment-list'), + ])), + + # Base URL for PartStar API endpoints + url(r'^star/', include([ + url(r'^(?P\d+)/?', PartStarDetail.as_view(), name='api-part-star-detail'), + url(r'^.*$', PartStarList.as_view(), name='api-part-star-list'), + ])), + + # Base URL for PartParameter API endpoints + url(r'^parameter/', include([ + url(r'^template/$', PartParameterTemplateList.as_view(), name='api-part-param-template-list'), + url(r'^.*$', PartParameterList.as_view(), name='api-part-param-list'), + ])), url(r'^thumbs/', PartThumbs.as_view(), name='api-part-thumbs'), @@ -653,16 +672,12 @@ part_api_urls = [ url(r'^.*$', PartList.as_view(), name='api-part-list'), ] -bom_item_urls = [ - - url(r'^validate/?', BomItemValidate.as_view(), name='api-bom-item-validate'), - - url(r'^.*$', BomDetail.as_view(), name='api-bom-item-detail'), -] - bom_api_urls = [ # BOM Item Detail - url(r'^(?P\d+)/', include(bom_item_urls)), + url(r'^(?P\d+)/', include([ + url(r'^validate/?', BomItemValidate.as_view(), name='api-bom-item-validate'), + url(r'^.*$', BomDetail.as_view(), name='api-bom-item-detail'), + ])), # Catch-all url(r'^.*$', BomList.as_view(), name='api-bom-list'), diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 920e0486c3..74005dd5af 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -9,6 +9,7 @@ from .models import Part, PartStar from .models import PartCategory from .models import BomItem from .models import PartParameter, PartParameterTemplate +from .models import PartAttachment from decimal import Decimal @@ -39,6 +40,22 @@ class CategorySerializer(InvenTreeModelSerializer): ] +class PartAttachmentSerializer(InvenTreeModelSerializer): + """ + Serializer for the PartAttachment class + """ + + class Meta: + model = PartAttachment + + fields = [ + 'pk', + 'part', + 'attachment', + 'comment' + ] + + class PartThumbSerializer(serializers.Serializer): """ Serializer for the 'image' field of the Part model. From 17d0a015f2a7d257cff656c17dfb94c568872386 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 11 May 2020 23:32:40 +1000 Subject: [PATCH 2/5] Add API endpoint for StockItem attachment items --- InvenTree/part/api.py | 1 + InvenTree/stock/api.py | 26 ++++++++++++++++++++++++++ InvenTree/stock/serializers.py | 15 +++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index f53be800b5..a0ee9dcd0b 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -639,6 +639,7 @@ class BomItemValidate(generics.UpdateAPIView): return Response(serializer.data) + part_api_urls = [ url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'), diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 6b7c15f980..03333569ef 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -12,6 +12,7 @@ from django.db.models import Q from .models import StockLocation, StockItem from .models import StockItemTracking +from .models import StockItemAttachment from part.models import Part, PartCategory from part.serializers import PartBriefSerializer @@ -22,6 +23,7 @@ from company.serializers import SupplierPartSerializer from .serializers import StockItemSerializer from .serializers import LocationSerializer, LocationBriefSerializer from .serializers import StockTrackingSerializer +from .serializers import StockItemAttachmentSerializer from InvenTree.views import TreeSerializer from InvenTree.helpers import str2bool, isNull @@ -624,6 +626,25 @@ class StockList(generics.ListCreateAPIView): ] +class StockAttachmentList(generics.ListCreateAPIView): + """ + API endpoint for listing (and creating) a StockItemAttachment (file upload) + """ + + queryset = StockItemAttachment.objects.all() + serializer_class = StockItemAttachmentSerializer + + filter_backends = [ + DjangoFilterBackend, + filters.OrderingFilter, + filters.SearchFilter, + ] + + filter_fields = [ + 'stock_item', + ] + + class StockTrackingList(generics.ListCreateAPIView): """ API endpoint for list view of StockItemTracking objects. @@ -692,6 +713,11 @@ stock_api_urls = [ url(r'remove/?', StockRemove.as_view(), name='api-stock-remove'), url(r'transfer/?', StockTransfer.as_view(), name='api-stock-transfer'), + # Base URL for StockItemAttachment API endpoints + url(r'^attachment/', include([ + url(r'^$', StockAttachmentList.as_view(), name='api-stock-attachment-list'), + ])), + url(r'track/?', StockTrackingList.as_view(), name='api-stock-track'), url(r'^tree/?', StockCategoryTree.as_view(), name='api-stock-tree'), diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index e04e2a149b..7e9a59470b 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -6,6 +6,7 @@ from rest_framework import serializers from .models import StockItem, StockLocation from .models import StockItemTracking +from .models import StockItemAttachment from django.db.models import Sum, Count from django.db.models.functions import Coalesce @@ -189,6 +190,20 @@ class LocationSerializer(InvenTreeModelSerializer): ] +class StockItemAttachmentSerializer(InvenTreeModelSerializer): + """ Serializer for StockItemAttachment model """ + + class Meta: + model = StockItemAttachment + + fields = [ + 'pk', + 'stock_item', + 'attachment', + 'comment' + ] + + class StockTrackingSerializer(InvenTreeModelSerializer): """ Serializer for StockItemTracking model """ From a7e5a79f4c88dd6164f2fd6cc2af970fe4a074a3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 11 May 2020 23:41:57 +1000 Subject: [PATCH 3/5] Expose salesorder and purchaseorder attachments to the API --- InvenTree/order/api.py | 57 +++++++++++++++++++++++++++++++--- InvenTree/order/serializers.py | 33 ++++++++++++++++++++ InvenTree/part/api.py | 4 +-- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 0f84d6bc32..cb48b4c11d 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -9,7 +9,7 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions from rest_framework import filters -from django.conf.urls import url +from django.conf.urls import url, include from InvenTree.helpers import str2bool @@ -17,10 +17,12 @@ from part.models import Part from company.models import SupplierPart from .models import PurchaseOrder, PurchaseOrderLineItem -from .serializers import POSerializer, POLineItemSerializer +from .models import PurchaseOrderAttachment +from .serializers import POSerializer, POLineItemSerializer, POAttachmentSerializer from .models import SalesOrder, SalesOrderLineItem -from .serializers import SalesOrderSerializer, SOLineItemSerializer +from .models import SalesOrderAttachment +from .serializers import SalesOrderSerializer, SOLineItemSerializer, SOAttachmentSerializer class POList(generics.ListCreateAPIView): @@ -198,6 +200,25 @@ class POLineItemDetail(generics.RetrieveUpdateAPIView): ] +class SOAttachmentList(generics.ListCreateAPIView): + """ + API endpoint for listing (and creating) a SalesOrderAttachment (file upload) + """ + + queryset = SalesOrderAttachment.objects.all() + serializer_class = SOAttachmentSerializer + + filter_backends = [ + DjangoFilterBackend, + filters.OrderingFilter, + filters.SearchFilter, + ] + + filter_fields = [ + 'order', + ] + + class SOList(generics.ListCreateAPIView): """ API endpoint for accessing a list of SalesOrder objects. @@ -378,10 +399,32 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView): permission_classes = [permissions.IsAuthenticated] +class POAttachmentList(generics.ListCreateAPIView): + """ + API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload) + """ + + queryset = PurchaseOrderAttachment.objects.all() + serializer_class = POAttachmentSerializer + + filter_backends = [ + DjangoFilterBackend, + filters.OrderingFilter, + filters.SearchFilter, + ] + + filter_fields = [ + 'order', + ] + + order_api_urls = [ # API endpoints for purchase orders url(r'^po/(?P\d+)/$', PODetail.as_view(), name='api-po-detail'), - url(r'^po/$', POList.as_view(), name='api-po-list'), + url(r'po/attachment/', include([ + url(r'^.*$', POAttachmentList.as_view(), name='api-po-attachment-list'), + ])), + url(r'^po/.*$', POList.as_view(), name='api-po-list'), # API endpoints for purchase order line items url(r'^po-line/(?P\d+)/$', POLineItemDetail.as_view(), name='api-po-line-detail'), @@ -389,7 +432,11 @@ order_api_urls = [ # API endpoints for sales ordesr url(r'^so/(?P\d+)/$', SODetail.as_view(), name='api-so-detail'), - url(r'^so/$', SOList.as_view(), name='api-so-list'), + url(r'so/attachment/', include([ + url(r'^.*$', SOAttachmentList.as_view(), name='api-so-attachment-list'), + ])), + + url(r'^so/.*$', SOList.as_view(), name='api-so-list'), # API endpoints for sales order line items url(r'^so-line/(?P\d+)/$', SOLineItemDetail.as_view(), name='api-so-line-detail'), diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index e0ef57f802..c22a637c67 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -14,6 +14,7 @@ from company.serializers import CompanyBriefSerializer, SupplierPartSerializer from part.serializers import PartBriefSerializer from .models import PurchaseOrder, PurchaseOrderLineItem +from .models import PurchaseOrderAttachment, SalesOrderAttachment from .models import SalesOrder, SalesOrderLineItem from .models import SalesOrderAllocation @@ -106,6 +107,22 @@ class POLineItemSerializer(InvenTreeModelSerializer): ] +class POAttachmentSerializer(InvenTreeModelSerializer): + """ + Serializers for the PurchaseOrderAttachment model + """ + + class Meta: + model = PurchaseOrderAttachment + + fields = [ + 'pk', + 'order', + 'attachment', + 'comment', + ] + + class SalesOrderSerializer(InvenTreeModelSerializer): """ Serializers for the SalesOrder object @@ -231,3 +248,19 @@ class SOLineItemSerializer(InvenTreeModelSerializer): 'part', 'part_detail', ] + + +class SOAttachmentSerializer(InvenTreeModelSerializer): + """ + Serializers for the SalesOrderAttachment model + """ + + class Meta: + model = SalesOrderAttachment + + fields = [ + 'pk', + 'order', + 'attachment', + 'comment', + ] diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index a0ee9dcd0b..c0d79b15b5 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -646,12 +646,12 @@ part_api_urls = [ # Base URL for PartCategory API endpoints url(r'^category/', include([ url(r'^(?P\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'), - url(r'^$', CategoryList.as_view(), name='api-part-category-list'), + url(r'.*^$', CategoryList.as_view(), name='api-part-category-list'), ])), # Base URL for PartAttachment API endpoints url(r'attachment/', include([ - url(r'^$', PartAttachmentList.as_view(), name='api-part-attachment-list'), + url(r'^.*$', PartAttachmentList.as_view(), name='api-part-attachment-list'), ])), # Base URL for PartStar API endpoints From cfc0145180c4e64e73581b6427a2d69fadf57fb2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 11 May 2020 23:44:22 +1000 Subject: [PATCH 4/5] Add some unit tests --- InvenTree/order/test_api.py | 18 +++++++++++++++++- InvenTree/part/api.py | 6 +++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py index cb0ffa2566..e23a591e43 100644 --- a/InvenTree/order/test_api.py +++ b/InvenTree/order/test_api.py @@ -31,7 +31,7 @@ class OrderTest(APITestCase): return self.client.get(url + "?" + options, format='json') - def test_po_list(self,): + def test_po_list(self): url = reverse('api-po-list') @@ -42,3 +42,19 @@ class OrderTest(APITestCase): # Filter by stuff response = self.doGet(url, 'status=10&part=1&supplier_part=1') self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_po_attachments(self): + + url = reverse('api-po-attachment-list') + + response = self.doGet(url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_so_attachments(self): + + url = reverse('api-so-attachment-list') + + response = self.doGet(url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index c0d79b15b5..8f9121885d 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -646,18 +646,18 @@ part_api_urls = [ # Base URL for PartCategory API endpoints url(r'^category/', include([ url(r'^(?P\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'), - url(r'.*^$', CategoryList.as_view(), name='api-part-category-list'), + url(r'^$', CategoryList.as_view(), name='api-part-category-list'), ])), # Base URL for PartAttachment API endpoints url(r'attachment/', include([ - url(r'^.*$', PartAttachmentList.as_view(), name='api-part-attachment-list'), + url(r'^$', PartAttachmentList.as_view(), name='api-part-attachment-list'), ])), # Base URL for PartStar API endpoints url(r'^star/', include([ url(r'^(?P\d+)/?', PartStarDetail.as_view(), name='api-part-star-detail'), - url(r'^.*$', PartStarList.as_view(), name='api-part-star-list'), + url(r'^$', PartStarList.as_view(), name='api-part-star-list'), ])), # Base URL for PartParameter API endpoints From 31516129625f26f3b1a711b0dbd0f5a1e2c8a092 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 12 May 2020 00:07:08 +1000 Subject: [PATCH 5/5] PEP fix --- InvenTree/order/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py index e23a591e43..3a0c48a809 100644 --- a/InvenTree/order/test_api.py +++ b/InvenTree/order/test_api.py @@ -57,4 +57,4 @@ class OrderTest(APITestCase): response = self.doGet(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file + self.assertEqual(response.status_code, status.HTTP_200_OK)