mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 03:56:43 +00:00
Merge pull request #39 from SchrodingersGat/master
Added hyperlinks and docs to API
This commit is contained in:
commit
150e1bb34c
@ -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:
|
||||||
|
@ -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')),
|
||||||
]
|
]
|
||||||
|
@ -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):
|
||||||
|
@ -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()),
|
||||||
]
|
]
|
||||||
|
@ -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')
|
|
||||||
|
@ -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)),
|
||||||
]
|
]
|
||||||
|
10
InvenTree/stock/location_urls.py
Normal file
10
InvenTree/stock/location_urls.py
Normal 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())
|
||||||
|
]
|
@ -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',
|
||||||
|
@ -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()),
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
12
InvenTree/supplier/customer_urls.py
Normal file
12
InvenTree/supplier/customer_urls.py
Normal 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())
|
||||||
|
]
|
12
InvenTree/supplier/manufacturer_urls.py
Normal file
12
InvenTree/supplier/manufacturer_urls.py
Normal 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())
|
||||||
|
]
|
@ -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)
|
||||||
|
10
InvenTree/supplier/part_urls.py
Normal file
10
InvenTree/supplier/part_urls.py
Normal 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())
|
||||||
|
]
|
10
InvenTree/supplier/price_urls.py
Normal file
10
InvenTree/supplier/price_urls.py
Normal 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())
|
||||||
|
]
|
@ -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']
|
||||||
|
@ -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())
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()),
|
||||||
]
|
]
|
||||||
|
6
Makefile
6
Makefile
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user