2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

Merge pull request #39 from SchrodingersGat/master

Added hyperlinks and docs to API
This commit is contained in:
Oliver 2017-04-15 00:56:27 +10:00 committed by GitHub
commit 150e1bb34c
24 changed files with 281 additions and 210 deletions

View File

@ -12,7 +12,7 @@ class Company(models.Model):
abstract = True abstract = True
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
URL = models.URLField(blank=True) website = models.URLField(blank=True)
address = models.CharField(max_length=200, address = models.CharField(max_length=200,
blank=True) blank=True)
phone = models.CharField(max_length=50, phone = models.CharField(max_length=50,
@ -35,6 +35,7 @@ class InvenTreeTree(models.Model):
class Meta: class Meta:
abstract = True abstract = True
unique_together = ('name', 'parent')
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
description = models.CharField(max_length=250, blank=True) description = models.CharField(max_length=250, blank=True)
@ -184,7 +185,7 @@ def FilterChildren(queryset, parent):
if not parent: if not parent:
return queryset 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) return queryset.filter(parent=None)
else: else:
try: try:

View File

@ -1,30 +1,29 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib import admin from django.contrib import admin
from rest_framework import status from rest_framework.documentation import include_docs_urls
from rest_framework.response import Response
from rest_framework.decorators import api_view
admin.site.site_header = "InvenTree Admin" admin.site.site_header = "InvenTree Admin"
apipatterns = [
@api_view() url(r'^stock/', include('stock.urls')),
def Inventree404(self): url(r'^stock-location/', include('stock.location_urls')),
""" Supplied URL is invalid url(r'^part/', include('part.urls')),
""" url(r'^supplier/', include('supplier.urls')),
content = {'detail': 'Malformed API URL'} url(r'^supplier-part/', include('supplier.part_urls')),
return Response(content, status=status.HTTP_404_NOT_FOUND) 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 = [ urlpatterns = [
url(r'^stock/?', include('stock.urls')), # API URL
url(r'^part/?', include('part.urls')), url(r'^api/', include(apipatterns)),
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'^api-doc/', include_docs_urls(title='InvenTree API')),
url(r'', Inventree404)
url(r'^admin/', admin.site.urls),
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
] ]

View File

@ -17,14 +17,14 @@ class PartParameterSerializer(serializers.ModelSerializer):
'units') 'units')
class PartSerializer(serializers.ModelSerializer): class PartSerializer(serializers.HyperlinkedModelSerializer):
""" Serializer for complete detail information of a part. """ Serializer for complete detail information of a part.
Used when displaying all details of a single component. Used when displaying all details of a single component.
""" """
class Meta: class Meta:
model = Part model = Part
fields = ('pk', fields = ('url',
'name', 'name',
'IPN', 'IPN',
'description', 'description',
@ -32,21 +32,15 @@ class PartSerializer(serializers.ModelSerializer):
'stock') 'stock')
class PartCategorySerializer(serializers.ModelSerializer): class PartCategorySerializer(serializers.HyperlinkedModelSerializer):
children = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
parts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta: class Meta:
model = PartCategory model = PartCategory
fields = ('pk', fields = ('url',
'name', 'name',
'description', 'description',
'parent', 'parent',
'path', 'path')
'children',
'parts')
class PartTemplateSerializer(serializers.ModelSerializer): class PartTemplateSerializer(serializers.ModelSerializer):

View File

@ -10,25 +10,28 @@ from . import views
categorypatterns = [ categorypatterns = [
# Part category detail # Part category detail
url(r'^(?P<pk>[0-9]+)/?$', views.PartCategoryDetail.as_view()), url(r'^(?P<pk>[0-9]+)/?$', views.PartCategoryDetail.as_view(), name='partcategory-detail'),
# List of top-level categories # List of top-level categories
url(r'^\?*[^/]*/?$', views.PartCategoryList.as_view()) url(r'^\?*.*/?$', views.PartCategoryList.as_view()),
url(r'^$', views.PartCategoryList.as_view())
] ]
partparampatterns = [ partparampatterns = [
# Detail of a single part parameter # 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='partparameter-detail'),
# Parameters associated with a particular part # 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 = [ parttemplatepatterns = [
# Detail of a single part field template # 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='partparametertemplate-detail'),
# List all part field templates # List all part field templates
url(r'^\?.*/?$', views.PartTemplateList.as_view()),
url(r'^$', views.PartTemplateList.as_view()) url(r'^$', views.PartTemplateList.as_view())
] ]
@ -39,18 +42,19 @@ parttemplatepatterns = [
/part/category -> (refer to categorypatterns) /part/category -> (refer to categorypatterns)
""" """
urlpatterns = [ urlpatterns = [
# Individual part
url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view()),
# Part categories # Part categories
url(r'^category/?', include(categorypatterns)), url(r'^category/', include(categorypatterns)),
# Part parameters # Part parameters
url(r'^parameters/?', include(partparampatterns)), url(r'^parameter/', include(partparampatterns)),
# Part templates # 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 # List parts with optional filters
url(r'^\?*[^/]*/?$', views.PartList.as_view()), url(r'^\?.*/?$', views.PartList.as_view()),
url(r'^$', views.PartList.as_view()),
] ]

