2019-04-27 12:18:07 +00:00
|
|
|
"""
|
|
|
|
Provides a JSON API for the Part app
|
|
|
|
"""
|
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import unicode_literals
|
2018-04-14 04:11:46 +00:00
|
|
|
|
2018-04-23 11:18:35 +00:00
|
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
2019-06-17 15:31:30 +00:00
|
|
|
from django.conf import settings
|
|
|
|
|
|
|
|
from django.db.models import Sum
|
2019-05-04 23:14:12 +00:00
|
|
|
|
2019-05-05 00:47:57 +00:00
|
|
|
from rest_framework import status
|
|
|
|
from rest_framework.response import Response
|
2018-04-23 11:10:13 +00:00
|
|
|
from rest_framework import filters
|
2019-04-13 23:25:46 +00:00
|
|
|
from rest_framework import generics, permissions
|
2018-04-14 04:11:46 +00:00
|
|
|
|
2018-05-04 13:54:57 +00:00
|
|
|
from django.conf.urls import url, include
|
2019-05-09 12:23:56 +00:00
|
|
|
from django.urls import reverse
|
2018-04-14 04:11:46 +00:00
|
|
|
|
2019-06-17 15:31:30 +00:00
|
|
|
import os
|
|
|
|
|
2019-05-04 23:05:44 +00:00
|
|
|
from .models import Part, PartCategory, BomItem, PartStar
|
2018-05-02 14:47:03 +00:00
|
|
|
|
2018-05-02 13:42:57 +00:00
|
|
|
from .serializers import PartSerializer, BomItemSerializer
|
2018-05-04 13:54:57 +00:00
|
|
|
from .serializers import CategorySerializer
|
2019-05-04 23:05:44 +00:00
|
|
|
from .serializers import PartStarSerializer
|
2018-04-14 04:11:46 +00:00
|
|
|
|
2018-04-28 13:22:12 +00:00
|
|
|
from InvenTree.views import TreeSerializer
|
2019-05-23 12:36:19 +00:00
|
|
|
from InvenTree.helpers import str2bool
|
2018-04-28 13:22:12 +00:00
|
|
|
|
2019-04-13 23:25:46 +00:00
|
|
|
|
2018-04-28 13:22:12 +00:00
|
|
|
class PartCategoryTree(TreeSerializer):
|
|
|
|
|
|
|
|
title = "Parts"
|
|
|
|
model = PartCategory
|
2019-05-09 12:23:56 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def root_url(self):
|
|
|
|
return reverse('part-index')
|
2018-04-28 13:22:12 +00:00
|
|
|
|
2019-06-17 13:52:49 +00:00
|
|
|
def get_items(self):
|
|
|
|
return PartCategory.objects.all().prefetch_related('parts', 'children')
|
|
|
|
|
2018-04-14 04:11:46 +00:00
|
|
|
|
2018-05-04 13:54:57 +00:00
|
|
|
class CategoryList(generics.ListCreateAPIView):
|
2019-04-27 12:18:07 +00:00
|
|
|
""" API endpoint for accessing a list of PartCategory objects.
|
|
|
|
|
|
|
|
- GET: Return a list of PartCategory objects
|
|
|
|
- POST: Create a new PartCategory object
|
|
|
|
"""
|
|
|
|
|
2018-05-04 13:54:57 +00:00
|
|
|
queryset = PartCategory.objects.all()
|
|
|
|
serializer_class = CategorySerializer
|
|
|
|
|
|
|
|
permission_classes = [
|
|
|
|
permissions.IsAuthenticatedOrReadOnly,
|
|
|
|
]
|
|
|
|
|
|
|
|
filter_backends = [
|
|
|
|
DjangoFilterBackend,
|
2019-06-02 09:15:05 +00:00
|
|
|
filters.SearchFilter,
|
2018-05-04 13:54:57 +00:00
|
|
|
filters.OrderingFilter,
|
|
|
|
]
|
|
|
|
|
|
|
|
filter_fields = [
|
|
|
|
'parent',
|
|
|
|
]
|
|
|
|
|
|
|
|
ordering_fields = [
|
|
|
|
'name',
|
|
|
|
]
|
|
|
|
|
|
|
|
ordering = 'name'
|
|
|
|
|
|
|
|
search_fields = [
|
|
|
|
'name',
|
|
|
|
'description',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2019-04-26 15:03:11 +00:00
|
|
|
class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
2019-04-27 12:18:07 +00:00
|
|
|
""" API endpoint for detail view of a single PartCategory object """
|
2019-04-26 15:03:11 +00:00
|
|
|
serializer_class = CategorySerializer
|
|
|
|
queryset = PartCategory.objects.all()
|
|
|
|
|
|
|
|
|
2019-04-28 13:41:47 +00:00
|
|
|
class PartDetail(generics.RetrieveUpdateAPIView):
|
2019-04-27 12:18:07 +00:00
|
|
|
""" API endpoint for detail view of a single Part object """
|
2018-05-04 14:51:17 +00:00
|
|
|
queryset = Part.objects.all()
|
|
|
|
serializer_class = PartSerializer
|
|
|
|
|
|
|
|
permission_classes = [
|
|
|
|
permissions.IsAuthenticatedOrReadOnly,
|
|
|
|
]
|
|
|
|
|
2019-04-13 23:25:46 +00:00
|
|
|
|
2018-04-14 04:11:46 +00:00
|
|
|
class PartList(generics.ListCreateAPIView):
|
2019-04-27 12:18:07 +00:00
|
|
|
""" API endpoint for accessing a list of Part objects
|
|
|
|
|
|
|
|
- GET: Return list of objects
|
|
|
|
- POST: Create a new Part object
|
|
|
|
"""
|
2018-04-14 04:11:46 +00:00
|
|
|
|
|
|
|
serializer_class = PartSerializer
|
2018-04-15 15:02:17 +00:00
|
|
|
|
2019-06-17 15:31:30 +00:00
|
|
|
def list(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Instead of using the DRF serialiser to LIST,
|
|
|
|
we serialize the objects manuually.
|
|
|
|
This turns out to be significantly faster.
|
|
|
|
"""
|
|
|
|
|
|
|
|
queryset = self.filter_queryset(self.get_queryset())
|
|
|
|
|
|
|
|
data = queryset.values(
|
|
|
|
'pk',
|
|
|
|
'category',
|
|
|
|
'image',
|
|
|
|
'name',
|
|
|
|
'IPN',
|
|
|
|
'description',
|
|
|
|
'keywords',
|
|
|
|
'is_template',
|
|
|
|
'URL',
|
|
|
|
'units',
|
|
|
|
'trackable',
|
|
|
|
'assembly',
|
|
|
|
'component',
|
|
|
|
'salable',
|
|
|
|
'active',
|
|
|
|
).annotate(
|
|
|
|
in_stock=Sum('stock_items__quantity'),
|
|
|
|
)
|
|
|
|
|
|
|
|
# TODO - Annotate total being built
|
|
|
|
# TODO - Annotate total on order
|
|
|
|
|
|
|
|
# Reduce the number of lookups we need to do for the part categories
|
|
|
|
categories = {}
|
|
|
|
|
|
|
|
for item in data:
|
|
|
|
|
|
|
|
if item['image']:
|
|
|
|
item['image'] = os.path.join(settings.MEDIA_URL, item['image'])
|
|
|
|
|
|
|
|
cat_id = item['category']
|
|
|
|
|
|
|
|
if cat_id:
|
|
|
|
if cat_id not in categories:
|
|
|
|
categories[cat_id] = PartCategory.objects.get(pk=cat_id).pathstring
|
|
|
|
|
|
|
|
item['category__name'] = categories[cat_id]
|
|
|
|
else:
|
|
|
|
item['category__name'] = None
|
|
|
|
|
|
|
|
return Response(data)
|
|
|
|
|
2019-04-14 01:59:07 +00:00
|
|
|
def get_queryset(self):
|
|
|
|
|
|
|
|
# Does the user wish to filter by category?
|
|
|
|
cat_id = self.request.query_params.get('category', None)
|
|
|
|
|
2019-04-15 12:39:28 +00:00
|
|
|
# Start with all objects
|
|
|
|
parts_list = Part.objects.all()
|
|
|
|
|
2019-04-14 01:59:07 +00:00
|
|
|
if cat_id:
|
2019-04-28 01:09:19 +00:00
|
|
|
try:
|
|
|
|
category = PartCategory.objects.get(pk=cat_id)
|
2019-06-17 15:38:43 +00:00
|
|
|
parts_list = parts_list.filter(category__in=category.getUniqueChildren())
|
2019-04-28 01:09:19 +00:00
|
|
|
except PartCategory.DoesNotExist:
|
|
|
|
pass
|
2019-04-14 02:30:06 +00:00
|
|
|
|
2019-05-19 22:13:22 +00:00
|
|
|
# Ensure that related models are pre-loaded to reduce DB trips
|
|
|
|
parts_list = self.get_serializer_class().setup_eager_loading(parts_list)
|
|
|
|
|
2019-04-15 12:39:28 +00:00
|
|
|
return parts_list
|
2019-04-14 01:59:07 +00:00
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
permission_classes = [
|
|
|
|
permissions.IsAuthenticatedOrReadOnly,
|
|
|
|
]
|
2018-04-14 04:19:03 +00:00
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
filter_backends = [
|
|
|
|
DjangoFilterBackend,
|
|
|
|
filters.SearchFilter,
|
|
|
|
filters.OrderingFilter,
|
|
|
|
]
|
2018-04-14 04:19:03 +00:00
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
filter_fields = [
|
2019-05-26 01:15:41 +00:00
|
|
|
'is_template',
|
|
|
|
'variant_of',
|
2019-06-02 09:46:30 +00:00
|
|
|
'assembly',
|
|
|
|
'component',
|
2019-04-15 14:01:15 +00:00
|
|
|
'trackable',
|
|
|
|
'purchaseable',
|
|
|
|
'salable',
|
2019-04-28 13:15:30 +00:00
|
|
|
'active',
|
2018-04-23 11:10:13 +00:00
|
|
|
]
|
2018-04-14 04:19:03 +00:00
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
ordering_fields = [
|
|
|
|
'name',
|
|
|
|
]
|
2018-04-14 04:19:03 +00:00
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
ordering = 'name'
|
2018-04-14 04:19:03 +00:00
|
|
|
|
2018-04-23 11:18:35 +00:00
|
|
|
search_fields = [
|
2019-04-16 22:19:40 +00:00
|
|
|
'$name',
|
2018-04-23 11:18:35 +00:00
|
|
|
'description',
|
2019-05-06 01:23:55 +00:00
|
|
|
'$IPN',
|
2019-05-14 07:23:20 +00:00
|
|
|
'keywords',
|
2018-04-23 11:18:35 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2019-05-05 00:36:48 +00:00
|
|
|
class PartStarDetail(generics.RetrieveDestroyAPIView):
|
|
|
|
""" API endpoint for viewing or removing a PartStar object """
|
|
|
|
|
|
|
|
queryset = PartStar.objects.all()
|
|
|
|
serializer_class = PartStarSerializer
|
|
|
|
|
|
|
|
|
2019-05-04 23:05:44 +00:00
|
|
|
class PartStarList(generics.ListCreateAPIView):
|
|
|
|
""" API endpoint for accessing a list of PartStar objects.
|
|
|
|
|
|
|
|
- GET: Return list of PartStar objects
|
|
|
|
- POST: Create a new PartStar object
|
|
|
|
"""
|
|
|
|
|
|
|
|
queryset = PartStar.objects.all()
|
|
|
|
serializer_class = PartStarSerializer
|
|
|
|
|
2019-05-04 23:14:12 +00:00
|
|
|
def create(self, request, *args, **kwargs):
|
|
|
|
|
2019-05-05 00:47:57 +00:00
|
|
|
# Override the user field (with the logged-in user)
|
2019-05-05 00:36:48 +00:00
|
|
|
data = request.data.copy()
|
|
|
|
data['user'] = str(request.user.id)
|
|
|
|
|
|
|
|
serializer = self.get_serializer(data=data)
|
2019-05-04 23:14:12 +00:00
|
|
|
|
2019-05-05 00:36:48 +00:00
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
self.perform_create(serializer)
|
|
|
|
headers = self.get_success_headers(serializer.data)
|
|
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
2019-05-04 23:14:12 +00:00
|
|
|
|
2019-05-04 23:05:44 +00:00
|
|
|
permission_classes = [
|
|
|
|
permissions.IsAuthenticatedOrReadOnly,
|
|
|
|
]
|
|
|
|
|
|
|
|
filter_backends = [
|
|
|
|
DjangoFilterBackend,
|
|
|
|
filters.SearchFilter
|
|
|
|
]
|
|
|
|
|
|
|
|
filter_fields = [
|
|
|
|
'part',
|
|
|
|
'user',
|
|
|
|
]
|
|
|
|
|
|
|
|
search_fields = [
|
|
|
|
'partname'
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2019-04-25 14:29:53 +00:00
|
|
|
class BomList(generics.ListCreateAPIView):
|
2019-05-04 23:05:44 +00:00
|
|
|
""" API endpoint for accessing a list of BomItem objects.
|
2019-04-27 12:18:07 +00:00
|
|
|
|
|
|
|
- GET: Return list of BomItem objects
|
|
|
|
- POST: Create a new BomItem object
|
|
|
|
"""
|
2019-05-23 12:36:19 +00:00
|
|
|
|
2018-05-02 13:42:57 +00:00
|
|
|
serializer_class = BomItemSerializer
|
2019-05-23 12:36:19 +00:00
|
|
|
|
|
|
|
def get_serializer(self, *args, **kwargs):
|
|
|
|
|
|
|
|
# Do we wish to include extra detail?
|
|
|
|
part_detail = str2bool(self.request.GET.get('part_detail', None))
|
|
|
|
sub_part_detail = str2bool(self.request.GET.get('sub_part_detail', None))
|
|
|
|
|
|
|
|
kwargs['part_detail'] = part_detail
|
|
|
|
kwargs['sub_part_detail'] = sub_part_detail
|
|
|
|
|
|
|
|
kwargs['context'] = self.get_serializer_context()
|
|
|
|
return self.serializer_class(*args, **kwargs)
|
2018-05-02 13:42:57 +00:00
|
|
|
|
2019-05-19 22:31:03 +00:00
|
|
|
def get_queryset(self):
|
|
|
|
queryset = BomItem.objects.all()
|
|
|
|
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
|
|
|
return queryset
|
|
|
|
|
2018-05-02 13:42:57 +00:00
|
|
|
permission_classes = [
|
|
|
|
permissions.IsAuthenticatedOrReadOnly,
|
|
|
|
]
|
|
|
|
|
|
|
|
filter_backends = [
|
|
|
|
DjangoFilterBackend,
|
|
|
|
filters.SearchFilter,
|
|
|
|
filters.OrderingFilter,
|
|
|
|
]
|
|
|
|
|
|
|
|
filter_fields = [
|
|
|
|
'part',
|
|
|
|
'sub_part'
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2019-04-25 14:29:53 +00:00
|
|
|
class BomDetail(generics.RetrieveUpdateDestroyAPIView):
|
2019-04-27 12:18:07 +00:00
|
|
|
""" API endpoint for detail view of a single BomItem object """
|
2019-04-25 14:29:53 +00:00
|
|
|
|
|
|
|
queryset = BomItem.objects.all()
|
|
|
|
serializer_class = BomItemSerializer
|
|
|
|
|
|
|
|
permission_classes = [
|
|
|
|
permissions.IsAuthenticatedOrReadOnly,
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2019-04-13 23:25:46 +00:00
|
|
|
cat_api_urls = [
|
2019-04-26 15:03:11 +00:00
|
|
|
|
|
|
|
url(r'^(?P<pk>\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'),
|
|
|
|
|
2018-05-04 13:54:57 +00:00
|
|
|
url(r'^$', CategoryList.as_view(), name='api-part-category-list'),
|
|
|
|
]
|
2018-05-02 14:47:03 +00:00
|
|
|
|
2019-04-25 13:56:03 +00:00
|
|
|
|
2019-05-04 23:05:44 +00:00
|
|
|
part_star_api_urls = [
|
2019-05-05 00:36:48 +00:00
|
|
|
url(r'^(?P<pk>\d+)/?', PartStarDetail.as_view(), name='api-part-star-detail'),
|
2019-05-04 23:05:44 +00:00
|
|
|
|
|
|
|
# Catchall
|
|
|
|
url(r'^.*$', PartStarList.as_view(), name='api-part-star-list'),
|
|
|
|
]
|
|
|
|
|
2019-05-18 10:24:09 +00:00
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
part_api_urls = [
|
2018-04-28 13:22:12 +00:00
|
|
|
url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'),
|
|
|
|
|
2018-05-04 13:54:57 +00:00
|
|
|
url(r'^category/', include(cat_api_urls)),
|
2019-05-04 23:05:44 +00:00
|
|
|
url(r'^star/', include(part_star_api_urls)),
|
|
|
|
|
2018-05-04 14:51:17 +00:00
|
|
|
url(r'^(?P<pk>\d+)/', PartDetail.as_view(), name='api-part-detail'),
|
|
|
|
|
2018-04-23 11:10:13 +00:00
|
|
|
url(r'^.*$', PartList.as_view(), name='api-part-list'),
|
|
|
|
]
|
2019-04-25 14:29:53 +00:00
|
|
|
|
2019-05-18 10:24:09 +00:00
|
|
|
|
2019-04-25 14:29:53 +00:00
|
|
|
bom_api_urls = [
|
|
|
|
# BOM Item Detail
|
2019-04-27 04:50:49 +00:00
|
|
|
url('^(?P<pk>\d+)/', BomDetail.as_view(), name='api-bom-detail'),
|
2019-04-25 14:29:53 +00:00
|
|
|
|
|
|
|
# Catch-all
|
|
|
|
url(r'^.*$', BomList.as_view(), name='api-bom-list'),
|
2019-04-26 13:34:15 +00:00
|
|
|
]
|