From 858a7fe089e3baff986614985f230ba740dc2a73 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 21:44:01 +1000 Subject: [PATCH 01/12] Tweaking URL patterns --- InvenTree/InvenTree/urls.py | 14 +++++++------- InvenTree/part/urls.py | 26 +++++++++++++++----------- InvenTree/project/urls.py | 28 +++++++++++----------------- InvenTree/stock/serializers.py | 13 +------------ InvenTree/stock/urls.py | 13 ++++++++----- InvenTree/stock/views.py | 6 +++--- InvenTree/track/urls.py | 12 +++++++----- 7 files changed, 52 insertions(+), 60 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 5ab4076fad..d750b6388c 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -17,13 +17,13 @@ def Inventree404(self): urlpatterns = [ - url(r'^stock/?', include('stock.urls')), - url(r'^part/?', include('part.urls')), - url(r'^supplier/?', include('supplier.urls')), - url(r'^track/?', include('track.urls')), - url(r'^project/?', include('project.urls')), - url(r'^admin/?', admin.site.urls), - url(r'^auth/?', include('rest_framework.urls', namespace='rest_framework')), + url(r'^stock/', include('stock.urls')), + url(r'^part/', include('part.urls')), + url(r'^supplier/', include('supplier.urls')), + url(r'^track/', include('track.urls')), + url(r'^project/', include('project.urls')), + url(r'^admin/', admin.site.urls), + url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), # Any other URL url(r'', Inventree404) diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index bb7595a271..e2481fab36 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -10,26 +10,29 @@ from . import views categorypatterns = [ # Part category detail - url(r'^(?P<pk>[0-9]+)/?$', views.PartCategoryDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.PartCategoryDetail.as_view(), name='part-category-detail'), # List of top-level categories - url(r'^\?*[^/]*/?$', views.PartCategoryList.as_view()) + url(r'^\?*.*/?$', views.PartCategoryList.as_view(), name='part-category-list'), + url(r'^$', views.PartCategoryList.as_view(), name='part-category-list') ] partparampatterns = [ # Detail of a single part parameter - url(r'^(?P<pk>[0-9]+)/?$', views.PartParamDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.PartParamDetail.as_view(), name='part-parameter-detail'), # Parameters associated with a particular part - url(r'^\?*[^/]*/?$', views.PartParamList.as_view()), + url(r'^\?.*/?$', views.PartParamList.as_view(), name='part-parameter-list'), + url(r'^$', views.PartParamList.as_view(), name='part-parameter-list'), ] parttemplatepatterns = [ # Detail of a single part field template - url(r'^(?P<pk>[0-9]+)/?$', views.PartTemplateDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.PartTemplateDetail.as_view(), name='part-template-detail'), # List all part field templates - url(r'^$', views.PartTemplateList.as_view()) + url(r'^\?.*/?$', views.PartTemplateList.as_view(), name='part-template-list'), + url(r'^$', views.PartTemplateList.as_view(), name='part-template-list') ] """ Top-level URL patterns for the Part app: @@ -40,17 +43,18 @@ parttemplatepatterns = [ """ urlpatterns = [ # Individual part - url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view(), name='part-detail'), # Part categories - url(r'^category/?', include(categorypatterns)), + url(r'^category/', include(categorypatterns)), # Part parameters - url(r'^parameters/?', include(partparampatterns)), + url(r'^parameters/', include(partparampatterns)), # Part templates - url(r'^templates/?', include(parttemplatepatterns)), + url(r'^templates/', include(parttemplatepatterns)), # List parts with optional filters - url(r'^\?*[^/]*/?$', views.PartList.as_view()), + url(r'^\?.*/?$', views.PartList.as_view(), name='part-list'), + url(r'^$', views.PartList.as_view(), name='part-list'), ] diff --git a/InvenTree/project/urls.py b/InvenTree/project/urls.py index d0f50bac56..ffd14bc6e3 100644 --- a/InvenTree/project/urls.py +++ b/InvenTree/project/urls.py @@ -2,41 +2,35 @@ from django.conf.urls import url, include from . import views -""" URL patterns associated with project -/project/<pk> -> Detail view of single project -/project/<pk>/parts -> Detail all parts associated with project -""" -projectdetailpatterns = [ - # Single project detail - url(r'^$', views.ProjectDetail.as_view()), -] - projectpartpatterns = [ # Detail of a single project part - url(r'^(?P<pk>[0-9]+)/?$', views.ProjectPartDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.ProjectPartDetail.as_view(), name='project-part-detail'), # List project parts, with optional filters - url(r'^\?*[^/]*/?$', views.ProjectPartsList.as_view()), + url(r'^\?.*/?$', views.ProjectPartsList.as_view(), name='project-part-list'), + url(r'^$', views.ProjectPartsList.as_view(), name='project-part-list'), ] projectcategorypatterns = [ # Detail of a single project category - url(r'^(?P<pk>[0-9]+)/?$', views.ProjectCategoryDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.ProjectCategoryDetail.as_view(), name='project-category-detail'), # List of project categories, with filters - url(r'^\?*[^/]*/?$', views.ProjectCategoryList.as_view()), + url(r'^\?.*/?$', views.ProjectCategoryList.as_view(), name='project-category-list'), + url(r'^$', views.ProjectCategoryList.as_view(), name='project-category-list'), ] urlpatterns = [ # Individual project URL - url(r'^(?P<pk>[0-9]+)/?', include(projectdetailpatterns)), + url(r'^(?P<pk>[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(), name='project-list'), + url(r'^$', views.ProjectList.as_view(), name='project-list'), # Project parts - url(r'^parts/?', include(projectpartpatterns)), + url(r'^parts/', include(projectpartpatterns)), # Project categories - url(r'^category/?', include(projectcategorypatterns)), + url(r'^category/', include(projectcategorypatterns)), ] diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index a542edf150..6a02db0439 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -20,18 +20,7 @@ class StockItemSerializer(serializers.ModelSerializer): 'expected_arrival') -class LocationBriefSerializer(serializers.ModelSerializer): - """ Brief information about a stock location - """ - - class Meta: - model = StockLocation - fields = ('pk', - 'name', - 'description') - - -class LocationDetailSerializer(serializers.ModelSerializer): +class LocationSerializer(serializers.ModelSerializer): """ Detailed information about a stock location """ diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index b3496e5768..ad1e2ac28a 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -3,18 +3,21 @@ from django.conf.urls import url, include from . import views locpatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.LocationDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.LocationDetail.as_view(), name='location-detail'), - url(r'^\?*[^/]*/?$', views.LocationList.as_view()) + url(r'^\?.*/?$', views.LocationList.as_view(), name='location-list'), + + url(r'^$', views.LocationList.as_view(), name='location-list') ] urlpatterns = [ # Stock location urls - url(r'^location/?', include(locpatterns)), + url(r'^location/', include(locpatterns)), # Detail for a single stock item - url(r'^(?P<pk>[0-9]+)$', views.StockDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.StockDetail.as_view(), name='stock-detail'), # List all stock items, with optional filters - url(r'^\?*[^/]*/?$', views.StockList.as_view()), + url(r'^\?.*/?$', views.StockList.as_view(), name='stock-list'), + url(r'^$', views.StockList.as_view(), name='stock-list'), ] diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 60f3a09c82..fa32ce234d 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -3,7 +3,7 @@ import django_filters from InvenTree.models import FilterChildren from .models import StockLocation, StockItem -from .serializers import StockItemSerializer, LocationDetailSerializer +from .serializers import StockItemSerializer, LocationSerializer class StockDetail(generics.RetrieveUpdateDestroyAPIView): @@ -51,7 +51,7 @@ class LocationDetail(generics.RetrieveUpdateDestroyAPIView): """ queryset = StockLocation.objects.all() - serializer_class = LocationDetailSerializer + serializer_class = LocationSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) @@ -69,5 +69,5 @@ class LocationList(generics.ListCreateAPIView): return locations - serializer_class = LocationDetailSerializer + serializer_class = LocationSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py index 447c1c3d38..35410a653a 100644 --- a/InvenTree/track/urls.py +++ b/InvenTree/track/urls.py @@ -3,17 +3,19 @@ from django.conf.urls import url, include from . import views infopatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.PartTrackingDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.PartTrackingDetail.as_view(), name='tracking-detail'), - url(r'^\?*[^/]*/?$', views.PartTrackingList.as_view()) + url(r'^\?.*/?$', views.PartTrackingList.as_view(), name='tracking-list'), + url(r'^$', views.PartTrackingList.as_view(), name='tracking-list') ] urlpatterns = [ - url(r'info/?', include(infopatterns)), + url(r'info/', include(infopatterns)), # Detail for a single unique part - url(r'^(?P<pk>[0-9]+)$', views.UniquePartDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.UniquePartDetail.as_view(), name='unique-detail'), # List all unique parts, with optional filters - url(r'^\?*[^/]*/?$', views.UniquePartList.as_view()), + url(r'^\?.*/?$', views.UniquePartList.as_view(), name='unique-list'), + url(r'^$', views.UniquePartList.as_view(), name='unique-list'), ] From 2c628c8ab40111fc8fe6216a1dc6986d84dd5b02 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 21:59:52 +1000 Subject: [PATCH 02/12] Prepend /api/ to API URL --- InvenTree/InvenTree/urls.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d750b6388c..7b94439c97 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -16,15 +16,21 @@ def Inventree404(self): return Response(content, status=status.HTTP_404_NOT_FOUND) -urlpatterns = [ +apipatterns = [ url(r'^stock/', include('stock.urls')), url(r'^part/', include('part.urls')), url(r'^supplier/', include('supplier.urls')), url(r'^track/', include('track.urls')), url(r'^project/', include('project.urls')), - url(r'^admin/', admin.site.urls), - url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), # Any other URL url(r'', Inventree404) ] + +urlpatterns = [ + # API URL + url(r'^api/', include(apipatterns)), + + url(r'^admin/', admin.site.urls), + url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), +] From 41eb427c02c2fc80da0c2be9bcc5844c6b3351af Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 22:05:29 +1000 Subject: [PATCH 03/12] Fix for FilterChildren function --- InvenTree/InvenTree/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index e8430694d0..be5de69be1 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -184,7 +184,7 @@ def FilterChildren(queryset, parent): if not parent: return queryset - elif isinstance(parent, str) and parent.lower() in ['none', 'false', 'null', 'top', '0']: + elif str(parent).lower() in ['none', 'false', 'null', 'top', '0']: return queryset.filter(parent=None) else: try: From f85489bc0ec402dcfa0d061688937dabb982dd60 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 22:40:59 +1000 Subject: [PATCH 04/12] Supplier API is hyperlinked --- InvenTree/InvenTree/models.py | 2 +- InvenTree/supplier/admin.py | 2 +- InvenTree/supplier/serializers.py | 12 +++++++----- InvenTree/supplier/urls.py | 15 +++++++++------ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index be5de69be1..ca602f400c 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -12,7 +12,7 @@ class Company(models.Model): abstract = True name = models.CharField(max_length=100) - URL = models.URLField(blank=True) + website = models.URLField(blank=True) address = models.CharField(max_length=200, blank=True) phone = models.CharField(max_length=50, diff --git a/InvenTree/supplier/admin.py b/InvenTree/supplier/admin.py index cf19edd2b0..4fe7914019 100644 --- a/InvenTree/supplier/admin.py +++ b/InvenTree/supplier/admin.py @@ -4,7 +4,7 @@ from .models import Supplier, SupplierPart, Customer, Manufacturer class CompanyAdmin(admin.ModelAdmin): - list_display = ('name', 'URL', 'contact') + list_display = ('name', 'website', 'contact') admin.site.register(Customer, CompanyAdmin) diff --git a/InvenTree/supplier/serializers.py b/InvenTree/supplier/serializers.py index 3635a93a72..9b80d33445 100644 --- a/InvenTree/supplier/serializers.py +++ b/InvenTree/supplier/serializers.py @@ -3,7 +3,7 @@ from rest_framework import serializers from .models import Supplier, SupplierPart, SupplierPriceBreak -class SupplierSerializer(serializers.ModelSerializer): +class SupplierSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Supplier @@ -12,11 +12,13 @@ class SupplierSerializer(serializers.ModelSerializer): class SupplierPartSerializer(serializers.ModelSerializer): - price_breaks = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + price_breaks = serializers.HyperlinkedRelatedField(many=True, + read_only=True, + view_name='price_break-detail') class Meta: model = SupplierPart - fields = ['pk', + fields = ['url', 'part', 'supplier', 'SKU', @@ -32,11 +34,11 @@ class SupplierPartSerializer(serializers.ModelSerializer): 'lead_time'] -class SupplierPriceBreakSerializer(serializers.ModelSerializer): +class SupplierPriceBreakSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = SupplierPriceBreak - fields = ['pk', + fields = ['url', 'part', 'quantity', 'cost'] diff --git a/InvenTree/supplier/urls.py b/InvenTree/supplier/urls.py index 10fbe8f230..28eb7c1699 100644 --- a/InvenTree/supplier/urls.py +++ b/InvenTree/supplier/urls.py @@ -3,15 +3,17 @@ from django.conf.urls import url, include from . import views partpatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPartDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPartDetail.as_view(), name='supplier-part-detail'), - url(r'^\?*[^/]*/?$', views.SupplierPartList.as_view()) + url(r'^\?.*/?$', views.SupplierPartList.as_view()), + url(r'^$', views.SupplierPartList.as_view()) ] pricepatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view(), name='price-break-detail'), - url(r'^\?*[^/]*/?$', views.SupplierPriceBreakList.as_view()) + url(r'^\?.*/?$', views.SupplierPriceBreakList.as_view()), + url(r'^$', views.SupplierPriceBreakList.as_view()) ] urlpatterns = [ @@ -23,8 +25,9 @@ urlpatterns = [ url(r'price/?', include(pricepatterns)), # Display details of a supplier - url(r'^(?P<pk>[0-9]+)/?$', views.SupplierDetail.as_view()), + url(r'^(?P<pk>[0-9]+)/?$', views.SupplierDetail.as_view(), name='supplier-detail'), # List suppliers - url(r'^\?*[^/]*/?$', views.SupplierList.as_view()) + url(r'^\?.*/?$', views.SupplierList.as_view()), + url(r'^$', views.SupplierList.as_view()) ] From e641a913f25a5f8d35c0b38b5a3040a0a5dcdc6b Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 23:29:58 +1000 Subject: [PATCH 05/12] Supplier API now hyperlinked --- InvenTree/InvenTree/urls.py | 14 ++--------- InvenTree/supplier/customer_urls.py | 12 ++++++++++ InvenTree/supplier/manufacturer_urls.py | 12 ++++++++++ InvenTree/supplier/serializers.py | 30 +++++++++++++++++++++++- InvenTree/supplier/urls.py | 10 ++++---- InvenTree/supplier/views.py | 31 +++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 InvenTree/supplier/customer_urls.py create mode 100644 InvenTree/supplier/manufacturer_urls.py diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 7b94439c97..2b5528cf5e 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -7,24 +7,14 @@ from rest_framework.decorators import api_view admin.site.site_header = "InvenTree Admin" - -@api_view() -def Inventree404(self): - """ Supplied URL is invalid - """ - content = {'detail': 'Malformed API URL'} - return Response(content, status=status.HTTP_404_NOT_FOUND) - - apipatterns = [ url(r'^stock/', include('stock.urls')), url(r'^part/', include('part.urls')), url(r'^supplier/', include('supplier.urls')), + 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')), - - # Any other URL - url(r'', Inventree404) ] urlpatterns = [ diff --git a/InvenTree/supplier/customer_urls.py b/InvenTree/supplier/customer_urls.py new file mode 100644 index 0000000000..870bba1a31 --- /dev/null +++ b/InvenTree/supplier/customer_urls.py @@ -0,0 +1,12 @@ +from django.conf.urls import url, include + +from . import views + +urlpatterns = [ + # Customer detail + url(r'^(?P<pk>[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 new file mode 100644 index 0000000000..56a74c8106 --- /dev/null +++ b/InvenTree/supplier/manufacturer_urls.py @@ -0,0 +1,12 @@ +from django.conf.urls import url, include + +from . import views + +urlpatterns = [ + # Manufacturer detail + url(r'^(?P<pk>[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/serializers.py b/InvenTree/supplier/serializers.py index 9b80d33445..517ea940d2 100644 --- a/InvenTree/supplier/serializers.py +++ b/InvenTree/supplier/serializers.py @@ -1,6 +1,10 @@ from rest_framework import serializers +from part.models import Part + from .models import Supplier, SupplierPart, SupplierPriceBreak +from .models import Manufacturer +from .models import Customer class SupplierSerializer(serializers.HyperlinkedModelSerializer): @@ -10,14 +14,38 @@ class SupplierSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' +class ManufacturerSerializer(serializers.HyperlinkedModelSerializer): + + class Meta: + model = Manufacturer + fields = '__all__' + + +class CustomerSerializer(serializers.HyperlinkedModelSerializer): + + class Meta: + model = Customer + fields = '__all__' + + class SupplierPartSerializer(serializers.ModelSerializer): price_breaks = serializers.HyperlinkedRelatedField(many=True, read_only=True, - view_name='price_break-detail') + view_name='supplierpricebreak-detail') + + part = serializers.HyperlinkedRelatedField(view_name='part-detail', + queryset = Part.objects.all()) + + supplier = serializers.HyperlinkedRelatedField(view_name='supplier-detail', + queryset = Supplier.objects.all()) + + manufacturer = serializers.HyperlinkedRelatedField(view_name='manufacturer-detail', + queryset = Manufacturer.objects.all()) class Meta: model = SupplierPart + #extra_kwargs = {'url': {'view_name': 'supplier-part-detail'}} fields = ['url', 'part', 'supplier', diff --git a/InvenTree/supplier/urls.py b/InvenTree/supplier/urls.py index 28eb7c1699..5efac75d31 100644 --- a/InvenTree/supplier/urls.py +++ b/InvenTree/supplier/urls.py @@ -3,14 +3,14 @@ from django.conf.urls import url, include from . import views partpatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPartDetail.as_view(), name='supplier-part-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPartDetail.as_view(), name='supplierpart-detail'), url(r'^\?.*/?$', views.SupplierPartList.as_view()), url(r'^$', views.SupplierPartList.as_view()) ] pricepatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view(), name='price-break-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view(), name='supplierpricebreak-detail'), url(r'^\?.*/?$', views.SupplierPriceBreakList.as_view()), url(r'^$', views.SupplierPriceBreakList.as_view()) @@ -19,13 +19,13 @@ pricepatterns = [ urlpatterns = [ # Supplier part information - url(r'part/?', include(partpatterns)), + url(r'part/', include(partpatterns)), # Supplier price information - url(r'price/?', include(pricepatterns)), + url(r'price/', include(pricepatterns)), # Display details of a supplier - url(r'^(?P<pk>[0-9]+)/?$', views.SupplierDetail.as_view(), name='supplier-detail'), + url(r'^(?P<pk>[0-9]+)/$', views.SupplierDetail.as_view(), name='supplier-detail'), # List suppliers url(r'^\?.*/?$', views.SupplierList.as_view()), diff --git a/InvenTree/supplier/views.py b/InvenTree/supplier/views.py index e6fa2e628b..a12a7f5124 100644 --- a/InvenTree/supplier/views.py +++ b/InvenTree/supplier/views.py @@ -1,9 +1,40 @@ from rest_framework import generics, permissions from .models import Supplier, SupplierPart, SupplierPriceBreak +from .models import Manufacturer, Customer from .serializers import SupplierSerializer from .serializers import SupplierPartSerializer from .serializers import SupplierPriceBreakSerializer +from .serializers import ManufacturerSerializer +from .serializers import CustomerSerializer + + +class ManufacturerDetail(generics.RetrieveUpdateDestroyAPIView): + + queryset = Manufacturer.objects.all() + serializer_class = ManufacturerSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + +class ManufacturerList(generics.ListCreateAPIView): + + queryset = Manufacturer.objects.all() + serializer_class = ManufacturerSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + +class CustomerDetail(generics.RetrieveUpdateDestroyAPIView): + + queryset = Customer.objects.all() + serializer_class = CustomerSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + +class CustomerList(generics.ListCreateAPIView): + + queryset = Customer.objects.all() + serializer_class = CustomerSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) class SupplierDetail(generics.RetrieveUpdateDestroyAPIView): From 9e3fa00fdd4f66f7c2d96b5662236271944a90a4 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 23:30:08 +1000 Subject: [PATCH 06/12] Fixed makefile Added "makemigrations" for each app --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b9047d0085..4e809701e8 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,11 @@ test: python InvenTree/manage.py test --noinput migrate: - python InvenTree/manage.py makemigrations + python InvenTree/manage.py makemigrations part + python InvenTree/manage.py makemigrations project + python InvenTree/manage.py makemigrations stock + python InvenTree/manage.py makemigrations supplier + python InvenTree/manage.py makemigrations track python InvenTree/manage.py migrate --run-syncdb python InvenTree/manage.py check From 7c5261bc4a6c4caf6f00b78615ef32448f9885f2 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 23:44:24 +1000 Subject: [PATCH 07/12] Stock API now hyperlinkd --- InvenTree/InvenTree/models.py | 1 + InvenTree/stock/serializers.py | 8 ++++---- InvenTree/stock/urls.py | 12 ++++++------ InvenTree/supplier/models.py | 3 +++ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index ca602f400c..c6aec418ad 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -35,6 +35,7 @@ class InvenTreeTree(models.Model): class Meta: abstract = True + unique_together = ('name', 'parent') name = models.CharField(max_length=100) description = models.CharField(max_length=250, blank=True) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 6a02db0439..5c69329f82 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -3,13 +3,13 @@ from rest_framework import serializers from .models import StockItem, StockLocation -class StockItemSerializer(serializers.ModelSerializer): +class StockItemSerializer(serializers.HyperlinkedModelSerializer): """ Serializer for a StockItem """ class Meta: model = StockItem - fields = ('pk', + fields = ('url', 'part', 'location', 'quantity', @@ -20,13 +20,13 @@ class StockItemSerializer(serializers.ModelSerializer): 'expected_arrival') -class LocationSerializer(serializers.ModelSerializer): +class LocationSerializer(serializers.HyperlinkedModelSerializer): """ Detailed information about a stock location """ class Meta: model = StockLocation - fields = ('pk', + fields = ('url', 'name', 'description', 'parent', diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index ad1e2ac28a..bb026d2a08 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -3,11 +3,11 @@ from django.conf.urls import url, include from . import views locpatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.LocationDetail.as_view(), name='location-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.LocationDetail.as_view(), name='stocklocation-detail'), - url(r'^\?.*/?$', views.LocationList.as_view(), name='location-list'), + url(r'^\?.*/?$', views.LocationList.as_view()), - url(r'^$', views.LocationList.as_view(), name='location-list') + url(r'^$', views.LocationList.as_view()) ] urlpatterns = [ @@ -15,9 +15,9 @@ urlpatterns = [ url(r'^location/', include(locpatterns)), # Detail for a single stock item - url(r'^(?P<pk>[0-9]+)/?$', views.StockDetail.as_view(), name='stock-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.StockDetail.as_view(), name='stockitem-detail'), # List all stock items, with optional filters - url(r'^\?.*/?$', views.StockList.as_view(), name='stock-list'), - url(r'^$', views.StockList.as_view(), name='stock-list'), + url(r'^\?.*/?$', views.StockList.as_view()), + url(r'^$', views.StockList.as_view()), ] diff --git a/InvenTree/supplier/models.py b/InvenTree/supplier/models.py index bd5d20dc86..894621ea97 100644 --- a/InvenTree/supplier/models.py +++ b/InvenTree/supplier/models.py @@ -31,6 +31,9 @@ class SupplierPart(models.Model): - A Part may be available from multiple suppliers """ + class Meta: + unique_together = ('part', 'supplier', 'SKU') + part = models.ForeignKey(Part, null=True, blank=True, on_delete=models.CASCADE) supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE) SKU = models.CharField(max_length=100) From a1db0c90e4e4347716b187b39176863055bf5142 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Fri, 14 Apr 2017 23:53:27 +1000 Subject: [PATCH 08/12] Tracking API is hyperlinked --- InvenTree/supplier/serializers.py | 1 - InvenTree/track/models.py | 17 +++++------------ InvenTree/track/serializers.py | 8 ++++---- InvenTree/track/urls.py | 12 ++++++------ 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/InvenTree/supplier/serializers.py b/InvenTree/supplier/serializers.py index 517ea940d2..88c23a573c 100644 --- a/InvenTree/supplier/serializers.py +++ b/InvenTree/supplier/serializers.py @@ -45,7 +45,6 @@ class SupplierPartSerializer(serializers.ModelSerializer): class Meta: model = SupplierPart - #extra_kwargs = {'url': {'view_name': 'supplier-part-detail'}} fields = ['url', 'part', 'supplier', diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index d50b6d7578..d0e8a5aa46 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -37,6 +37,10 @@ class UniquePart(models.Model): and tracking all events in the life of a part """ + class Meta: + # Cannot have multiple parts with same serial number + unique_together = ('part', 'serial') + objects = UniquePartManager() part = models.ForeignKey(Part, on_delete=models.CASCADE) @@ -50,7 +54,7 @@ class UniquePart(models.Model): editable=False) serial = models.IntegerField() - createdBy = models.ForeignKey(User) + # createdBy = models.ForeignKey(User) customer = models.ForeignKey(Customer, blank=True, null=True) @@ -76,17 +80,6 @@ class UniquePart(models.Model): def __str__(self): return self.part.name - def save(self, *args, **kwargs): - - # Disallow saving a serial number that already exists - matches = UniquePart.objects.filter(serial=self.serial, part=self.part) - matches = matches.filter(~models.Q(id=self.id)) - - if len(matches) > 0: - raise ValidationError(_("Matching serial number already exists")) - - super(UniquePart, self).save(*args, **kwargs) - class PartTrackingInfo(models.Model): """ Single data-point in the life of a UniquePart diff --git a/InvenTree/track/serializers.py b/InvenTree/track/serializers.py index 6e958acbf3..3bd84aa0e7 100644 --- a/InvenTree/track/serializers.py +++ b/InvenTree/track/serializers.py @@ -3,24 +3,24 @@ from rest_framework import serializers from .models import UniquePart, PartTrackingInfo -class UniquePartSerializer(serializers.ModelSerializer): +class UniquePartSerializer(serializers.HyperlinkedModelSerializer): tracking_info = serializers.PrimaryKeyRelatedField(many=True, read_only=True) class Meta: model = UniquePart - fields = ['pk', + fields = ['url', 'part', 'revision', 'creation_date', 'serial', - 'createdBy', + # 'createdBy', 'customer', 'status', 'tracking_info'] -class PartTrackingInfoSerializer(serializers.ModelSerializer): +class PartTrackingInfoSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = PartTrackingInfo diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py index 35410a653a..aaf3b539ce 100644 --- a/InvenTree/track/urls.py +++ b/InvenTree/track/urls.py @@ -3,19 +3,19 @@ from django.conf.urls import url, include from . import views infopatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.PartTrackingDetail.as_view(), name='tracking-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.PartTrackingDetail.as_view(), name='parttrackinginfo-detail'), - url(r'^\?.*/?$', views.PartTrackingList.as_view(), name='tracking-list'), - url(r'^$', views.PartTrackingList.as_view(), name='tracking-list') + url(r'^\?.*/?$', views.PartTrackingList.as_view()), + url(r'^$', views.PartTrackingList.as_view()) ] urlpatterns = [ url(r'info/', include(infopatterns)), # Detail for a single unique part - url(r'^(?P<pk>[0-9]+)/?$', views.UniquePartDetail.as_view(), name='unique-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.UniquePartDetail.as_view(), name='uniquepart-detail'), # List all unique parts, with optional filters - url(r'^\?.*/?$', views.UniquePartList.as_view(), name='unique-list'), - url(r'^$', views.UniquePartList.as_view(), name='unique-list'), + url(r'^\?.*/?$', views.UniquePartList.as_view()), + url(r'^$', views.UniquePartList.as_view()), ] From 9e287d85d84aa1dee501a42613d955d68c80fc24 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Sat, 15 Apr 2017 00:00:55 +1000 Subject: [PATCH 09/12] Project API is hyperlinked --- InvenTree/project/serializers.py | 22 ++++++++++++---------- InvenTree/project/urls.py | 16 ++++++++-------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/InvenTree/project/serializers.py b/InvenTree/project/serializers.py index 5e3904cb3e..91bc79e619 100644 --- a/InvenTree/project/serializers.py +++ b/InvenTree/project/serializers.py @@ -3,38 +3,40 @@ from rest_framework import serializers from .models import ProjectCategory, Project, ProjectPart -class ProjectPartSerializer(serializers.ModelSerializer): +class ProjectPartSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = ProjectPart - fields = ('pk', + fields = ('url', 'part', 'project', 'quantity', 'output') -class ProjectSerializer(serializers.ModelSerializer): - """ Serializer for displaying brief overview of a project - """ +class ProjectSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Project - fields = ('pk', + fields = ('url', 'name', 'description', 'category') -class ProjectCategorySerializer(serializers.ModelSerializer): +class ProjectCategorySerializer(serializers.HyperlinkedModelSerializer): - children = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + children = serializers.HyperlinkedRelatedField(many=True, + read_only=True, + view_name='projectcategory-detail') - projects = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + projects = serializers.HyperlinkedRelatedField(many=True, + read_only=True, + view_name='project-detail') class Meta: model = ProjectCategory - fields = ('pk', + fields = ('url', 'name', 'description', 'parent', diff --git a/InvenTree/project/urls.py b/InvenTree/project/urls.py index ffd14bc6e3..a0f0bc29b6 100644 --- a/InvenTree/project/urls.py +++ b/InvenTree/project/urls.py @@ -4,20 +4,20 @@ from . import views projectpartpatterns = [ # Detail of a single project part - url(r'^(?P<pk>[0-9]+)/?$', views.ProjectPartDetail.as_view(), name='project-part-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.ProjectPartDetail.as_view(), name='projectpart-detail'), # List project parts, with optional filters - url(r'^\?.*/?$', views.ProjectPartsList.as_view(), name='project-part-list'), - url(r'^$', views.ProjectPartsList.as_view(), name='project-part-list'), + url(r'^\?.*/?$', views.ProjectPartsList.as_view()), + url(r'^$', views.ProjectPartsList.as_view()), ] projectcategorypatterns = [ # Detail of a single project category - url(r'^(?P<pk>[0-9]+)/?$', views.ProjectCategoryDetail.as_view(), name='project-category-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.ProjectCategoryDetail.as_view(), name='projectcategory-detail'), # List of project categories, with filters - url(r'^\?.*/?$', views.ProjectCategoryList.as_view(), name='project-category-list'), - url(r'^$', views.ProjectCategoryList.as_view(), name='project-category-list'), + url(r'^\?.*/?$', views.ProjectCategoryList.as_view()), + url(r'^$', views.ProjectCategoryList.as_view()), ] urlpatterns = [ @@ -25,8 +25,8 @@ urlpatterns = [ url(r'^(?P<pk>[0-9]+)/?$', views.ProjectDetail.as_view(), name='project-detail'), # List of all projects - url(r'^\?.*/?$', views.ProjectList.as_view(), name='project-list'), - url(r'^$', views.ProjectList.as_view(), name='project-list'), + url(r'^\?.*/?$', views.ProjectList.as_view()), + url(r'^$', views.ProjectList.as_view()), # Project parts url(r'^parts/', include(projectpartpatterns)), From f7107008f08aa5ccceac32f9929103a64bae08b6 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Sat, 15 Apr 2017 00:10:35 +1000 Subject: [PATCH 10/12] Part API now hyperlinked --- InvenTree/part/serializers.py | 16 +++++----------- InvenTree/part/urls.py | 32 ++++++++++++++++---------------- InvenTree/project/serializers.py | 12 +----------- 3 files changed, 22 insertions(+), 38 deletions(-) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 47bbaedf40..bc38ca309d 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -17,14 +17,14 @@ class PartParameterSerializer(serializers.ModelSerializer): 'units') -class PartSerializer(serializers.ModelSerializer): +class PartSerializer(serializers.HyperlinkedModelSerializer): """ Serializer for complete detail information of a part. Used when displaying all details of a single component. """ class Meta: model = Part - fields = ('pk', + fields = ('url', 'name', 'IPN', 'description', @@ -32,21 +32,15 @@ class PartSerializer(serializers.ModelSerializer): 'stock') -class PartCategorySerializer(serializers.ModelSerializer): - - children = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - - parts = serializers.PrimaryKeyRelatedField(many=True, read_only=True) +class PartCategorySerializer(serializers.HyperlinkedModelSerializer): class Meta: model = PartCategory - fields = ('pk', + fields = ('url', 'name', 'description', 'parent', - 'path', - 'children', - 'parts') + 'path') class PartTemplateSerializer(serializers.ModelSerializer): diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index e2481fab36..72fffc5b32 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -10,29 +10,29 @@ from . import views categorypatterns = [ # Part category detail - url(r'^(?P<pk>[0-9]+)/?$', views.PartCategoryDetail.as_view(), name='part-category-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.PartCategoryDetail.as_view(), name='partcategory-detail'), # List of top-level categories - url(r'^\?*.*/?$', views.PartCategoryList.as_view(), name='part-category-list'), - url(r'^$', views.PartCategoryList.as_view(), name='part-category-list') + url(r'^\?*.*/?$', views.PartCategoryList.as_view()), + url(r'^$', views.PartCategoryList.as_view()) ] partparampatterns = [ # Detail of a single part parameter - url(r'^(?P<pk>[0-9]+)/?$', views.PartParamDetail.as_view(), name='part-parameter-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.PartParamDetail.as_view(), name='partparameter-detail'), # Parameters associated with a particular part - url(r'^\?.*/?$', views.PartParamList.as_view(), name='part-parameter-list'), - url(r'^$', views.PartParamList.as_view(), name='part-parameter-list'), + url(r'^\?.*/?$', views.PartParamList.as_view()), + url(r'^$', views.PartParamList.as_view()), ] parttemplatepatterns = [ # Detail of a single part field template - url(r'^(?P<pk>[0-9]+)/?$', views.PartTemplateDetail.as_view(), name='part-template-detail'), + url(r'^(?P<pk>[0-9]+)/?$', views.PartTemplateDetail.as_view(), name='partparametertemplate-detail'), # List all part field templates - url(r'^\?.*/?$', views.PartTemplateList.as_view(), name='part-template-list'), - url(r'^$', views.PartTemplateList.as_view(), name='part-template-list') + url(r'^\?.*/?$', views.PartTemplateList.as_view()), + url(r'^$', views.PartTemplateList.as_view()) ] """ Top-level URL patterns for the Part app: @@ -42,19 +42,19 @@ parttemplatepatterns = [ /part/category -> (refer to categorypatterns) """ urlpatterns = [ - # Individual part - url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view(), name='part-detail'), - # Part categories url(r'^category/', include(categorypatterns)), # Part parameters - url(r'^parameters/', include(partparampatterns)), + url(r'^parameter/', include(partparampatterns)), # Part templates - url(r'^templates/', include(parttemplatepatterns)), + url(r'^template/', include(parttemplatepatterns)), + + # Individual part + url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view(), name='part-detail'), # List parts with optional filters - url(r'^\?.*/?$', views.PartList.as_view(), name='part-list'), - url(r'^$', views.PartList.as_view(), name='part-list'), + url(r'^\?.*/?$', views.PartList.as_view()), + url(r'^$', views.PartList.as_view()), ] diff --git a/InvenTree/project/serializers.py b/InvenTree/project/serializers.py index 91bc79e619..1b854da4f1 100644 --- a/InvenTree/project/serializers.py +++ b/InvenTree/project/serializers.py @@ -26,20 +26,10 @@ class ProjectSerializer(serializers.HyperlinkedModelSerializer): class ProjectCategorySerializer(serializers.HyperlinkedModelSerializer): - children = serializers.HyperlinkedRelatedField(many=True, - read_only=True, - view_name='projectcategory-detail') - - projects = serializers.HyperlinkedRelatedField(many=True, - read_only=True, - view_name='project-detail') - class Meta: model = ProjectCategory fields = ('url', 'name', 'description', 'parent', - 'path', - 'children', - 'projects') + 'path') From bf1c7125a472187f3e5af88c163afeebc4607bd6 Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Sat, 15 Apr 2017 00:15:38 +1000 Subject: [PATCH 11/12] Added API documentation --- InvenTree/InvenTree/urls.py | 3 +++ requirements/base.txt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 2b5528cf5e..46ab1311d0 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -4,6 +4,7 @@ from django.contrib import admin from rest_framework import status from rest_framework.response import Response from rest_framework.decorators import api_view +from rest_framework.documentation import include_docs_urls admin.site.site_header = "InvenTree Admin" @@ -21,6 +22,8 @@ urlpatterns = [ # API URL url(r'^api/', include(apipatterns)), + url(r'^api-doc/', include_docs_urls(title='InvenTree API')), + url(r'^admin/', admin.site.urls), url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), ] diff --git a/requirements/base.txt b/requirements/base.txt index 0c17b62bfe..d80fe55e91 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,3 +1,5 @@ Django==1.11 djangorestframework==3.6.2 django_filter==1.0.2 +coreapi==2.3.0 +pygments==2.2.0 From 7d7579d3d62df45a8c824bba9d2cefb4985d957c Mon Sep 17 00:00:00 2001 From: Oliver Walters <oliver.henry.walters@gmail.com> Date: Sat, 15 Apr 2017 00:55:30 +1000 Subject: [PATCH 12/12] Style fixes --- InvenTree/InvenTree/urls.py | 6 +-- InvenTree/stock/location_urls.py | 10 +++++ InvenTree/stock/urls.py | 13 +----- InvenTree/stock/views.py | 55 ++++++++++------------- InvenTree/supplier/customer_urls.py | 2 +- InvenTree/supplier/manufacturer_urls.py | 2 +- InvenTree/supplier/part_urls.py | 10 +++++ InvenTree/supplier/price_urls.py | 10 +++++ InvenTree/supplier/serializers.py | 6 +-- InvenTree/supplier/urls.py | 15 +------ InvenTree/supplier/views.py | 59 ++++++++++++++----------- InvenTree/track/models.py | 2 +- 12 files changed, 97 insertions(+), 93 deletions(-) create mode 100644 InvenTree/stock/location_urls.py create mode 100644 InvenTree/supplier/part_urls.py create mode 100644 InvenTree/supplier/price_urls.py diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 46ab1311d0..63014a36d1 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -1,17 +1,17 @@ from django.conf.urls import url, include from django.contrib import admin -from rest_framework import status -from rest_framework.response import Response -from rest_framework.decorators import api_view from rest_framework.documentation import include_docs_urls admin.site.site_header = "InvenTree Admin" apipatterns = [ url(r'^stock/', include('stock.urls')), + url(r'^stock-location/', include('stock.location_urls')), url(r'^part/', include('part.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'^track/', include('track.urls')), diff --git a/InvenTree/stock/location_urls.py b/InvenTree/stock/location_urls.py new file mode 100644 index 0000000000..9df7a53b28 --- /dev/null +++ b/InvenTree/stock/location_urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url +from . import views + +urlpatterns = [ + url(r'^(?P<pk>[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 bb026d2a08..b2822682d5 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -1,19 +1,8 @@ -from django.conf.urls import url, include +from django.conf.urls import url from . import views -locpatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.LocationDetail.as_view(), name='stocklocation-detail'), - - url(r'^\?.*/?$', views.LocationList.as_view()), - - url(r'^$', views.LocationList.as_view()) -] - urlpatterns = [ - # Stock location urls - url(r'^location/', include(locpatterns)), - # Detail for a single stock item url(r'^(?P<pk>[0-9]+)/?$', views.StockDetail.as_view(), name='stockitem-detail'), diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index fa32ce234d..27fa65bfbe 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -1,7 +1,10 @@ -from rest_framework import generics, permissions import django_filters +from django_filters.rest_framework import FilterSet, DjangoFilterBackend +from django_filters import NumberFilter -from InvenTree.models import FilterChildren +from rest_framework import generics, permissions + +# from InvenTree.models import FilterChildren from .models import StockLocation, StockItem from .serializers import StockItemSerializer, LocationSerializer @@ -14,37 +17,24 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView): class StockFilter(django_filters.rest_framework.FilterSet): - min_stock = django_filters.NumberFilter(name='quantity', lookup_expr='gte') - max_stock = django_filters.NumberFilter(name='quantity', lookup_expr='lte') + min_stock = NumberFilter(name='quantity', lookup_expr='gte') + max_stock = NumberFilter(name='quantity', lookup_expr='lte') + part = NumberFilter(name='part', lookup_expr='exact') + location = NumberFilter(name='location', lookup_expr='exact') class Meta: model = StockItem - fields = ['quantity'] + fields = ['quantity', 'part', 'location'] class StockList(generics.ListCreateAPIView): + queryset = StockItem.objects.all() serializer_class = StockItemSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - filter_backends = (django_filters.rest_framework.DjangoFilterBackend,) + filter_backends = (DjangoFilterBackend,) filter_class = StockFilter - def get_queryset(self): - items = StockItem.objects.all() - - # Specify a particular part - part_id = self.request.query_params.get('part', None) - if part_id: - items = items.filter(part=part_id) - - # Specify a particular location - loc_id = self.request.query_params.get('location', None) - - if loc_id: - items = items.filter(location=loc_id) - - return items - class LocationDetail(generics.RetrieveUpdateDestroyAPIView): """ Return information on a specific stock location @@ -55,19 +45,22 @@ class LocationDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) +class StockLocationFilter(FilterSet): + + parent = NumberFilter(name='parent', lookup_expr='exact') + + class Meta: + model = StockLocation + fields = ['parent'] + + class LocationList(generics.ListCreateAPIView): """ Return a list of top-level locations Locations are considered "top-level" if they do not have a parent """ - def get_queryset(self): - params = self.request.query_params - - locations = StockLocation.objects.all() - - locations = FilterChildren(locations, params.get('parent', None)) - - return locations - + queryset = StockLocation.objects.all() serializer_class = LocationSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + filter_backends = (DjangoFilterBackend,) + filter_class = StockLocationFilter diff --git a/InvenTree/supplier/customer_urls.py b/InvenTree/supplier/customer_urls.py index 870bba1a31..9bfc222074 100644 --- a/InvenTree/supplier/customer_urls.py +++ b/InvenTree/supplier/customer_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/supplier/manufacturer_urls.py b/InvenTree/supplier/manufacturer_urls.py index 56a74c8106..9586990f1b 100644 --- a/InvenTree/supplier/manufacturer_urls.py +++ b/InvenTree/supplier/manufacturer_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/supplier/part_urls.py b/InvenTree/supplier/part_urls.py new file mode 100644 index 0000000000..813e4ce37e --- /dev/null +++ b/InvenTree/supplier/part_urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^(?P<pk>[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 new file mode 100644 index 0000000000..044f22b289 --- /dev/null +++ b/InvenTree/supplier/price_urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^(?P<pk>[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/serializers.py b/InvenTree/supplier/serializers.py index 88c23a573c..5aa278ed4b 100644 --- a/InvenTree/supplier/serializers.py +++ b/InvenTree/supplier/serializers.py @@ -35,13 +35,13 @@ class SupplierPartSerializer(serializers.ModelSerializer): view_name='supplierpricebreak-detail') part = serializers.HyperlinkedRelatedField(view_name='part-detail', - queryset = Part.objects.all()) + queryset=Part.objects.all()) supplier = serializers.HyperlinkedRelatedField(view_name='supplier-detail', - queryset = Supplier.objects.all()) + queryset=Supplier.objects.all()) manufacturer = serializers.HyperlinkedRelatedField(view_name='manufacturer-detail', - queryset = Manufacturer.objects.all()) + queryset=Manufacturer.objects.all()) class Meta: model = SupplierPart diff --git a/InvenTree/supplier/urls.py b/InvenTree/supplier/urls.py index 5efac75d31..f810662976 100644 --- a/InvenTree/supplier/urls.py +++ b/InvenTree/supplier/urls.py @@ -1,14 +1,7 @@ -from django.conf.urls import url, include +from django.conf.urls import url from . import views -partpatterns = [ - url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPartDetail.as_view(), name='supplierpart-detail'), - - url(r'^\?.*/?$', views.SupplierPartList.as_view()), - url(r'^$', views.SupplierPartList.as_view()) -] - pricepatterns = [ url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view(), name='supplierpricebreak-detail'), @@ -18,12 +11,6 @@ pricepatterns = [ urlpatterns = [ - # Supplier part information - url(r'part/', include(partpatterns)), - - # Supplier price information - url(r'price/', include(pricepatterns)), - # Display details of a supplier url(r'^(?P<pk>[0-9]+)/$', views.SupplierDetail.as_view(), name='supplier-detail'), diff --git a/InvenTree/supplier/views.py b/InvenTree/supplier/views.py index a12a7f5124..5867ddea67 100644 --- a/InvenTree/supplier/views.py +++ b/InvenTree/supplier/views.py @@ -1,3 +1,6 @@ +from django_filters.rest_framework import FilterSet, DjangoFilterBackend +from django_filters import NumberFilter + from rest_framework import generics, permissions from .models import Supplier, SupplierPart, SupplierPriceBreak @@ -58,28 +61,27 @@ class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) +class SupplierPartFilter(FilterSet): + + supplier = NumberFilter(name='supplier', lookup_expr='exact') + + part = NumberFilter(name='part', lookup_expr='exact') + + manufacturer = NumberFilter(name='manufacturer', lookup_expr='exact') + + class Meta: + model = SupplierPart + fields = ['supplier', 'part', 'manufacturer'] + + class SupplierPartList(generics.ListCreateAPIView): + queryset = SupplierPart.objects.all() serializer_class = SupplierPartSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - def get_queryset(self): - parts = SupplierPart.objects.all() - params = self.request.query_params - - supplier_id = params.get('supplier', None) - if supplier_id: - parts = parts.filter(supplier=supplier_id) - - part_id = params.get('part', None) - if part_id: - parts = parts.filter(part=part_id) - - manu_id = params.get('manufacturer', None) - if manu_id: - parts = parts.filter(manufacturer=manu_id) - - return parts + filter_backends = (DjangoFilterBackend,) + filter_class = SupplierPartFilter class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView): @@ -89,17 +91,20 @@ class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) +class PriceBreakFilter(FilterSet): + + part = NumberFilter(name='part', lookup_expr='exact') + + class Meta: + model = SupplierPriceBreak + fields = ['part'] + + class SupplierPriceBreakList(generics.ListCreateAPIView): - def get_queryset(self): - prices = SupplierPriceBreak.objects.all() - params = self.request.query_params - - part_id = params.get('part', None) - if part_id: - prices = prices.filter(part=part_id) - - return prices - + queryset = SupplierPriceBreak.objects.all() serializer_class = SupplierPriceBreakSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + filter_backends = (DjangoFilterBackend,) + filter_class = PriceBreakFilter diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index d0e8a5aa46..1bc189ae7c 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.utils.translation import ugettext as _ from django.db import models -from django.contrib.auth.models import User +# from django.contrib.auth.models import User from supplier.models import Customer from part.models import Part, PartRevision