View File

@ -3,41 +3,33 @@ from rest_framework import serializers
from .models import ProjectCategory, Project, ProjectPart from .models import ProjectCategory, Project, ProjectPart
class ProjectPartSerializer(serializers.ModelSerializer): class ProjectPartSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = ProjectPart model = ProjectPart
fields = ('pk', fields = ('url',
'part', 'part',
'project', 'project',
'quantity', 'quantity',
'output') 'output')
class ProjectSerializer(serializers.ModelSerializer): class ProjectSerializer(serializers.HyperlinkedModelSerializer):
""" Serializer for displaying brief overview of a project
"""
class Meta: class Meta:
model = Project model = Project
fields = ('pk', fields = ('url',
'name', 'name',
'description', 'description',
'category') 'category')
class ProjectCategorySerializer(serializers.ModelSerializer): class ProjectCategorySerializer(serializers.HyperlinkedModelSerializer):
children = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
projects = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta: class Meta:
model = ProjectCategory model = ProjectCategory
fields = ('pk', fields = ('url',
'name', 'name',
'description', 'description',
'parent', 'parent',
'path', 'path')
'children',
'projects')

View File

@ -2,41 +2,35 @@ from django.conf.urls import url, include
from . import views 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 = [ projectpartpatterns = [
# Detail of a single project part # 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='projectpart-detail'),
# List project parts, with optional filters # 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 = [ projectcategorypatterns = [
# Detail of a single project category # 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='projectcategory-detail'),
# List of project categories, with filters # 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 = [ urlpatterns = [
# Individual project URL # 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 # List of all projects
url(r'^\?.*/?$', views.ProjectList.as_view()),
url(r'^$', views.ProjectList.as_view()), url(r'^$', views.ProjectList.as_view()),
# Project parts # Project parts
url(r'^parts/?', include(projectpartpatterns)), url(r'^parts/', include(projectpartpatterns)),
# Project categories # Project categories
url(r'^category/?', include(projectcategorypatterns)), url(r'^category/', include(projectcategorypatterns)),
] ]

View File

@ -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())
]

View File

