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

Merge pull request #36 from SchrodingersGat/master

API improvements
This commit is contained in:
Oliver 2017-04-14 13:31:43 +10:00 committed by GitHub
commit 72c3c5271e
10 changed files with 161 additions and 87 deletions

View File

@ -173,3 +173,25 @@ class InvenTreeTree(models.Model):
""" """
return self.path return self.path
def FilterChildren(queryset, parent):
""" Filter a queryset, limit to only objects that are a child of the given parent
Filter is passed in the URL string, e.g. '/?parent=123'
To accommodate for items without a parent, top-level items can be specified as:
none / false / null / top / 0
"""
if not parent:
return queryset
elif isinstance(parent, str) and parent.lower() in ['none', 'false', 'null', 'top', '0']:
return queryset.filter(parent=None)
else:
try:
parent_id = int(parent)
if parent_id == 0:
return queryset.filter(parent=None)
else:
return queryset.filter(parent=parent_id)
except:
return queryset

View File

@ -43,21 +43,13 @@ class PartCategoryBriefSerializer(serializers.ModelSerializer):
class PartCategoryDetailSerializer(serializers.ModelSerializer): class PartCategoryDetailSerializer(serializers.ModelSerializer):
# List of parts in this category
parts = PartSerializer(many=True, read_only=True)
# List of child categories under this one
children = PartCategoryBriefSerializer(many=True, read_only=True)
class Meta: class Meta:
model = PartCategory model = PartCategory
fields = ('pk', fields = ('pk',
'name', 'name',
'description', 'description',
'parent', 'parent',
'path', 'path')
'children',
'parts')
class PartTemplateSerializer(serializers.ModelSerializer): class PartTemplateSerializer(serializers.ModelSerializer):

View File

