diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index e8430694d0..c6aec418ad 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, @@ -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) @@ -184,7 +185,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: diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 5ab4076fad..63014a36d1 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -1,30 +1,29 @@ 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" - -@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'^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')), + url(r'^project/', include('project.urls')), +] 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')), + # API URL + url(r'^api/', include(apipatterns)), - # Any other URL - url(r'', Inventree404) + 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/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 bb7595a271..72fffc5b32 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -10,25 +10,28 @@ from . import views categorypatterns = [ # Part category detail - url(r'^(?P[0-9]+)/?$', views.PartCategoryDetail.as_view()), + url(r'^(?P[0-9]+)/?$', views.PartCategoryDetail.as_view(), name='partcategory-detail'), # List of top-level categories - url(r'^\?*[^/]*/?$', views.PartCategoryList.as_view()) + url(r'^\?*.*/?$', views.PartCategoryList.as_view()), + url(r'^$', views.PartCategoryList.as_view()) ] partparampatterns = [ # Detail of a single part parameter - url(r'^(?P[0-9]+)/?$', views.PartParamDetail.as_view()), + url(r'^(?P[0-9]+)/?$', views.PartParamDetail.as_view(), name='partparameter-detail'), # Parameters associated with a particular part - url(r'^\?*[^/]*/?$', views.PartParamList.as_view()), + url(r'^\?.*/?$', views.PartParamList.as_view()), + url(r'^$', views.PartParamList.as_view()), ] parttemplatepatterns = [ # Detail of a single part field template - url(r'^(?P[0-9]+)/?$', views.PartTemplateDetail.as_view()), + url(r'^(?P[0-9]+)/?$', views.PartTemplateDetail.as_view(), name='partparametertemplate-detail'), # List all part field templates + url(r'^\?.*/?$', views.PartTemplateList.as_view()), url(r'^$', views.PartTemplateList.as_view()) ] @@ -39,18 +42,19 @@ parttemplatepatterns = [ /part/category -> (refer to categorypatterns) """ urlpatterns = [ - # Individual part - url(r'^(?P[0-9]+)/?$', views.PartDetail.as_view()), - # Part categories - url(r'^category/?', include(categorypatterns)), + 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[0-9]+)/?$', views.PartDetail.as_view(), name='part-detail'), # List parts with optional filters - url(r'^\?*[^/]*/?$', views.PartList.as_view()), + 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 5e3904cb3e..1b854da4f1 100644 --- a/InvenTree/project/serializers.py +++ b/InvenTree/project/serializers.py @@ -3,41 +3,33 @@ 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): - - children = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - - projects = serializers.PrimaryKeyRelatedField(many=True, read_only=True) +class ProjectCategorySerializer(serializers.HyperlinkedModelSerializer): class Meta: model = ProjectCategory - fields = ('pk', + fields = ('url', 'name', 'description', 'parent', - 'path', - 'children', - 'projects') + 'path') diff --git a/InvenTree/project/urls.py b/InvenTree/project/urls.py index d0f50bac56..a0f0bc29b6 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/ -> Detail view of single project -/project//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[0-9]+)/?$', views.ProjectPartDetail.as_view()), + 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 = [ # Detail of a single project category - url(r'^(?P[0-9]+)/?$', views.ProjectCategoryDetail.as_view()), + 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 = [ # Individual project URL - url(r'^(?P[0-9]+)/?', include(projectdetailpatterns)), + 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)), + url(r'^parts/', include(projectpartpatterns)), # Project categories - url(r'^category/?', include(projectcategorypatterns)), + url(r'^category/', include(projectcategorypatterns)), ] 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[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/serializers.py b/InvenTree/stock/serializers.py index a542edf150..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,24 +20,13 @@ 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.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 b3496e5768..b2822682d5 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -1,20 +1,12 @@ -from django.conf.urls import url, include +from django.conf.urls import url from . import views -locpatterns = [ - url(r'^(?P[0-9]+)/?$', views.LocationDetail.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[0-9]+)$', views.StockDetail.as_view()), + url(r'^(?P[0-9]+)/?$', views.StockDetail.as_view(), name='stockitem-detail'), # List all stock items, with optional filters - url(r'^\?*[^/]*/?$', views.StockList.as_view()), + url(r'^\?.*/?$', views.StockList.as_view()), + url(r'^$', views.StockList.as_view()), ] diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 60f3a09c82..27fa65bfbe 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -1,9 +1,12 @@ -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, LocationDetailSerializer +from .serializers import StockItemSerializer, LocationSerializer class StockDetail(generics.RetrieveUpdateDestroyAPIView): @@ -14,60 +17,50 @@ 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 """ queryset = StockLocation.objects.all() - serializer_class = LocationDetailSerializer + serializer_class = LocationSerializer 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 - - serializer_class = LocationDetailSerializer + queryset = StockLocation.objects.all() + serializer_class = LocationSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + filter_backends = (DjangoFilterBackend,) + filter_class = StockLocationFilter 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/customer_urls.py b/InvenTree/supplier/customer_urls.py new file mode 100644 index 0000000000..9bfc222074 --- /dev/null +++ b/InvenTree/supplier/customer_urls.py @@ -0,0 +1,12 @@ +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 new file mode 100644 index 0000000000..9586990f1b --- /dev/null +++ b/InvenTree/supplier/manufacturer_urls.py @@ -0,0 +1,12 @@ +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/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) 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[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[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 3635a93a72..5aa278ed4b 100644 --- a/InvenTree/supplier/serializers.py +++ b/InvenTree/supplier/serializers.py @@ -1,22 +1,51 @@ 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.ModelSerializer): +class SupplierSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Supplier 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.PrimaryKeyRelatedField(many=True, read_only=True) + price_breaks = serializers.HyperlinkedRelatedField(many=True, + read_only=True, + 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 - fields = ['pk', + fields = ['url', 'part', 'supplier', 'SKU', @@ -32,11 +61,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..f810662976 100644 --- a/InvenTree/supplier/urls.py +++ b/InvenTree/supplier/urls.py @@ -1,30 +1,20 @@ -from django.conf.urls import url, include +from django.conf.urls import url from . import views -partpatterns = [ - url(r'^(?P[0-9]+)/?$', views.SupplierPartDetail.as_view()), - - url(r'^\?*[^/]*/?$', views.SupplierPartList.as_view()) -] - pricepatterns = [ - url(r'^(?P[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view()), + url(r'^(?P[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view(), name='supplierpricebreak-detail'), - url(r'^\?*[^/]*/?$', views.SupplierPriceBreakList.as_view()) + url(r'^\?.*/?$', views.SupplierPriceBreakList.as_view()), + url(r'^$', views.SupplierPriceBreakList.as_view()) ] 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[0-9]+)/?$', views.SupplierDetail.as_view()), + url(r'^(?P[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()) ] diff --git a/InvenTree/supplier/views.py b/InvenTree/supplier/views.py index e6fa2e628b..5867ddea67 100644 --- a/InvenTree/supplier/views.py +++ b/InvenTree/supplier/views.py @@ -1,9 +1,43 @@ +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 +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): @@ -27,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): @@ -58,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 d50b6d7578..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 @@ -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 447c1c3d38..aaf3b539ce 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[0-9]+)/?$', views.PartTrackingDetail.as_view()), + url(r'^(?P[0-9]+)/?$', views.PartTrackingDetail.as_view(), name='parttrackinginfo-detail'), - url(r'^\?*[^/]*/?$', views.PartTrackingList.as_view()) + url(r'^\?.*/?$', views.PartTrackingList.as_view()), + url(r'^$', views.PartTrackingList.as_view()) ] urlpatterns = [ - url(r'info/?', include(infopatterns)), + url(r'info/', include(infopatterns)), # Detail for a single unique part - url(r'^(?P[0-9]+)$', views.UniquePartDetail.as_view()), + url(r'^(?P[0-9]+)/?$', views.UniquePartDetail.as_view(), name='uniquepart-detail'), # List all unique parts, with optional filters - url(r'^\?*[^/]*/?$', views.UniquePartList.as_view()), + url(r'^\?.*/?$', views.UniquePartList.as_view()), + url(r'^$', views.UniquePartList.as_view()), ] 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 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