@ -3,13 +3,13 @@ from rest_framework import serializers
from .models import StockItem, StockLocation from .models import StockItem, StockLocation
class StockItemSerializer(serializers.ModelSerializer): class StockItemSerializer(serializers.HyperlinkedModelSerializer):
""" Serializer for a StockItem """ Serializer for a StockItem
""" """
class Meta: class Meta:
model = StockItem model = StockItem
fields = ('pk', fields = ('url',
'part', 'part',
'location', 'location',
'quantity', 'quantity',
@ -20,24 +20,13 @@ class StockItemSerializer(serializers.ModelSerializer):
'expected_arrival') 'expected_arrival')
class LocationBriefSerializer(serializers.ModelSerializer): class LocationSerializer(serializers.HyperlinkedModelSerializer):
""" Brief information about a stock location
"""
class Meta:
model = StockLocation
fields = ('pk',
'name',
'description')
class LocationDetailSerializer(serializers.ModelSerializer):
""" Detailed information about a stock location """ Detailed information about a stock location
""" """
class Meta: class Meta:
model = StockLocation model = StockLocation
fields = ('pk', fields = ('url',
'name', 'name',
'description', 'description',
'parent', 'parent',

View File

@ -1,20 +1,12 @@
from django.conf.urls import url, include from django.conf.urls import url
from . import views from . import views
locpatterns = [
url(r'^(?P<pk>[0-9]+)/?$', views.LocationDetail.as_view()),
url(r'^\?*[^/]*/?$', views.LocationList.as_view())
]
urlpatterns = [ urlpatterns = [
# Stock location urls
url(r'^location/?', include(locpatterns)),
# Detail for a single stock item # 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='stockitem-detail'),
# List all stock items, with optional filters # 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()),
] ]

View File

@ -1,9 +1,12 @@
from rest_framework import generics, permissions
import django_filters 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 .models import StockLocation, StockItem
from .serializers import StockItemSerializer, LocationDetailSerializer from .serializers import StockItemSerializer, LocationSerializer
class StockDetail(generics.RetrieveUpdateDestroyAPIView): class StockDetail(generics.RetrieveUpdateDestroyAPIView):
@ -14,60 +17,50 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
class StockFilter(django_filters.rest_framework.FilterSet): class StockFilter(django_filters.rest_framework.FilterSet):
min_stock = django_filters.NumberFilter(name='quantity', lookup_expr='gte') min_stock = NumberFilter(name='quantity', lookup_expr='gte')
max_stock = django_filters.NumberFilter(name='quantity', lookup_expr='lte') max_stock = NumberFilter(name='quantity', lookup_expr='lte')
part = NumberFilter(name='part', lookup_expr='exact')
location = NumberFilter(name='location', lookup_expr='exact')
class Meta: class Meta:
model = StockItem model = StockItem
fields = ['quantity'] fields = ['quantity', 'part', 'location']
class StockList(generics.ListCreateAPIView): class StockList(generics.ListCreateAPIView):
queryset = StockItem.objects.all()
serializer_class = StockItemSerializer serializer_class = StockItemSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filter_class = StockFilter 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): class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
""" Return information on a specific stock location """ Return information on a specific stock location
""" """
queryset = StockLocation.objects.all() queryset = StockLocation.objects.all()
serializer_class = LocationDetailSerializer serializer_class = LocationSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class StockLocationFilter(FilterSet):
parent = NumberFilter(name='parent', lookup_expr='exact')
class Meta:
model = StockLocation
fields = ['parent']
class LocationList(generics.ListCreateAPIView): class LocationList(generics.ListCreateAPIView):
""" Return a list of top-level locations """ Return a list of top-level locations
Locations are considered "top-level" if they do not have a parent Locations are considered "top-level" if they do not have a parent
""" """
def get_queryset(self): queryset = StockLocation.objects.all()
params = self.request.query_params serializer_class = LocationSerializer
locations = StockLocation.objects.all()
locations = FilterChildren(locations, params.get('parent', None))
return locations
serializer_class = LocationDetailSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filter_class = StockLocationFilter

View File

@ -4,7 +4,7 @@ from .models import Supplier, SupplierPart, Customer, Manufacturer
class CompanyAdmin(admin.ModelAdmin): class CompanyAdmin(admin.ModelAdmin):
list_display = ('name', 'URL', 'contact') list_display = ('name', 'website', 'contact')
admin.site.register(Customer, CompanyAdmin) admin.site.register(Customer, CompanyAdmin)

View File

@ -0,0 +1,12 @@
from django.conf.urls import url
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())
]

View File

@ -0,0 +1,12 @@
from django.conf.urls import url
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())
]

View File

@ -31,6 +31,9 @@ class SupplierPart(models.Model):
- A Part may be available from multiple suppliers - 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) part = models.ForeignKey(Part, null=True, blank=True, on_delete=models.CASCADE)
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE) supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE)
SKU = models.CharField(max_length=100) SKU = models.CharField(max_length=100)

View File

@ -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())
]

View File

@ -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())
]

View File

@ -1,22 +1,51 @@
from rest_framework import serializers from rest_framework import serializers
from part.models import Part
from .models import Supplier, SupplierPart, SupplierPriceBreak from .models import Supplier, SupplierPart, SupplierPriceBreak
from .models import Manufacturer
from .models import Customer
class SupplierSerializer(serializers.ModelSerializer): class SupplierSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = Supplier model = Supplier
fields = '__all__' 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): 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: class Meta:
model = SupplierPart model = SupplierPart
fields = ['pk', fields = ['url',
'part', 'part',
'supplier', 'supplier',
'SKU', 'SKU',
@ -32,11 +61,11 @@ class SupplierPartSerializer(serializers.ModelSerializer):
'lead_time'] 'lead_time']
class SupplierPriceBreakSerializer(serializers.ModelSerializer): class SupplierPriceBreakSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = SupplierPriceBreak model = SupplierPriceBreak
fields = ['pk', fields = ['url',
'part', 'part',
'quantity', 'quantity',
'cost'] 'cost']

View File

@ -1,30 +1,20 @@
from django.conf.urls import url, include from django.conf.urls import url
from . import views from . import views
partpatterns = [
url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPartDetail.as_view()),
url(r'^\?*[^/]*/?$', views.SupplierPartList.as_view())
]
pricepatterns = [ pricepatterns = [
url(r'^(?P<pk>[0-9]+)/?$', views.SupplierPriceBreakDetail.as_view()), 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()),
url(r'^$', views.SupplierPriceBreakList.as_view())
] ]
urlpatterns = [ urlpatterns = [
# Supplier part information
url(r'part/?', include(partpatterns)),
# Supplier price information
url(r'price/?', include(pricepatterns)),
# Display details of a supplier # 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 # List suppliers
url(r'^\?*[^/]*/?$', views.SupplierList.as_view()) url(r'^\?.*/?$', views.SupplierList.as_view()),
url(r'^$', views.SupplierList.as_view())
] ]