@ -10,30 +10,26 @@ from . import views
categorypatterns = [ categorypatterns = [
# Part category detail # Part category detail
url(r'^category/(?P<pk>[0-9]+)/$', views.PartCategoryDetail.as_view()), url(r'^(?P<pk>[0-9]+)/?$', views.PartCategoryDetail.as_view()),
# List of top-level categories # List of top-level categories
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()),
# Parameters associated with a particular part # Parameters associated with a particular part
url(r'^\?[^/]*/$', views.PartParamList.as_view()), url(r'^\?*[^/]*/?$', views.PartParamList.as_view()),
# All part parameters
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()),
# List all part field templates # List all part field templates
url(r'^$', views.PartTemplateList.as_view()) url(r'^$', views.PartTemplateList.as_view())
] ]
""" Top-level URL patterns for the Part app: """ Top-level URL patterns for the Part app:
@ -44,16 +40,16 @@ parttemplatepatterns = [
""" """
urlpatterns = [ urlpatterns = [
# Individual part # Individual part
url(r'^(?P<pk>[0-9]+)/$', views.PartDetail.as_view()), url(r'^(?P<pk>[0-9]+)/?$', views.PartDetail.as_view()),
# Part categories # Part categories
url(r'^category/', views.PartCategoryList.as_view()), url(r'^category/?', include(categorypatterns)),
# Part parameters # Part parameters
url(r'^parameters/', include(partparampatterns)), url(r'^parameters/?', include(partparampatterns)),
# Part templates # Part templates
url(r'^templates/', include(parttemplatepatterns)), url(r'^templates/?', include(parttemplatepatterns)),
# List parts with optional filters # List parts with optional filters
url(r'^\?*[^/]*/?$', views.PartList.as_view()), url(r'^\?*[^/]*/?$', views.PartList.as_view()),

View File

@ -2,6 +2,7 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from InvenTree.models import FilterChildren
from .models import PartCategory, Part, PartParameter, PartParameterTemplate from .models import PartCategory, Part, PartParameter, PartParameterTemplate
from .serializers import PartSerializer from .serializers import PartSerializer
from .serializers import PartCategoryDetailSerializer from .serializers import PartCategoryDetailSerializer
@ -68,8 +69,10 @@ class PartList(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
parts = Part.objects.all() parts = Part.objects.all()
params = self.request.query_params
cat_id = params.get('category', None)
cat_id = self.request.query_params.get('category', None)
if cat_id: if cat_id:
parts = parts.filter(category=cat_id) parts = parts.filter(category=cat_id)
@ -91,6 +94,16 @@ class PartCategoryList(generics.ListCreateAPIView):
""" Return a list of all top-level part categories. """ Return a list of all top-level part categories.
Categories are considered "top-level" if they do not have a parent Categories are considered "top-level" if they do not have a parent
""" """
def get_queryset(self):
params = self.request.query_params
categories = PartCategory.objects.all()
categories = FilterChildren(categories, params.get('parent', None))
return categories
queryset = PartCategory.objects.filter(parent=None) queryset = PartCategory.objects.filter(parent=None)
serializer_class = PartCategoryDetailSerializer serializer_class = PartCategoryDetailSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

View File

@ -35,16 +35,10 @@ class ProjectCategoryBriefSerializer(serializers.ModelSerializer):
class ProjectCategoryDetailSerializer(serializers.ModelSerializer): class ProjectCategoryDetailSerializer(serializers.ModelSerializer):
projects = ProjectSerializer(many=True, read_only=True)
children = ProjectCategoryBriefSerializer(many=True, read_only=True)
class Meta: class Meta:
model = ProjectCategory model = ProjectCategory
fields = ('pk', fields = ('pk',
'name', 'name',
'description', 'description',
'parent', 'parent',
'path', 'path')
'children',
'projects')

View File

@ -13,34 +13,30 @@ projectdetailpatterns = [
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()),
# List project parts, with optional filters # List project parts, with optional filters
url(r'^\?*[^/]*/?$', views.ProjectPartsList.as_view()), url(r'^\?*[^/]*/?$', views.ProjectPartsList.as_view()),
] ]
projectcategorypatterns = [ projectcategorypatterns = [
# List of top-level project categories
url(r'^$', views.ProjectCategoryList.as_view()),
# 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()),
# Create a new category # List of project categories, with filters
url(r'^new/$', views.NewProjectCategory.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]+)/?', include(projectdetailpatterns)),
# List of all projects # List of all projects
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

@ -1,5 +1,6 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from InvenTree.models import FilterChildren
from .models import ProjectCategory, Project, ProjectPart from .models import ProjectCategory, Project, ProjectPart
from .serializers import ProjectSerializer from .serializers import ProjectSerializer
from .serializers import ProjectCategoryDetailSerializer from .serializers import ProjectCategoryDetailSerializer
@ -16,21 +17,24 @@ class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
class ProjectList(generics.ListCreateAPIView): class ProjectList(generics.ListCreateAPIView):
""" List all projects """ List projects
""" """
queryset = Project.objects.all() def get_queryset(self):
projects = Project.objects.all()
params = self.request.query_params
cat_id = params.get('category', None)
if cat_id:
projects = projects.filter(category=cat_id)
return projects
serializer_class = ProjectSerializer serializer_class = ProjectSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class NewProjectCategory(generics.CreateAPIView):
""" Create a new Project Category
"""
serializer_class = ProjectCategoryDetailSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class ProjectCategoryDetail(generics.RetrieveUpdateAPIView): class ProjectCategoryDetail(generics.RetrieveUpdateAPIView):
""" Project details """ Project details
""" """
@ -41,29 +45,42 @@ class ProjectCategoryDetail(generics.RetrieveUpdateAPIView):
class ProjectCategoryList(generics.ListCreateAPIView): class ProjectCategoryList(generics.ListCreateAPIView):
""" Top-level project categories. """ List project categories
Projects are considered top-level if they do not have a parent
""" """
queryset = ProjectCategory.objects.filter(parent=None) def get_queryset(self):
params = self.request.query_params
categories = ProjectCategory.objects.all()
categories = FilterChildren(categories, params.get('parent', None))
return categories
serializer_class = ProjectCategoryDetailSerializer serializer_class = ProjectCategoryDetailSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class ProjectPartsList(generics.ListCreateAPIView): class ProjectPartsList(generics.ListCreateAPIView):
""" List all parts associated with a particular project """ List project parts
""" """
serializer_class = ProjectPartSerializer serializer_class = ProjectPartSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self): def get_queryset(self):
project_id = self.request.query_params.get('project', None) parts = ProjectPart.objects.all()
params = self.request.query_params
project_id = params.get('project', None)
if project_id: if project_id:
return ProjectPart.objects.filter(project=project_id) parts = parts.filter(project=project_id)
else:
return ProjectPart.objects.all() part_id = params.get('part', None)
if part_id:
parts = parts.filter(part=part_id)
return parts
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
# Ensure project link is set correctly # Ensure project link is set correctly

View File

@ -35,18 +35,10 @@ class LocationDetailSerializer(serializers.ModelSerializer):
""" Detailed information about a stock location """ Detailed information about a stock location
""" """
# List of all stock items in this location
items = StockItemSerializer(many=True, read_only=True)
# List of all child locations under this one
children = LocationBriefSerializer(many=True, read_only=True)
class Meta: class Meta:
model = StockLocation model = StockLocation
fields = ('pk', fields = ('pk',
'name', 'name',
'description', 'description',
'parent', 'parent',
'path', 'path')
'children',
'items')

View File

@ -1,15 +1,20 @@
from django.conf.urls import url from django.conf.urls import url, include
from . import views from . import views
urlpatterns = [ locpatterns = [
# List all stock quantities for a given part url(r'^(?P<pk>[0-9]+)/?$', views.LocationDetail.as_view()),
url(r'^part/(?P<part>[0-9]+)$', views.PartStockDetail.as_view()),
# List all stock items in a given location url(r'^\?*[^/]*/?$', views.LocationList.as_view())
url(r'^location/(?P<pk>[0-9]+)$', views.LocationDetail.as_view()), ]
# List all top-level locations urlpatterns = [
url(r'^location/$', views.LocationList.as_view()), # Stock location urls
url(r'^$', views.LocationList.as_view()) url(r'^location/?', include(locpatterns)),
# Detail for a single stock item
url(r'^(?P<pk>[0-9]+)$', views.StockDetail.as_view()),
# List all stock items, with optional filters
url(r'^\?*[^/]*/?$', views.StockList.as_view()),
] ]

View File

@ -1,33 +1,80 @@
from rest_framework import generics from rest_framework import generics, permissions
import django_filters
from InvenTree.models import FilterChildren
from .models import StockLocation, StockItem from .models import StockLocation, StockItem
from .serializers import StockItemSerializer, LocationDetailSerializer from .serializers import StockItemSerializer, LocationDetailSerializer
class PartStockDetail(generics.ListCreateAPIView): class StockDetail(generics.RetrieveUpdateDestroyAPIView):
""" Return a list of all stockitems for a given part
""" queryset = StockItem.objects.all()
serializer_class = StockItemSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
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')
class Meta:
model = StockItem
fields = ['quantity']
class StockList(generics.ListCreateAPIView):
serializer_class = StockItemSerializer serializer_class = StockItemSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
filter_class = StockFilter
def get_queryset(self): def get_queryset(self):
part_id = self.kwargs['part'] items = StockItem.objects.all()
return StockItem.objects.filter(part=part_id)
# 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
def create(self, request, *args, **kwargs):
# If the PART parameter is passed in the URL, use that
part_id = self.request.query_params.get('part', None)
if part_id:
request.data['part'] = part_id
return super(StockList, self).create(request, *args, **kwargs)
class LocationDetail(generics.RetrieveAPIView): 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 = LocationDetailSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class LocationList(generics.ListAPIView): 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
""" """
queryset = StockLocation.objects.filter(parent=None) 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 serializer_class = LocationDetailSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)