mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 12:06:44 +00:00
commit
146ad5e9ae
@ -17,13 +17,13 @@ def Inventree404(self):
|
|||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^stock/', include('stock.urls')),
|
url(r'^stock/?', include('stock.urls')),
|
||||||
url(r'^part/', include('part.urls')),
|
url(r'^part/?', include('part.urls')),
|
||||||
url(r'^supplier/', include('supplier.urls')),
|
url(r'^supplier/?', include('supplier.urls')),
|
||||||
url(r'^track/', include('track.urls')),
|
url(r'^track/?', include('track.urls')),
|
||||||
url(r'^project/', include('project.urls')),
|
url(r'^project/?', include('project.urls')),
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/?', admin.site.urls),
|
||||||
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
|
url(r'^auth/?', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
|
|
||||||
# Any other URL
|
# Any other URL
|
||||||
url(r'', Inventree404)
|
url(r'', Inventree404)
|
||||||
|
@ -149,11 +149,9 @@ class PartParameterManager(models.Manager):
|
|||||||
part_id = kwargs['part']
|
part_id = kwargs['part']
|
||||||
template_id = kwargs['template']
|
template_id = kwargs['template']
|
||||||
|
|
||||||
try:
|
params = self.filter(part=part_id, template=template_id)
|
||||||
params = self.filter(part=part_id, template=template_id)
|
if len(params) > 0:
|
||||||
return params[0]
|
return params[0]
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return super(PartParameterManager, self).create(*args, **kwargs)
|
return super(PartParameterManager, self).create(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -32,16 +32,11 @@ class PartSerializer(serializers.ModelSerializer):
|
|||||||
'stock')
|
'stock')
|
||||||
|
|
||||||
|
|
||||||
class PartCategoryBriefSerializer(serializers.ModelSerializer):
|
class PartCategorySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
children = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
model = PartCategory
|
|
||||||
fields = ('pk',
|
|
||||||
'name',
|
|
||||||
'description')
|
|
||||||
|
|
||||||
|
parts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
class PartCategoryDetailSerializer(serializers.ModelSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PartCategory
|
model = PartCategory
|
||||||
@ -49,7 +44,9 @@ class PartCategoryDetailSerializer(serializers.ModelSerializer):
|
|||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'parent',
|
'parent',
|
||||||
'path')
|
'path',
|
||||||
|
'children',
|
||||||
|
'parts')
|
||||||
|
|
||||||
|
|
||||||
class PartTemplateSerializer(serializers.ModelSerializer):
|
class PartTemplateSerializer(serializers.ModelSerializer):
|
||||||
|
@ -5,7 +5,7 @@ from rest_framework import generics, permissions
|
|||||||
from InvenTree.models import FilterChildren
|
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 PartCategorySerializer
|
||||||
from .serializers import PartParameterSerializer
|
from .serializers import PartParameterSerializer
|
||||||
from .serializers import PartTemplateSerializer
|
from .serializers import PartTemplateSerializer
|
||||||
|
|
||||||
@ -32,13 +32,6 @@ class PartParamList(generics.ListCreateAPIView):
|
|||||||
serializer_class = PartParameterSerializer
|
serializer_class = PartParameterSerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
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):
|
class PartParamDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" Detail view of a single PartParameter
|
""" Detail view of a single PartParameter
|
||||||
@ -83,7 +76,7 @@ class PartCategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
""" Return information on a single PartCategory
|
""" Return information on a single PartCategory
|
||||||
"""
|
"""
|
||||||
queryset = PartCategory.objects.all()
|
queryset = PartCategory.objects.all()
|
||||||
serializer_class = PartCategoryDetailSerializer
|
serializer_class = PartCategorySerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
|
||||||
@ -102,7 +95,7 @@ class PartCategoryList(generics.ListCreateAPIView):
|
|||||||
return categories
|
return categories
|
||||||
|
|
||||||
queryset = PartCategory.objects.filter(parent=None)
|
queryset = PartCategory.objects.filter(parent=None)
|
||||||
serializer_class = PartCategoryDetailSerializer
|
serializer_class = PartCategorySerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,17 +29,11 @@ class Project(models.Model):
|
|||||||
|
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
description = models.CharField(max_length=500, blank=True)
|
description = models.CharField(max_length=500, blank=True)
|
||||||
category = models.ForeignKey(ProjectCategory, on_delete=models.CASCADE)
|
category = models.ForeignKey(ProjectCategory, on_delete=models.CASCADE, related_name='projects')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
|
||||||
def projectParts(self):
|
|
||||||
""" Return a list of all project parts associated with this project
|
|
||||||
"""
|
|
||||||
return self.projectpart_set.all()
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectPartManager(models.Manager):
|
class ProjectPartManager(models.Manager):
|
||||||
""" Manager for handling ProjectParts
|
""" Manager for handling ProjectParts
|
||||||
@ -56,11 +50,9 @@ class ProjectPartManager(models.Manager):
|
|||||||
project_id = kwargs['project']
|
project_id = kwargs['project']
|
||||||
part_id = kwargs['part']
|
part_id = kwargs['part']
|
||||||
|
|
||||||
try:
|
project_parts = self.filter(project=project_id, part=part_id)
|
||||||
project_parts = self.filter(project=project_id, part=part_id)
|
if len(project_parts) > 0:
|
||||||
return project_parts[0]
|
return project_parts[0]
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return super(ProjectPartManager, self).create(*args, **kwargs)
|
return super(ProjectPartManager, self).create(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -26,14 +26,11 @@ class ProjectSerializer(serializers.ModelSerializer):
|
|||||||
'category')
|
'category')
|
||||||
|
|
||||||
|
|
||||||
class ProjectCategoryBriefSerializer(serializers.ModelSerializer):
|
class ProjectCategorySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
children = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
model = ProjectCategory
|
|
||||||
fields = ('pk', 'name', 'description')
|
|
||||||
|
|
||||||
|
projects = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
class ProjectCategoryDetailSerializer(serializers.ModelSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProjectCategory
|
model = ProjectCategory
|
||||||
@ -41,4 +38,6 @@ class ProjectCategoryDetailSerializer(serializers.ModelSerializer):
|
|||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'parent',
|
'parent',
|
||||||
'path')
|
'path',
|
||||||
|
'children',
|
||||||
|
'projects')
|
||||||
|
@ -3,7 +3,7 @@ from rest_framework import generics, permissions
|
|||||||
from InvenTree.models import FilterChildren
|
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 ProjectCategorySerializer
|
||||||
from .serializers import ProjectPartSerializer
|
from .serializers import ProjectPartSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ class ProjectCategoryDetail(generics.RetrieveUpdateAPIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = ProjectCategory.objects.all()
|
queryset = ProjectCategory.objects.all()
|
||||||
serializer_class = ProjectCategoryDetailSerializer
|
serializer_class = ProjectCategorySerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class ProjectCategoryList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
return categories
|
return categories
|
||||||
|
|
||||||
serializer_class = ProjectCategoryDetailSerializer
|
serializer_class = ProjectCategorySerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
|
||||||
@ -82,13 +82,6 @@ class ProjectPartsList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
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):
|
class ProjectPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" Detail for a single project part
|
""" Detail for a single project part
|
||||||
|
@ -45,13 +45,6 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
return items
|
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.RetrieveUpdateDestroyAPIView):
|
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" Return information on a specific stock location
|
""" Return information on a specific stock location
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
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
|
||||||
@ -7,12 +8,37 @@ from supplier.models import Customer
|
|||||||
from part.models import Part, PartRevision
|
from part.models import Part, PartRevision
|
||||||
|
|
||||||
|
|
||||||
|
class UniquePartManager(models.Manager):
|
||||||
|
""" Ensures UniqueParts are correctly handled
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create(self, *args, **kwargs):
|
||||||
|
|
||||||
|
part_id = kwargs['part']
|
||||||
|
sn = kwargs.get('serial', None)
|
||||||
|
|
||||||
|
if not sn:
|
||||||
|
raise ValidationError(_("Serial number must be supplied"))
|
||||||
|
|
||||||
|
if not isinstance(sn, int):
|
||||||
|
raise ValidationError(_("Serial number must be integer"))
|
||||||
|
|
||||||
|
# Does a part already exists with this serial number?
|
||||||
|
parts = self.filter(part=part_id, serial=sn)
|
||||||
|
if len(parts) > 0:
|
||||||
|
raise ValidationError(_("Matching part and serial number found!"))
|
||||||
|
|
||||||
|
return super(UniquePartManager, self).create(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UniquePart(models.Model):
|
class UniquePart(models.Model):
|
||||||
""" A unique instance of a Part object.
|
""" A unique instance of a Part object.
|
||||||
Used for tracking parts based on serial numbers,
|
Used for tracking parts based on serial numbers,
|
||||||
and tracking all events in the life of a part
|
and tracking all events in the life of a part
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
objects = UniquePartManager()
|
||||||
|
|
||||||
part = models.ForeignKey(Part, on_delete=models.CASCADE)
|
part = models.ForeignKey(Part, on_delete=models.CASCADE)
|
||||||
|
|
||||||
revision = models.ForeignKey(PartRevision,
|
revision = models.ForeignKey(PartRevision,
|
||||||
@ -50,6 +76,17 @@ 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
|
||||||
@ -57,7 +94,6 @@ class PartTrackingInfo(models.Model):
|
|||||||
a new PartTrackingInfo object should be created.
|
a new PartTrackingInfo object should be created.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
part = models.ForeignKey(UniquePart, on_delete=models.CASCADE)
|
part = models.ForeignKey(UniquePart, on_delete=models.CASCADE, related_name='tracking_info')
|
||||||
date = models.DateField(auto_now_add=True,
|
date = models.DateField(auto_now_add=True, editable=False)
|
||||||
editable=False)
|
|
||||||
notes = models.CharField(max_length=500)
|
notes = models.CharField(max_length=500)
|
||||||
|
27
InvenTree/track/serializers.py
Normal file
27
InvenTree/track/serializers.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import UniquePart, PartTrackingInfo
|
||||||
|
|
||||||
|
|
||||||
|
class UniquePartSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
tracking_info = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UniquePart
|
||||||
|
fields = ['pk',
|
||||||
|
'part',
|
||||||
|
'revision',
|
||||||
|
'creation_date',
|
||||||
|
'serial',
|
||||||
|
'createdBy',
|
||||||
|
'customer',
|
||||||
|
'status',
|
||||||
|
'tracking_info']
|
||||||
|
|
||||||
|
|
||||||
|
class PartTrackingInfoSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PartTrackingInfo
|
||||||
|
fields = '__all__'
|
@ -1,7 +1,19 @@
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url, include
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
infopatterns = [
|
||||||
url(r'^$', views.index, name='index')
|
url(r'^(?P<pk>[0-9]+)/?$', views.PartTrackingDetail.as_view()),
|
||||||
|
|
||||||
|
url(r'^\?*[^/]*/?$', views.PartTrackingList.as_view())
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'info/?', include(infopatterns)),
|
||||||
|
|
||||||
|
# Detail for a single unique part
|
||||||
|
url(r'^(?P<pk>[0-9]+)$', views.UniquePartDetail.as_view()),
|
||||||
|
|
||||||
|
# List all unique parts, with optional filters
|
||||||
|
url(r'^\?*[^/]*/?$', views.UniquePartList.as_view()),
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,75 @@
|
|||||||
from django.http import HttpResponse
|
import django_filters
|
||||||
|
|
||||||
|
from rest_framework import generics, permissions
|
||||||
|
|
||||||
|
from .models import UniquePart, PartTrackingInfo
|
||||||
|
from .serializers import UniquePartSerializer, PartTrackingInfoSerializer
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
class UniquePartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
return HttpResponse("This is the Tracking page")
|
|
||||||
|
queryset = UniquePart.objects.all()
|
||||||
|
serializer_class = UniquePartSerializer
|
||||||
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
|
||||||
|
class UniquePartFilter(django_filters.rest_framework.FilterSet):
|
||||||
|
# Filter based on serial number
|
||||||
|
min_sn = django_filters.NumberFilter(name='serial', lookup_expr='gte')
|
||||||
|
max_sn = django_filters.NumberFilter(name='serial', lookup_expr='lte')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UniquePart
|
||||||
|
fields = ['serial', ]
|
||||||
|
|
||||||
|
|
||||||
|
class UniquePartList(generics.ListCreateAPIView):
|
||||||
|
|
||||||
|
serializer_class = UniquePartSerializer
|
||||||
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
|
||||||
|
filter_class = UniquePartFilter
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
parts = UniquePart.objects.all()
|
||||||
|
query = self.request.query_params
|
||||||
|
|
||||||
|
# Filter by associated part
|
||||||
|
part_id = query.get('part', None)
|
||||||
|
if part_id:
|
||||||
|
parts = parts.filter(part=part_id)
|
||||||
|
|
||||||
|
# Filter by serial number
|
||||||
|
sn = query.get('sn', None)
|
||||||
|
if sn:
|
||||||
|
parts = parts.filter(serial=sn)
|
||||||
|
|
||||||
|
# Filter by customer
|
||||||
|
customer = query.get('customer', None)
|
||||||
|
if customer:
|
||||||
|
parts = parts.filter(customer=customer)
|
||||||
|
|
||||||
|
return parts
|
||||||
|
|
||||||
|
|
||||||
|
class PartTrackingDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
|
queryset = PartTrackingInfo.objects.all()
|
||||||
|
serializer_class = PartTrackingInfoSerializer
|
||||||
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
|
||||||
|
class PartTrackingList(generics.ListCreateAPIView):
|
||||||
|
|
||||||
|
serializer_class = PartTrackingInfoSerializer
|
||||||
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
tracking = PartTrackingInfo.objects.all()
|
||||||
|
query = self.request.query_params
|
||||||
|
|
||||||
|
part_id = query.get('part', None)
|
||||||
|
if part_id:
|
||||||
|
tracking = tracking.filter(part=part_id)
|
||||||
|
|
||||||
|
return tracking
|
||||||
|
Loading…
x
Reference in New Issue
Block a user