From 71d78951488dc41c7a83ead1d7a0f53e3003c761 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 15 Apr 2017 23:04:09 +1000 Subject: [PATCH 1/8] Improved Part API docs --- InvenTree/InvenTree/urls.py | 13 +++++- InvenTree/part/urls.py | 27 ++---------- InvenTree/part/views.py | 84 +++++++++++++++++++++++++++++++++---- 3 files changed, 93 insertions(+), 31 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 63014a36d1..ce47010cb1 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -3,12 +3,23 @@ from django.contrib import admin from rest_framework.documentation import include_docs_urls +from part.urls import part_urls, part_cat_urls, part_param_urls, part_param_template_urls + admin.site.site_header = "InvenTree Admin" apipatterns = [ + + # Stock URLs url(r'^stock/', include('stock.urls')), url(r'^stock-location/', include('stock.location_urls')), - url(r'^part/', include('part.urls')), + + # Part URLs + url(r'^part/', include(part_urls)), + url(r'^part-category/', include(part_cat_urls)), + url(r'^part-param/', include(part_param_urls)), + url(r'^part-param-template/', include(part_param_template_urls)), + + # Supplier URLs url(r'^supplier/', include('supplier.urls')), url(r'^supplier-part/', include('supplier.part_urls')), url(r'^price-break/', include('supplier.price_urls')), diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 72fffc5b32..057ea18233 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -2,12 +2,7 @@ from django.conf.urls import url, include from . import views -""" URL patterns associated with part categories: -/category -> List all top-level categories -/category/ -> Detail view of given category -/category/new -> Create a new category -""" -categorypatterns = [ +part_cat_urls = [ # Part category detail url(r'^(?P[0-9]+)/?$', views.PartCategoryDetail.as_view(), name='partcategory-detail'), @@ -17,7 +12,7 @@ categorypatterns = [ url(r'^$', views.PartCategoryList.as_view()) ] -partparampatterns = [ +part_param_urls = [ # Detail of a single part parameter url(r'^(?P[0-9]+)/?$', views.PartParamDetail.as_view(), name='partparameter-detail'), @@ -26,7 +21,7 @@ partparampatterns = [ url(r'^$', views.PartParamList.as_view()), ] -parttemplatepatterns = [ +part_param_template_urls = [ # Detail of a single part field template url(r'^(?P[0-9]+)/?$', views.PartTemplateDetail.as_view(), name='partparametertemplate-detail'), @@ -35,21 +30,7 @@ parttemplatepatterns = [ url(r'^$', views.PartTemplateList.as_view()) ] -""" Top-level URL patterns for the Part app: -/part/ -> List all parts -/part/new -> Create a new part -/part/ -> (refer to partdetailpatterns) -/part/category -> (refer to categorypatterns) -""" -urlpatterns = [ - # Part categories - url(r'^category/', include(categorypatterns)), - - # Part parameters - url(r'^parameter/', include(partparampatterns)), - - # Part templates - url(r'^template/', include(parttemplatepatterns)), +part_urls = [ # Individual part url(r'^(?P[0-9]+)/?$', views.PartDetail.as_view(), name='part-detail'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index eb930606b1..79e190ada2 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -11,7 +11,17 @@ from .serializers import PartTemplateSerializer class PartDetail(generics.RetrieveUpdateDestroyAPIView): - """ Return information on a single part + """ + + get: + Return detail on a single Part + + post: + Update data for a single Part + + delete: + Remove a part from the database + """ queryset = Part.objects.all() serializer_class = PartSerializer @@ -19,7 +29,13 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView): class PartParamList(generics.ListCreateAPIView): - """ Return all parameters associated with a particular part + """ + + get: + Return a list of all part parameters (with optional filters) + + post: + Create a new part parameter """ def get_queryset(self): part_id = self.request.query_params.get('part', None) @@ -34,7 +50,17 @@ class PartParamList(generics.ListCreateAPIView): class PartParamDetail(generics.RetrieveUpdateDestroyAPIView): - """ Detail view of a single PartParameter + """ + + get: + Detail view of a single PartParameter + + post: + Update data for a PartParameter + + delete: + Remove a PartParameter from the database + """ queryset = PartParameter.objects.all() @@ -54,7 +80,13 @@ class PartFilter(django_filters.rest_framework.FilterSet): class PartList(generics.ListCreateAPIView): - """ List of parts, with optional filters + """ + + get: + List of Parts, with optional filters + + post: + Create a new Part """ def get_queryset(self): @@ -73,7 +105,17 @@ class PartList(generics.ListCreateAPIView): class PartCategoryDetail(generics.RetrieveUpdateDestroyAPIView): - """ Return information on a single PartCategory + """ + + get: + Return information on a single PartCategory + + post: + Update a PartCategory + + delete: + Remove a PartCategory + """ queryset = PartCategory.objects.all() serializer_class = PartCategorySerializer @@ -81,8 +123,14 @@ class PartCategoryDetail(generics.RetrieveUpdateDestroyAPIView): class PartCategoryList(generics.ListCreateAPIView): - """ Return a list of all top-level part categories. - Categories are considered "top-level" if they do not have a parent + """ + + get: + Return a list of all categories + (with optional filters) + + post: + Create a new PartCategory """ def get_queryset(self): @@ -100,6 +148,18 @@ class PartCategoryList(generics.ListCreateAPIView): class PartTemplateDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return detail on a single PartParameterTemplate object + + post: + Update a PartParameterTemplate object + + delete: + Remove a PartParameterTemplate object + + """ queryset = PartParameterTemplate.objects.all() serializer_class = PartTemplateSerializer @@ -107,6 +167,16 @@ class PartTemplateDetail(generics.RetrieveUpdateDestroyAPIView): class PartTemplateList(generics.ListCreateAPIView): + """ + + get: + Return a list of all PartParameterTemplate objects + (with optional query filters) + + post: + Create a new PartParameterTemplate object + + """ queryset = PartParameterTemplate.objects.all() serializer_class = PartTemplateSerializer From 4259c6f9eb6fbb1acf8f2aadc37ff865c2758963 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 15 Apr 2017 23:14:05 +1000 Subject: [PATCH 2/8] Fixed stock URLs and API docs --- InvenTree/InvenTree/urls.py | 5 ++-- InvenTree/stock/location_urls.py | 10 ------- InvenTree/stock/urls.py | 10 ++++++- InvenTree/stock/views.py | 45 +++++++++++++++++++++++++++++--- 4 files changed, 53 insertions(+), 17 deletions(-) delete mode 100644 InvenTree/stock/location_urls.py diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index ce47010cb1..ca3a620d37 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -4,14 +4,15 @@ from django.contrib import admin from rest_framework.documentation import include_docs_urls from part.urls import part_urls, part_cat_urls, part_param_urls, part_param_template_urls +from stock.urls import stock_urls, stock_loc_urls admin.site.site_header = "InvenTree Admin" apipatterns = [ # Stock URLs - url(r'^stock/', include('stock.urls')), - url(r'^stock-location/', include('stock.location_urls')), + url(r'^stock/', include(stock_urls)), + url(r'^stock-location/', include(stock_loc_urls)), # Part URLs url(r'^part/', include(part_urls)), diff --git a/InvenTree/stock/location_urls.py b/InvenTree/stock/location_urls.py deleted file mode 100644 index 9df7a53b28..0000000000 --- a/InvenTree/stock/location_urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.conf.urls import url -from . import views - -urlpatterns = [ - url(r'^(?P[0-9]+)/?$', views.LocationDetail.as_view(), name='stocklocation-detail'), - - url(r'^\?.*/?$', views.LocationList.as_view()), - - url(r'^$', views.LocationList.as_view()) -] diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index b2822682d5..bf58ddbb89 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -2,7 +2,7 @@ from django.conf.urls import url from . import views -urlpatterns = [ +stock_urls = [ # Detail for a single stock item url(r'^(?P[0-9]+)/?$', views.StockDetail.as_view(), name='stockitem-detail'), @@ -10,3 +10,11 @@ urlpatterns = [ url(r'^\?.*/?$', views.StockList.as_view()), url(r'^$', views.StockList.as_view()), ] + +stock_loc_urls = [ + url(r'^(?P[0-9]+)/?$', views.LocationDetail.as_view(), name='stocklocation-detail'), + + url(r'^\?.*/?$', views.LocationList.as_view()), + + url(r'^$', views.LocationList.as_view()) +] diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 27fa65bfbe..ad7742fd21 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -10,13 +10,24 @@ from .serializers import StockItemSerializer, LocationSerializer class StockDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single StockItem object + + post: + Update a StockItem + + delete: + Remove a StockItem + """ queryset = StockItem.objects.all() serializer_class = StockItemSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) -class StockFilter(django_filters.rest_framework.FilterSet): +class StockFilter(FilterSet): min_stock = NumberFilter(name='quantity', lookup_expr='gte') max_stock = NumberFilter(name='quantity', lookup_expr='lte') part = NumberFilter(name='part', lookup_expr='exact') @@ -28,6 +39,15 @@ class StockFilter(django_filters.rest_framework.FilterSet): class StockList(generics.ListCreateAPIView): + """ + + get: + Return a list of all StockItem objects + (with optional query filters) + + post: + Create a new StockItem + """ queryset = StockItem.objects.all() serializer_class = StockItemSerializer @@ -37,7 +57,17 @@ class StockList(generics.ListCreateAPIView): class LocationDetail(generics.RetrieveUpdateDestroyAPIView): - """ Return information on a specific stock location + """ + + get: + Return a single StockLocation object + + post: + Update a StockLocation object + + delete: + Remove a StockLocation object + """ queryset = StockLocation.objects.all() @@ -55,8 +85,15 @@ class StockLocationFilter(FilterSet): class LocationList(generics.ListCreateAPIView): - """ Return a list of top-level locations - Locations are considered "top-level" if they do not have a parent + """ + + get: + Return a list of all StockLocation objects + (with optional query filter) + + post: + Create a new StockLocation + """ queryset = StockLocation.objects.all() From a20987f1229ad029c17d5669cec9d402ff67224f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 15 Apr 2017 23:14:16 +1000 Subject: [PATCH 3/8] Added part filters --- InvenTree/part/views.py | 46 +++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 79e190ada2..9bb416917c 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1,4 +1,6 @@ -# import django_filters +import django_filters +from django_filters.rest_framework import FilterSet, DjangoFilterBackend +from django_filters import NumberFilter from rest_framework import generics, permissions @@ -28,6 +30,15 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) +class PartParamFilter(FilterSet): + + part = NumberFilter(name='part', lookup_expr='exact') + + class Meta: + model = PartParameter + fields = ['part'] + + class PartParamList(generics.ListCreateAPIView): """ @@ -37,16 +48,12 @@ class PartParamList(generics.ListCreateAPIView): post: Create a new part parameter """ - def get_queryset(self): - part_id = self.request.query_params.get('part', None) - - if part_id: - return PartParameter.objects.filter(part=part_id) - else: - return PartParameter.objects.all() + queryset = PartParameter.objects.all() serializer_class = PartParameterSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + filter_backends = (DjangoFilterBackend,) + filter_class = PartParamFilter class PartParamDetail(generics.RetrieveUpdateDestroyAPIView): @@ -68,15 +75,12 @@ class PartParamDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) -""" -class PartFilter(django_filters.rest_framework.FilterSet): - min_stock = django_filters.NumberFilter(name="stock", lookup_expr="gte") - max_stock = django_filters.NumberFilter(name="stock", lookup_expr="lte") +class PartFilter(FilterSet): + category = NumberFilter(name='category', lookup_expr='exact') class Meta: model = Part - fields = ['stock'] -""" + fields = ['category'] class PartList(generics.ListCreateAPIView): @@ -89,19 +93,11 @@ class PartList(generics.ListCreateAPIView): Create a new Part """ - def get_queryset(self): - parts = Part.objects.all() - params = self.request.query_params - - cat_id = params.get('category', None) - - if cat_id: - parts = parts.filter(category=cat_id) - - return parts - + queryset = Part.objects.all() serializer_class = PartSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + filter_backends = (DjangoFilterBackend,) + filter_class = PartFilter class PartCategoryDetail(generics.RetrieveUpdateDestroyAPIView): From e8dc592ec743fb985a152c72be2b1620d3dc9e95 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 15 Apr 2017 23:21:45 +1000 Subject: [PATCH 4/8] Fixed Project URLs and API docs --- InvenTree/InvenTree/urls.py | 7 +++- InvenTree/project/urls.py | 18 ++++------- InvenTree/project/views.py | 64 +++++++++++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index ca3a620d37..ba9aa88d91 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -5,6 +5,7 @@ from rest_framework.documentation import include_docs_urls from part.urls import part_urls, part_cat_urls, part_param_urls, part_param_template_urls from stock.urls import stock_urls, stock_loc_urls +from project.urls import prj_urls, prj_part_urls, prj_cat_urls admin.site.site_header = "InvenTree Admin" @@ -27,7 +28,11 @@ apipatterns = [ url(r'^manufacturer/', include('supplier.manufacturer_urls')), url(r'^customer/', include('supplier.customer_urls')), url(r'^track/', include('track.urls')), - url(r'^project/', include('project.urls')), + + # Project URLs + url(r'^project/', include(prj_urls)), + url(r'^project-category/', include(prj_cat_urls)), + url(r'^project-part/', include(prj_part_urls)), ] urlpatterns = [ diff --git a/InvenTree/project/urls.py b/InvenTree/project/urls.py index a0f0bc29b6..181f8c3b0d 100644 --- a/InvenTree/project/urls.py +++ b/InvenTree/project/urls.py @@ -2,35 +2,29 @@ from django.conf.urls import url, include from . import views -projectpartpatterns = [ +prj_part_urls = [ # Detail of a single project part url(r'^(?P[0-9]+)/?$', views.ProjectPartDetail.as_view(), name='projectpart-detail'), # List project parts, with optional filters url(r'^\?.*/?$', views.ProjectPartsList.as_view()), - url(r'^$', views.ProjectPartsList.as_view()), + url(r'^$', views.ProjectPartsList.as_view()) ] -projectcategorypatterns = [ +prj_cat_urls = [ # Detail of a single project category url(r'^(?P[0-9]+)/?$', views.ProjectCategoryDetail.as_view(), name='projectcategory-detail'), # List of project categories, with filters url(r'^\?.*/?$', views.ProjectCategoryList.as_view()), - url(r'^$', views.ProjectCategoryList.as_view()), + url(r'^$', views.ProjectCategoryList.as_view()) ] -urlpatterns = [ +prj_urls = [ # Individual project URL url(r'^(?P[0-9]+)/?$', views.ProjectDetail.as_view(), name='project-detail'), # List of all projects url(r'^\?.*/?$', views.ProjectList.as_view()), - url(r'^$', views.ProjectList.as_view()), - - # Project parts - url(r'^parts/', include(projectpartpatterns)), - - # Project categories - url(r'^category/', include(projectcategorypatterns)), + url(r'^$', views.ProjectList.as_view()) ] diff --git a/InvenTree/project/views.py b/InvenTree/project/views.py index 1aeeb5d6b6..2a293a0cca 100644 --- a/InvenTree/project/views.py +++ b/InvenTree/project/views.py @@ -8,7 +8,17 @@ from .serializers import ProjectPartSerializer class ProjectDetail(generics.RetrieveUpdateDestroyAPIView): - """ Project details + """ + + get: + Return a single Project object + + post: + Update a Project + + delete: + Remove a Project + """ queryset = Project.objects.all() @@ -17,7 +27,15 @@ class ProjectDetail(generics.RetrieveUpdateDestroyAPIView): class ProjectList(generics.ListCreateAPIView): - """ List projects + """ + + get: + Return a list of all Project objects + (with optional query filters) + + post: + Create a new Project + """ def get_queryset(self): @@ -36,7 +54,17 @@ class ProjectList(generics.ListCreateAPIView): class ProjectCategoryDetail(generics.RetrieveUpdateAPIView): - """ Project details + """ + + get: + Return a single ProjectCategory object + + post: + Update a ProjectCategory + + delete: + Remove a ProjectCategory + """ queryset = ProjectCategory.objects.all() @@ -45,7 +73,14 @@ class ProjectCategoryDetail(generics.RetrieveUpdateAPIView): class ProjectCategoryList(generics.ListCreateAPIView): - """ List project categories + """ + + get: + Return a list of all ProjectCategory objects + + post: + Create a new ProjectCategory + """ def get_queryset(self): @@ -62,7 +97,14 @@ class ProjectCategoryList(generics.ListCreateAPIView): class ProjectPartsList(generics.ListCreateAPIView): - """ List project parts + """ + + get: + Return a list of all ProjectPart objects + + post: + Create a new ProjectPart + """ serializer_class = ProjectPartSerializer @@ -84,7 +126,17 @@ class ProjectPartsList(generics.ListCreateAPIView): class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView): - """ Detail for a single project part + """ + + get: + Return a single ProjectPart object + + post: + Update a ProjectPart + + delete: + Remove a ProjectPart + """ queryset = ProjectPart.objects.all() From d75ed57d48f92a07b9b4d62cf59ed77ca93a34e1 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 15 Apr 2017 23:34:01 +1000 Subject: [PATCH 5/8] Updated Supplier URLs and API docs --- InvenTree/InvenTree/models.py | 2 +- InvenTree/InvenTree/urls.py | 14 ++-- InvenTree/supplier/customer_urls.py | 12 --- InvenTree/supplier/manufacturer_urls.py | 12 --- InvenTree/supplier/part_urls.py | 10 --- InvenTree/supplier/price_urls.py | 10 --- InvenTree/supplier/urls.py | 29 ++++++- InvenTree/supplier/views.py | 107 ++++++++++++++++++++++++ 8 files changed, 144 insertions(+), 52 deletions(-) delete mode 100644 InvenTree/supplier/customer_urls.py delete mode 100644 InvenTree/supplier/manufacturer_urls.py delete mode 100644 InvenTree/supplier/part_urls.py delete mode 100644 InvenTree/supplier/price_urls.py diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index c6aec418ad..35ed5ccc0c 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -11,7 +11,7 @@ class Company(models.Model): class Meta: abstract = True - name = models.CharField(max_length=100) + name = models.CharField(max_length=100, unique=True) website = models.URLField(blank=True) address = models.CharField(max_length=200, blank=True) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index ba9aa88d91..26ef11466a 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -6,6 +6,8 @@ from rest_framework.documentation import include_docs_urls from part.urls import part_urls, part_cat_urls, part_param_urls, part_param_template_urls from stock.urls import stock_urls, stock_loc_urls from project.urls import prj_urls, prj_part_urls, prj_cat_urls +from supplier.urls import cust_urls, manu_urls, supplier_part_urls, price_break_urls, supplier_urls +import supplier admin.site.site_header = "InvenTree Admin" @@ -22,11 +24,13 @@ apipatterns = [ url(r'^part-param-template/', include(part_param_template_urls)), # Supplier URLs - url(r'^supplier/', include('supplier.urls')), - url(r'^supplier-part/', include('supplier.part_urls')), - url(r'^price-break/', include('supplier.price_urls')), - url(r'^manufacturer/', include('supplier.manufacturer_urls')), - url(r'^customer/', include('supplier.customer_urls')), + url(r'^supplier/', include(supplier_urls)), + url(r'^supplier-part/', include(supplier_part_urls)), + url(r'^price-break/', include(price_break_urls)), + url(r'^manufacturer/', include(manu_urls)), + url(r'^customer/', include(cust_urls)), + + # Tracking URLs url(r'^track/', include('track.urls')), # Project URLs diff --git a/InvenTree/supplier/customer_urls.py b/InvenTree/supplier/customer_urls.py deleted file mode 100644 index 9bfc222074..0000000000 --- a/InvenTree/supplier/customer_urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.conf.urls import url - -from . import views - -urlpatterns = [ - # Customer detail - url(r'^(?P[0-9]+)/?$', views.CustomerDetail.as_view(), name='customer-detail'), - - # List customers - url(r'^\?.*/?$', views.CustomerList.as_view()), - url(r'^$', views.CustomerList.as_view()) -] diff --git a/InvenTree/supplier/manufacturer_urls.py b/InvenTree/supplier/manufacturer_urls.py deleted file mode 100644 index 9586990f1b..0000000000 --- a/InvenTree/supplier/manufacturer_urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.conf.urls import url - -from . import views - -urlpatterns = [ - # Manufacturer detail - url(r'^(?P[0-9]+)/?$', views.ManufacturerDetail.as_view(), name='manufacturer-detail'), - - # List manufacturers - url(r'^\?.*/?$', views.ManufacturerList.as_view()), - url(r'^$', views.ManufacturerList.as_view()) -] diff --git a/InvenTree/supplier/part_urls.py b/InvenTree/supplier/part_urls.py deleted file mode 100644 index 813e4ce37e..0000000000 --- a/InvenTree/supplier/part_urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.conf.urls import url - -from . import views - -urlpatterns = [ - url(r'^(?P[0-9]+)/?$', views.SupplierPartDetail.as_view(), name='supplierpart-detail'), - - url(r'^\?.*/?$', views.SupplierPartList.as_view()), - url(r'^$', views.SupplierPartList.as_view()) -] diff --git a/InvenTree/supplier/price_urls.py b/InvenTree/supplier/price_urls.py deleted file mode 100644 index 044f22b289..0000000000 --- a/InvenTree/supplier/price_urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.conf.urls import url - -from . import views - -urlpatterns = [ - url(r'^(?P[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view(), name='supplierpricebreak-detail'), - - url(r'^\?.*/?$', views.SupplierPriceBreakList.as_view()), - url(r'^$', views.SupplierPriceBreakList.as_view()) -] diff --git a/InvenTree/supplier/urls.py b/InvenTree/supplier/urls.py index f810662976..f6491f5bba 100644 --- a/InvenTree/supplier/urls.py +++ b/InvenTree/supplier/urls.py @@ -2,14 +2,39 @@ from django.conf.urls import url from . import views -pricepatterns = [ +cust_urls = [ + # Customer detail + url(r'^(?P[0-9]+)/?$', views.CustomerDetail.as_view(), name='customer-detail'), + + # List customers + url(r'^\?.*/?$', views.CustomerList.as_view()), + url(r'^$', views.CustomerList.as_view()) +] + +manu_urls = [ + # Manufacturer detail + url(r'^(?P[0-9]+)/?$', views.ManufacturerDetail.as_view(), name='manufacturer-detail'), + + # List manufacturers + url(r'^\?.*/?$', views.ManufacturerList.as_view()), + url(r'^$', views.ManufacturerList.as_view()) +] + +supplier_part_urls = [ + url(r'^(?P[0-9]+)/?$', views.SupplierPartDetail.as_view(), name='supplierpart-detail'), + + url(r'^\?.*/?$', views.SupplierPartList.as_view()), + url(r'^$', views.SupplierPartList.as_view()) +] + +price_break_urls = [ url(r'^(?P[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view(), name='supplierpricebreak-detail'), url(r'^\?.*/?$', views.SupplierPriceBreakList.as_view()), url(r'^$', views.SupplierPriceBreakList.as_view()) ] -urlpatterns = [ +supplier_urls = [ # Display details of a supplier url(r'^(?P[0-9]+)/$', views.SupplierDetail.as_view(), name='supplier-detail'), diff --git a/InvenTree/supplier/views.py b/InvenTree/supplier/views.py index 5867ddea67..ac8995de48 100644 --- a/InvenTree/supplier/views.py +++ b/InvenTree/supplier/views.py @@ -13,6 +13,18 @@ from .serializers import CustomerSerializer class ManufacturerDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single Manufacturer + + post: + Update a Manufacturer + + delete: + Remove a Manufacturer + + """ queryset = Manufacturer.objects.all() serializer_class = ManufacturerSerializer @@ -20,6 +32,15 @@ class ManufacturerDetail(generics.RetrieveUpdateDestroyAPIView): class ManufacturerList(generics.ListCreateAPIView): + """ + + get: + Return a list of all Manufacturers + + post: + Create a new Manufacturer + + """ queryset = Manufacturer.objects.all() serializer_class = ManufacturerSerializer @@ -27,6 +48,18 @@ class ManufacturerList(generics.ListCreateAPIView): class CustomerDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single Customer + + post: + Update a Customer + + delete: + Remove a Customer + + """ queryset = Customer.objects.all() serializer_class = CustomerSerializer @@ -34,6 +67,15 @@ class CustomerDetail(generics.RetrieveUpdateDestroyAPIView): class CustomerList(generics.ListCreateAPIView): + """ + + get: + Return a list of all Cutstomers + + post: + Create a new Customer + + """ queryset = Customer.objects.all() serializer_class = CustomerSerializer @@ -41,6 +83,18 @@ class CustomerList(generics.ListCreateAPIView): class SupplierDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single Supplier + + post: + Update a supplier + + delete: + Remove a supplier + + """ queryset = Supplier.objects.all() serializer_class = SupplierSerializer @@ -48,6 +102,15 @@ class SupplierDetail(generics.RetrieveUpdateDestroyAPIView): class SupplierList(generics.ListCreateAPIView): + """ + + get: + Return a list of all Suppliers + + post: + Create a new Supplier + + """ queryset = Supplier.objects.all() serializer_class = SupplierSerializer @@ -55,6 +118,18 @@ class SupplierList(generics.ListCreateAPIView): class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single SupplierPart + + post: + Update a SupplierPart + + delete: + Remove a SupplierPart + + """ queryset = SupplierPart.objects.all() serializer_class = SupplierPartSerializer @@ -75,6 +150,16 @@ class SupplierPartFilter(FilterSet): class SupplierPartList(generics.ListCreateAPIView): + """ + + get: + List all SupplierParts + (with optional query filters) + + post: + Create a new SupplierPart + + """ queryset = SupplierPart.objects.all() serializer_class = SupplierPartSerializer @@ -85,6 +170,18 @@ class SupplierPartList(generics.ListCreateAPIView): class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single SupplierPriceBreak + + post: + Update a SupplierPriceBreak + + delete: + Remove a SupplierPriceBreak + + """ queryset = SupplierPriceBreak.objects.all() serializer_class = SupplierPriceBreakSerializer @@ -101,6 +198,16 @@ class PriceBreakFilter(FilterSet): class SupplierPriceBreakList(generics.ListCreateAPIView): + """ + + get: + Return a list of all SupplierPriceBreaks + (with optional query filters) + + post: + Create a new SupplierPriceBreak + + """ queryset = SupplierPriceBreak.objects.all() serializer_class = SupplierPriceBreakSerializer From a139a00843b49ffcf5152fda6ec9b5418cf13bd5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 16 Apr 2017 00:13:22 +1000 Subject: [PATCH 6/8] Updated track API --- InvenTree/InvenTree/urls.py | 5 +- InvenTree/track/admin.py | 2 +- InvenTree/track/models.py | 30 ---------- InvenTree/track/serializers.py | 1 - InvenTree/track/urls.py | 5 +- InvenTree/track/views.py | 102 +++++++++++++++++++++------------ 6 files changed, 71 insertions(+), 74 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 26ef11466a..9e71cb3e86 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -7,7 +7,7 @@ from part.urls import part_urls, part_cat_urls, part_param_urls, part_param_temp from stock.urls import stock_urls, stock_loc_urls from project.urls import prj_urls, prj_part_urls, prj_cat_urls from supplier.urls import cust_urls, manu_urls, supplier_part_urls, price_break_urls, supplier_urls -import supplier +from track.urls import unique_urls, part_track_urls admin.site.site_header = "InvenTree Admin" @@ -31,7 +31,8 @@ apipatterns = [ url(r'^customer/', include(cust_urls)), # Tracking URLs - url(r'^track/', include('track.urls')), + url(r'^track/', include(part_track_urls)), + url(r'^unique-part/', include(unique_urls)), # Project URLs url(r'^project/', include(prj_urls)), diff --git a/InvenTree/track/admin.py b/InvenTree/track/admin.py index 573e458878..a2908c2e7c 100644 --- a/InvenTree/track/admin.py +++ b/InvenTree/track/admin.py @@ -4,7 +4,7 @@ from .models import UniquePart class UniquePartAdmin(admin.ModelAdmin): - list_display = ('part', 'revision', 'serial', 'status', 'creation_date') + list_display = ('part', 'serial', 'status', 'creation_date') admin.site.register(UniquePart, UniquePartAdmin) diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index 1bc189ae7c..da2a912df7 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -8,29 +8,6 @@ from supplier.models import Customer from part.models import Part, PartRevision -class UniquePartManager(models.Manager): - """ Ensures UniqueParts are correctly handled - """ - - def create(self, *args, **kwargs): - - part_id = kwargs['part'] - sn = kwargs.get('serial', None) - - if not sn: - raise ValidationError(_("Serial number must be supplied")) - - if not isinstance(sn, int): - raise ValidationError(_("Serial number must be integer")) - - # Does a part already exists with this serial number? - parts = self.filter(part=part_id, serial=sn) - if len(parts) > 0: - raise ValidationError(_("Matching part and serial number found!")) - - return super(UniquePartManager, self).create(*args, **kwargs) - - class UniquePart(models.Model): """ A unique instance of a Part object. Used for tracking parts based on serial numbers, @@ -41,15 +18,8 @@ class UniquePart(models.Model): # Cannot have multiple parts with same serial number unique_together = ('part', 'serial') - objects = UniquePartManager() - part = models.ForeignKey(Part, on_delete=models.CASCADE) - revision = models.ForeignKey(PartRevision, - on_delete=models.CASCADE, - blank=True, - null=True) - creation_date = models.DateField(auto_now_add=True, editable=False) serial = models.IntegerField() diff --git a/InvenTree/track/serializers.py b/InvenTree/track/serializers.py index 3bd84aa0e7..c2c097c28d 100644 --- a/InvenTree/track/serializers.py +++ b/InvenTree/track/serializers.py @@ -11,7 +11,6 @@ class UniquePartSerializer(serializers.HyperlinkedModelSerializer): model = UniquePart fields = ['url', 'part', - 'revision', 'creation_date', 'serial', # 'createdBy', diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py index aaf3b539ce..788fe9b64c 100644 --- a/InvenTree/track/urls.py +++ b/InvenTree/track/urls.py @@ -2,15 +2,14 @@ from django.conf.urls import url, include from . import views -infopatterns = [ +part_track_urls = [ url(r'^(?P[0-9]+)/?$', views.PartTrackingDetail.as_view(), name='parttrackinginfo-detail'), url(r'^\?.*/?$', views.PartTrackingList.as_view()), url(r'^$', views.PartTrackingList.as_view()) ] -urlpatterns = [ - url(r'info/', include(infopatterns)), +unique_urls = [ # Detail for a single unique part url(r'^(?P[0-9]+)/?$', views.UniquePartDetail.as_view(), name='uniquepart-detail'), diff --git a/InvenTree/track/views.py b/InvenTree/track/views.py index 436e98344e..355d9b3449 100644 --- a/InvenTree/track/views.py +++ b/InvenTree/track/views.py @@ -1,4 +1,6 @@ import django_filters +from django_filters.rest_framework import FilterSet, DjangoFilterBackend +from django_filters import NumberFilter from rest_framework import generics, permissions @@ -7,69 +9,95 @@ from .serializers import UniquePartSerializer, PartTrackingInfoSerializer class UniquePartDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single UniquePart + + post: + Update a UniquePart + + delete: + Remove a UniquePart + + """ queryset = UniquePart.objects.all() serializer_class = UniquePartSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) -class UniquePartFilter(django_filters.rest_framework.FilterSet): +class UniquePartFilter(FilterSet): # Filter based on serial number - min_sn = django_filters.NumberFilter(name='serial', lookup_expr='gte') - max_sn = django_filters.NumberFilter(name='serial', lookup_expr='lte') + min_sn = NumberFilter(name='serial', lookup_expr='gte') + max_sn = NumberFilter(name='serial', lookup_expr='lte') + + sn = NumberFilter(name='serial', lookup_expr='exact') + part = NumberFilter(name='part', lookup_expr='exact') + customer = NumberFilter(name='customer', lookup_expr='exact') class Meta: model = UniquePart - fields = ['serial', ] + fields = ['serial', 'part', 'customer'] class UniquePartList(generics.ListCreateAPIView): + """ + get: + Return a list of all UniqueParts + (with optional query filter) + + post: + Create a new UniquePart + """ + + queryset = UniquePart.objects.all() serializer_class = UniquePartSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - filter_backends = (django_filters.rest_framework.DjangoFilterBackend,) + filter_backends = (DjangoFilterBackend,) filter_class = UniquePartFilter - def get_queryset(self): - parts = UniquePart.objects.all() - query = self.request.query_params - - # Filter by associated part - part_id = query.get('part', None) - if part_id: - parts = parts.filter(part=part_id) - - # Filter by serial number - sn = query.get('sn', None) - if sn: - parts = parts.filter(serial=sn) - - # Filter by customer - customer = query.get('customer', None) - if customer: - parts = parts.filter(customer=customer) - - return parts - class PartTrackingDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single PartTrackingInfo object + + post: + Update a PartTrackingInfo object + + delete: + Remove a PartTrackingInfo object + """ queryset = PartTrackingInfo.objects.all() serializer_class = PartTrackingInfoSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) -class PartTrackingList(generics.ListCreateAPIView): +class PartTrackingFilter(FilterSet): + part = NumberFilter(name='part', lookup_expr='exact') + class Meta: + model = PartTrackingInfo + fields = ['part'] + + +class PartTrackingList(generics.ListCreateAPIView): + """ + + get: + Return a list of all PartTrackingInfo objects + (with optional query filter) + + post: + Create a new PartTrackingInfo object + """ + + queryset = PartTrackingInfo.objects.all() serializer_class = PartTrackingInfoSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - - def get_queryset(self): - tracking = PartTrackingInfo.objects.all() - query = self.request.query_params - - part_id = query.get('part', None) - if part_id: - tracking = tracking.filter(part=part_id) - - return tracking + filter_backends = (DjangoFilterBackend,) + filter_class = PartTrackingFilter From 2e133b774472aebf43a12b1c9db1809f5c44cbca Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 16 Apr 2017 00:58:40 +1000 Subject: [PATCH 7/8] Start of exception handler --- InvenTree/InvenTree/settings.py | 4 ++++ InvenTree/InvenTree/utils.py | 17 +++++++++++++++++ InvenTree/part/serializers.py | 4 +++- InvenTree/track/models.py | 18 +++++++++++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 InvenTree/InvenTree/utils.py diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 975af8c20b..5cfdbcf924 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -79,6 +79,10 @@ TEMPLATES = [ }, ] +REST_FRAMEWORK = { + 'EXCEPTION_HANDLER': 'InvenTree.utils.api_exception_handler' +} + WSGI_APPLICATION = 'InvenTree.wsgi.application' diff --git a/InvenTree/InvenTree/utils.py b/InvenTree/InvenTree/utils.py new file mode 100644 index 0000000000..3edf222306 --- /dev/null +++ b/InvenTree/InvenTree/utils.py @@ -0,0 +1,17 @@ +from rest_framework.views import exception_handler + + +#class APIValidationError() + +def api_exception_handler(exc, context): + response = exception_handler(exc, context) + + #print(response) + + # Now add the HTTP status code to the response. + if response is not None: + + data = {'error': response.data} + response.data = data + + return response diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index bc38ca309d..48719e9cd5 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -29,7 +29,9 @@ class PartSerializer(serializers.HyperlinkedModelSerializer): 'IPN', 'description', 'category', - 'stock') + 'stock', + 'units', + 'trackable') class PartCategorySerializer(serializers.HyperlinkedModelSerializer): diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index da2a912df7..ac22092731 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -from django.core.exceptions import ValidationError +from rest_framework.exceptions import ValidationError from django.utils.translation import ugettext as _ from django.db import models # from django.contrib.auth.models import User @@ -8,12 +8,28 @@ from supplier.models import Customer from part.models import Part, PartRevision +class UniquePartManager(models.Manager): + + def create(self, *args, **kwargs): + + print(kwargs) + + part = kwargs.get('part', None) + + if not part.trackable: + raise ValidationError("Unique part cannot be created for a non-trackable part") + + return super(UniquePartManager, self).create(*args, **kwargs) + + class UniquePart(models.Model): """ A unique instance of a Part object. Used for tracking parts based on serial numbers, and tracking all events in the life of a part """ + objects = UniquePartManager() + class Meta: # Cannot have multiple parts with same serial number unique_together = ('part', 'serial') From 7e1fbf8a042c898adddbcc201d3336cffc37a12a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 16 Apr 2017 01:01:48 +1000 Subject: [PATCH 8/8] Style fixes --- InvenTree/InvenTree/utils.py | 4 ---- InvenTree/part/models.py | 17 ----------------- InvenTree/part/urls.py | 2 +- InvenTree/part/views.py | 1 - InvenTree/project/urls.py | 2 +- InvenTree/stock/views.py | 1 - InvenTree/track/models.py | 2 +- InvenTree/track/urls.py | 2 +- InvenTree/track/views.py | 1 - 9 files changed, 4 insertions(+), 28 deletions(-) diff --git a/InvenTree/InvenTree/utils.py b/InvenTree/InvenTree/utils.py index 3edf222306..dc28da81a0 100644 --- a/InvenTree/InvenTree/utils.py +++ b/InvenTree/InvenTree/utils.py @@ -1,13 +1,9 @@ from rest_framework.views import exception_handler -#class APIValidationError() - def api_exception_handler(exc, context): response = exception_handler(exc, context) - #print(response) - # Now add the HTTP status code to the response. if response is not None: diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 4edbf6f7f0..f5daf0915a 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -187,20 +187,3 @@ class PartParameter(models.Model): class Meta: verbose_name = "Part Parameter" verbose_name_plural = "Part Parameters" - - -class PartRevision(models.Model): - """ A PartRevision represents a change-notification to a Part - A Part may go through several revisions in its lifetime, - which should be tracked. - UniqueParts can have a single associated PartRevision - """ - - part = models.ForeignKey(Part, on_delete=models.CASCADE) - - name = models.CharField(max_length=100) - description = models.CharField(max_length=500) - revision_date = models.DateField(auto_now_add=True) - - def __str__(self): - return self.name diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 057ea18233..1ed26cac2b 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url, include +from django.conf.urls import url from . import views diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 9bb416917c..40c7134860 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1,4 +1,3 @@ -import django_filters from django_filters.rest_framework import FilterSet, DjangoFilterBackend from django_filters import NumberFilter diff --git a/InvenTree/project/urls.py b/InvenTree/project/urls.py index 181f8c3b0d..ad150a00ca 100644 --- a/InvenTree/project/urls.py +++ b/InvenTree/project/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url, include +from django.conf.urls import url from . import views diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index ad7742fd21..6c19410fc4 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -1,4 +1,3 @@ -import django_filters from django_filters.rest_framework import FilterSet, DjangoFilterBackend from django_filters import NumberFilter diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index ac22092731..445e6d30fc 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -5,7 +5,7 @@ from django.db import models # from django.contrib.auth.models import User from supplier.models import Customer -from part.models import Part, PartRevision +from part.models import Part class UniquePartManager(models.Manager): diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py index 788fe9b64c..cae9f9d5c2 100644 --- a/InvenTree/track/urls.py +++ b/InvenTree/track/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url, include +from django.conf.urls import url from . import views diff --git a/InvenTree/track/views.py b/InvenTree/track/views.py index 355d9b3449..f45205993d 100644 --- a/InvenTree/track/views.py +++ b/InvenTree/track/views.py @@ -1,4 +1,3 @@ -import django_filters from django_filters.rest_framework import FilterSet, DjangoFilterBackend from django_filters import NumberFilter