mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
commit
92f4d47f74
@ -32,6 +32,7 @@ ALLOWED_HOSTS = []
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django_filters',
|
||||
'rest_framework',
|
||||
|
||||
# Core django modules
|
||||
|
@ -95,13 +95,8 @@ class PartParameterTemplate(models.Model):
|
||||
A PartParameterTemplate can be optionally associated with a PartCategory
|
||||
"""
|
||||
name = models.CharField(max_length=20)
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
units = models.CharField(max_length=10, blank=True)
|
||||
|
||||
default_value = models.CharField(max_length=50, blank=True)
|
||||
default_min = models.CharField(max_length=50, blank=True)
|
||||
default_max = models.CharField(max_length=50, blank=True)
|
||||
|
||||
# Parameter format
|
||||
PARAM_NUMERIC = 10
|
||||
PARAM_TEXT = 20
|
||||
@ -143,10 +138,32 @@ class CategoryParameterLink(models.Model):
|
||||
verbose_name_plural = "Category Parameters"
|
||||
|
||||
|
||||
class PartParameterManager(models.Manager):
|
||||
""" Manager for handling PartParameter objects
|
||||
"""
|
||||
|
||||
def create(self, *args, **kwargs):
|
||||
""" Prevent creation of duplicate PartParameter
|
||||
"""
|
||||
|
||||
part_id = kwargs['part']
|
||||
template_id = kwargs['template']
|
||||
|
||||
try:
|
||||
params = self.filter(part=part_id, template=template_id)
|
||||
return params[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
return super(PartParameterManager, self).create(*args, **kwargs)
|
||||
|
||||
|
||||
class PartParameter(models.Model):
|
||||
""" PartParameter is associated with a single part
|
||||
"""
|
||||
|
||||
objects = PartParameterManager()
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters')
|
||||
template = models.ForeignKey(PartParameterTemplate)
|
||||
|
||||
@ -155,17 +172,6 @@ class PartParameter(models.Model):
|
||||
min_value = models.CharField(max_length=50, blank=True)
|
||||
max_value = models.CharField(max_length=50, blank=True)
|
||||
|
||||
# Prevent multiple parameters of the same template
|
||||
# from being added to the same part
|
||||
def save(self, *args, **kwargs):
|
||||
params = PartParameter.objects.filter(part=self.part, template=self.template)
|
||||
if len(params) > 1:
|
||||
return
|
||||
if len(params) == 1 and params[0].id != self.id:
|
||||
return
|
||||
|
||||
super(PartParameter, self).save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return "{name} : {val}{units}".format(
|
||||
name=self.template.name,
|
||||
|
@ -1,6 +1,6 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Part, PartCategory, PartParameter
|
||||
from .models import Part, PartCategory, PartParameter, PartParameterTemplate
|
||||
|
||||
|
||||
class PartParameterSerializer(serializers.ModelSerializer):
|
||||
@ -58,3 +58,13 @@ class PartCategoryDetailSerializer(serializers.ModelSerializer):
|
||||
'path',
|
||||
'children',
|
||||
'parts')
|
||||
|
||||
|
||||
class PartTemplateSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PartParameterTemplate
|
||||
fields = ('pk',
|
||||
'name',
|
||||
'units',
|
||||
'format')
|
||||
|
@ -16,16 +16,24 @@ categorypatterns = [
|
||||
url(r'^$', views.PartCategoryList.as_view())
|
||||
]
|
||||
|
||||
""" URL patterns associated with a particular part:
|
||||
/part/<pk> -> Detail view of a given part
|
||||
/part/<pk>/parameters -> List parameters associated with a part
|
||||
"""
|
||||
partdetailpatterns = [
|
||||
# Single part detail
|
||||
url(r'^$', views.PartDetail.as_view()),
|
||||
partparampatterns = [
|
||||
# Detail of a single part parameter
|
||||
url(r'^(?P<pk>[0-9]+)/$', views.PartParamDetail.as_view()),
|
||||
|
||||
# Parameters associated with a particular part
|
||||
url(r'^\?[^/]*/$', views.PartParamList.as_view()),
|
||||
|
||||
# All part parameters
|
||||
url(r'^$', views.PartParamList.as_view()),
|
||||
]
|
||||
|
||||
parttemplatepatterns = [
|
||||
# Detail of a single part field template
|
||||
url(r'^(?P<pk>[0-9]+)/$', views.PartTemplateDetail.as_view()),
|
||||
|
||||
# List all part field templates
|
||||
url(r'^$', views.PartTemplateList.as_view())
|
||||
|
||||
# View part parameters
|
||||
url(r'parameters/$', views.PartParameters.as_view())
|
||||
]
|
||||
|
||||
""" Top-level URL patterns for the Part app:
|
||||
@ -36,11 +44,17 @@ partdetailpatterns = [
|
||||
"""
|
||||
urlpatterns = [
|
||||
# Individual part
|
||||
url(r'^(?P<pk>[0-9]+)/', include(partdetailpatterns)),
|
||||
url(r'^(?P<pk>[0-9]+)/$', views.PartDetail.as_view()),
|
||||
|
||||
# Part categories
|
||||
url(r'^category/', views.PartCategoryList.as_view()),
|
||||
|
||||
# List of all parts
|
||||
url(r'^$', views.PartList.as_view())
|
||||
# Part parameters
|
||||
url(r'^parameters/', include(partparampatterns)),
|
||||
|
||||
# Part templates
|
||||
url(r'^templates/', include(parttemplatepatterns)),
|
||||
|
||||
# List parts with optional filters
|
||||
url(r'^\?*[^/]*/?$', views.PartList.as_view()),
|
||||
]
|
||||
|
@ -1,9 +1,12 @@
|
||||
# import django_filters
|
||||
|
||||
from rest_framework import generics, permissions
|
||||
|
||||
from .models import PartCategory, Part, PartParameter
|
||||
from .models import PartCategory, Part, PartParameter, PartParameterTemplate
|
||||
from .serializers import PartSerializer
|
||||
from .serializers import PartCategoryDetailSerializer
|
||||
from .serializers import PartParameterSerializer
|
||||
from .serializers import PartTemplateSerializer
|
||||
|
||||
|
||||
class PartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
@ -14,25 +17,69 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class PartParameters(generics.ListCreateAPIView):
|
||||
class PartParamList(generics.ListCreateAPIView):
|
||||
""" Return all parameters associated with a particular part
|
||||
"""
|
||||
def get_queryset(self):
|
||||
part_id = self.kwargs['pk']
|
||||
return PartParameter.objects.filter(part=part_id)
|
||||
part_id = self.request.query_params.get('part', None)
|
||||
|
||||
if part_id:
|
||||
return PartParameter.objects.filter(part=part_id)
|
||||
else:
|
||||
return PartParameter.objects.all()
|
||||
|
||||
serializer_class = PartParameterSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# Ensure part link is set correctly
|
||||
part_id = self.request.query_params.get('part', None)
|
||||
if part_id:
|
||||
request.data['part'] = part_id
|
||||
return super(PartParamList, self).create(request, *args, **kwargs)
|
||||
|
||||
|
||||
class PartParamDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
""" Detail view of a single PartParameter
|
||||
"""
|
||||
|
||||
queryset = PartParameter.objects.all()
|
||||
serializer_class = PartParameterSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
"""
|
||||
class PartFilter(django_filters.rest_framework.FilterSet):
|
||||
min_stock = django_filters.NumberFilter(name="stock", lookup_expr="gte")
|
||||
max_stock = django_filters.NumberFilter(name="stock", lookup_expr="lte")
|
||||
|
||||
class Meta:
|
||||
model = Part
|
||||
fields = ['stock']
|
||||
"""
|
||||
|
||||
|
||||
class PartList(generics.ListCreateAPIView):
|
||||
""" Display a list of parts, with optional filters
|
||||
Filters are specified in the url, e.g.
|
||||
/part/?category=127
|
||||
/part/?min_stock=100
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
parts = Part.objects.all()
|
||||
|
||||
cat_id = self.request.query_params.get('category', None)
|
||||
if cat_id:
|
||||
parts = parts.filter(category=cat_id)
|
||||
|
||||
return parts
|
||||
|
||||
queryset = Part.objects.all()
|
||||
serializer_class = PartSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class PartCategoryDetail(generics.RetrieveUpdateAPIView):
|
||||
class PartCategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
""" Return information on a single PartCategory
|
||||
"""
|
||||
queryset = PartCategory.objects.all()
|
||||
@ -47,3 +94,17 @@ class PartCategoryList(generics.ListCreateAPIView):
|
||||
queryset = PartCategory.objects.filter(parent=None)
|
||||
serializer_class = PartCategoryDetailSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class PartTemplateDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
queryset = PartParameterTemplate.objects.all()
|
||||
serializer_class = PartTemplateSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
|
||||
class PartTemplateList(generics.ListCreateAPIView):
|
||||
|
||||
queryset = PartParameterTemplate.objects.all()
|
||||
serializer_class = PartTemplateSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
@ -41,12 +41,38 @@ class Project(models.Model):
|
||||
return self.projectpart_set.all()
|
||||
|
||||
|
||||
class ProjectPartManager(models.Manager):
|
||||
""" Manager for handling ProjectParts
|
||||
"""
|
||||
|
||||
def create(self, *args, **kwargs):
|
||||
""" Test for validity of new ProjectPart before actually creating it.
|
||||
If a ProjectPart already exists that references the same:
|
||||
a) Part
|
||||
b) Project
|
||||
then return THAT project instead.
|
||||
"""
|
||||
|
||||
project_id = kwargs['project']
|
||||
part_id = kwargs['part']
|
||||
|
||||
try:
|
||||
project_parts = self.filter(project=project_id, part=part_id)
|
||||
return project_parts[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
return super(ProjectPartManager, self).create(*args, **kwargs)
|
||||
|
||||
|
||||
class ProjectPart(models.Model):
|
||||
""" A project part associates a single part with a project
|
||||
The quantity of parts required for a single-run of that project is stored.
|
||||
The overage is the number of extra parts that are generally used for a single run.
|
||||
"""
|
||||
|
||||
objects = ProjectPartManager()
|
||||
|
||||
part = models.ForeignKey(Part, on_delete=models.CASCADE)
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
quantity = models.PositiveIntegerField(default=1)
|
||||
|
@ -9,9 +9,14 @@ from . import views
|
||||
projectdetailpatterns = [
|
||||
# Single project detail
|
||||
url(r'^$', views.ProjectDetail.as_view()),
|
||||
]
|
||||
|
||||
# Parts associated with a project
|
||||
url(r'^parts/$', views.ProjectPartsList.as_view()),
|
||||
projectpartpatterns = [
|
||||
# Detail of a single project part
|
||||
url(r'^(?P<pk>[0-9]+)/$', views.ProjectPartDetail.as_view()),
|
||||
|
||||
# List project parts, with optional filters
|
||||
url(r'^\?*[^/]*/?$', views.ProjectPartsList.as_view()),
|
||||
]
|
||||
|
||||
projectcategorypatterns = [
|
||||
@ -23,7 +28,6 @@ projectcategorypatterns = [
|
||||
|
||||
# Create a new category
|
||||
url(r'^new/$', views.NewProjectCategory.as_view())
|
||||
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
@ -34,6 +38,9 @@ urlpatterns = [
|
||||
# List of all projects
|
||||
url(r'^$', views.ProjectList.as_view()),
|
||||
|
||||
# Project parts
|
||||
url(r'^parts/', include(projectpartpatterns)),
|
||||
|
||||
# Project categories
|
||||
url(r'^category/', include(projectcategorypatterns)),
|
||||
]
|
||||
|
@ -6,7 +6,9 @@ from .serializers import ProjectCategoryDetailSerializer
|
||||
from .serializers import ProjectPartSerializer
|
||||
|
||||
|
||||
class ProjectDetail(generics.RetrieveUpdateAPIView):
|
||||
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
""" Project details
|
||||
"""
|
||||
|
||||
queryset = Project.objects.all()
|
||||
serializer_class = ProjectSerializer
|
||||
@ -14,6 +16,8 @@ class ProjectDetail(generics.RetrieveUpdateAPIView):
|
||||
|
||||
|
||||
class ProjectList(generics.ListCreateAPIView):
|
||||
""" List all projects
|
||||
"""
|
||||
|
||||
queryset = Project.objects.all()
|
||||
serializer_class = ProjectSerializer
|
||||
@ -28,6 +32,8 @@ class NewProjectCategory(generics.CreateAPIView):
|
||||
|
||||
|
||||
class ProjectCategoryDetail(generics.RetrieveUpdateAPIView):
|
||||
""" Project details
|
||||
"""
|
||||
|
||||
queryset = ProjectCategory.objects.all()
|
||||
serializer_class = ProjectCategoryDetailSerializer
|
||||
@ -35,6 +41,9 @@ class ProjectCategoryDetail(generics.RetrieveUpdateAPIView):
|
||||
|
||||
|
||||
class ProjectCategoryList(generics.ListCreateAPIView):
|
||||
""" Top-level project categories.
|
||||
Projects are considered top-level if they do not have a parent
|
||||
"""
|
||||
|
||||
queryset = ProjectCategory.objects.filter(parent=None)
|
||||
serializer_class = ProjectCategoryDetailSerializer
|
||||
@ -42,10 +51,32 @@ class ProjectCategoryList(generics.ListCreateAPIView):
|
||||
|
||||
|
||||
class ProjectPartsList(generics.ListCreateAPIView):
|
||||
""" List all parts associated with a particular project
|
||||
"""
|
||||
|
||||
serializer_class = ProjectPartSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
||||
def get_queryset(self):
|
||||
project_id = self.kwargs['pk']
|
||||
return ProjectPart.objects.filter(project=project_id)
|
||||
project_id = self.request.query_params.get('project', None)
|
||||
|
||||
if project_id:
|
||||
return ProjectPart.objects.filter(project=project_id)
|
||||
else:
|
||||
return ProjectPart.objects.all()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# Ensure project link is set correctly
|
||||
prj_id = self.request.query_params.get('project', None)
|
||||
if prj_id:
|
||||
request.data['project'] = prj_id
|
||||
return super(ProjectPartsList, self).create(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
""" Detail for a single project part
|
||||
"""
|
||||
|
||||
queryset = ProjectPart.objects.all()
|
||||
serializer_class = ProjectPartSerializer
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
@ -10,5 +10,6 @@ urlpatterns = [
|
||||
url(r'^location/(?P<pk>[0-9]+)$', views.LocationDetail.as_view()),
|
||||
|
||||
# List all top-level locations
|
||||
url(r'^location/$', views.LocationList.as_view())
|
||||
url(r'^location/$', views.LocationList.as_view()),
|
||||
url(r'^$', views.LocationList.as_view())
|
||||
]
|
||||
|
@ -5,7 +5,7 @@ from .models import StockLocation, StockItem
|
||||
from .serializers import StockItemSerializer, LocationDetailSerializer
|
||||
|
||||
|
||||
class PartStockDetail(generics.ListAPIView):
|
||||
class PartStockDetail(generics.ListCreateAPIView):
|
||||
""" Return a list of all stockitems for a given part
|
||||
"""
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
Django==1.11
|
||||
djangorestframework==3.6.2
|
||||
django_filter==1.0.2
|
||||
|
Loading…
Reference in New Issue
Block a user