View File

@ -1,9 +1,43 @@
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from django_filters import NumberFilter
from rest_framework import generics, permissions from rest_framework import generics, permissions
from .models import Supplier, SupplierPart, SupplierPriceBreak from .models import Supplier, SupplierPart, SupplierPriceBreak
from .models import Manufacturer, Customer
from .serializers import SupplierSerializer from .serializers import SupplierSerializer
from .serializers import SupplierPartSerializer from .serializers import SupplierPartSerializer
from .serializers import SupplierPriceBreakSerializer 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): class SupplierDetail(generics.RetrieveUpdateDestroyAPIView):
@ -27,28 +61,27 @@ class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) 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): class SupplierPartList(generics.ListCreateAPIView):
queryset = SupplierPart.objects.all()
serializer_class = SupplierPartSerializer serializer_class = SupplierPartSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self): filter_backends = (DjangoFilterBackend,)
parts = SupplierPart.objects.all() filter_class = SupplierPartFilter
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
class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView): class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView):
@ -58,17 +91,20 @@ class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class PriceBreakFilter(FilterSet):
part = NumberFilter(name='part', lookup_expr='exact')
class Meta:
model = SupplierPriceBreak
fields = ['part']
class SupplierPriceBreakList(generics.ListCreateAPIView): class SupplierPriceBreakList(generics.ListCreateAPIView):
def get_queryset(self): queryset = SupplierPriceBreak.objects.all()
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
serializer_class = SupplierPriceBreakSerializer serializer_class = SupplierPriceBreakSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filter_class = PriceBreakFilter

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.db import models 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 supplier.models import Customer
from part.models import Part, PartRevision from part.models import Part, PartRevision
@ -37,6 +37,10 @@ class UniquePart(models.Model):
and tracking all events in the life of a part 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() objects = UniquePartManager()
part = models.ForeignKey(Part, on_delete=models.CASCADE) part = models.ForeignKey(Part, on_delete=models.CASCADE)
@ -50,7 +54,7 @@ class UniquePart(models.Model):
editable=False) editable=False)
serial = models.IntegerField() serial = models.IntegerField()
createdBy = models.ForeignKey(User) # createdBy = models.ForeignKey(User)
customer = models.ForeignKey(Customer, blank=True, null=True) customer = models.ForeignKey(Customer, blank=True, null=True)
@ -76,17 +80,6 @@ class UniquePart(models.Model):
def __str__(self): def __str__(self):
return self.part.name 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): class PartTrackingInfo(models.Model):
""" Single data-point in the life of a UniquePart """ Single data-point in the life of a UniquePart

View File

@ -3,24 +3,24 @@ from rest_framework import serializers
from .models import UniquePart, PartTrackingInfo from .models import UniquePart, PartTrackingInfo
class UniquePartSerializer(serializers.ModelSerializer): class UniquePartSerializer(serializers.HyperlinkedModelSerializer):
tracking_info = serializers.PrimaryKeyRelatedField(many=True, read_only=True) tracking_info = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta: class Meta:
model = UniquePart model = UniquePart
fields = ['pk', fields = ['url',
'part', 'part',
'revision', 'revision',
'creation_date', 'creation_date',
'serial', 'serial',
'createdBy', # 'createdBy',
'customer', 'customer',
'status', 'status',
'tracking_info'] 'tracking_info']
class PartTrackingInfoSerializer(serializers.ModelSerializer): class PartTrackingInfoSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = PartTrackingInfo model = PartTrackingInfo

View File

@ -3,17 +3,19 @@ from django.conf.urls import url, include
from . import views from . import views
infopatterns = [ infopatterns = [
url(r'^(?P<pk>[0-9]+)/?$', views.PartTrackingDetail.as_view()), url(r'^(?P<pk>[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 = [ urlpatterns = [
url(r'info/?', include(infopatterns)), url(r'info/', include(infopatterns)),
# Detail for a single unique part # 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='uniquepart-detail'),
# List all unique parts, with optional filters # 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()),
] ]

View File

@ -15,7 +15,11 @@ test:
python InvenTree/manage.py test --noinput python InvenTree/manage.py test --noinput
migrate: 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 migrate --run-syncdb
python InvenTree/manage.py check python InvenTree/manage.py check

View File

@ -1,3 +1,5 @@
Django==1.11 Django==1.11
djangorestframework==3.6.2 djangorestframework==3.6.2
django_filter==1.0.2 django_filter==1.0.2
coreapi==2.3.0
pygments==2.2.0