diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 26ef11466a..9e71cb3e86 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -7,7 +7,7 @@ from part.urls import part_urls, part_cat_urls, part_param_urls, part_param_temp from stock.urls import stock_urls, stock_loc_urls from project.urls import prj_urls, prj_part_urls, prj_cat_urls from supplier.urls import cust_urls, manu_urls, supplier_part_urls, price_break_urls, supplier_urls -import supplier +from track.urls import unique_urls, part_track_urls admin.site.site_header = "InvenTree Admin" @@ -31,7 +31,8 @@ apipatterns = [ url(r'^customer/', include(cust_urls)), # Tracking URLs - url(r'^track/', include('track.urls')), + url(r'^track/', include(part_track_urls)), + url(r'^unique-part/', include(unique_urls)), # Project URLs url(r'^project/', include(prj_urls)), diff --git a/InvenTree/track/admin.py b/InvenTree/track/admin.py index 573e458878..a2908c2e7c 100644 --- a/InvenTree/track/admin.py +++ b/InvenTree/track/admin.py @@ -4,7 +4,7 @@ from .models import UniquePart class UniquePartAdmin(admin.ModelAdmin): - list_display = ('part', 'revision', 'serial', 'status', 'creation_date') + list_display = ('part', 'serial', 'status', 'creation_date') admin.site.register(UniquePart, UniquePartAdmin) diff --git a/InvenTree/track/models.py b/InvenTree/track/models.py index 1bc189ae7c..da2a912df7 100644 --- a/InvenTree/track/models.py +++ b/InvenTree/track/models.py @@ -8,29 +8,6 @@ from supplier.models import Customer 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): """ A unique instance of a Part object. Used for tracking parts based on serial numbers, @@ -41,15 +18,8 @@ class UniquePart(models.Model): # Cannot have multiple parts with same serial number unique_together = ('part', 'serial') - objects = UniquePartManager() - part = models.ForeignKey(Part, on_delete=models.CASCADE) - revision = models.ForeignKey(PartRevision, - on_delete=models.CASCADE, - blank=True, - null=True) - creation_date = models.DateField(auto_now_add=True, editable=False) serial = models.IntegerField() diff --git a/InvenTree/track/serializers.py b/InvenTree/track/serializers.py index 3bd84aa0e7..c2c097c28d 100644 --- a/InvenTree/track/serializers.py +++ b/InvenTree/track/serializers.py @@ -11,7 +11,6 @@ class UniquePartSerializer(serializers.HyperlinkedModelSerializer): model = UniquePart fields = ['url', 'part', - 'revision', 'creation_date', 'serial', # 'createdBy', diff --git a/InvenTree/track/urls.py b/InvenTree/track/urls.py index aaf3b539ce..788fe9b64c 100644 --- a/InvenTree/track/urls.py +++ b/InvenTree/track/urls.py @@ -2,15 +2,14 @@ from django.conf.urls import url, include from . import views -infopatterns = [ +part_track_urls = [ url(r'^(?P[0-9]+)/?$', views.PartTrackingDetail.as_view(), name='parttrackinginfo-detail'), url(r'^\?.*/?$', views.PartTrackingList.as_view()), url(r'^$', views.PartTrackingList.as_view()) ] -urlpatterns = [ - url(r'info/', include(infopatterns)), +unique_urls = [ # Detail for a single unique part url(r'^(?P[0-9]+)/?$', views.UniquePartDetail.as_view(), name='uniquepart-detail'), diff --git a/InvenTree/track/views.py b/InvenTree/track/views.py index 436e98344e..355d9b3449 100644 --- a/InvenTree/track/views.py +++ b/InvenTree/track/views.py @@ -1,4 +1,6 @@ import django_filters +from django_filters.rest_framework import FilterSet, DjangoFilterBackend +from django_filters import NumberFilter from rest_framework import generics, permissions @@ -7,69 +9,95 @@ from .serializers import UniquePartSerializer, PartTrackingInfoSerializer class UniquePartDetail(generics.RetrieveUpdateDestroyAPIView): + """ + + get: + Return a single UniquePart + + post: + Update a UniquePart + + delete: + Remove a UniquePart + + """ queryset = UniquePart.objects.all() serializer_class = UniquePartSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) -class UniquePartFilter(django_filters.rest_framework.FilterSet): +class UniquePartFilter(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') + min_sn = NumberFilter(name='serial', lookup_expr='gte') + max_sn = NumberFilter(name='serial', lookup_expr='lte') + + sn = NumberFilter(name='serial', lookup_expr='exact') + part = NumberFilter(name='part', lookup_expr='exact') + customer = NumberFilter(name='customer', lookup_expr='exact') class Meta: model = UniquePart - fields = ['serial', ] + fields = ['serial', 'part', 'customer'] class UniquePartList(generics.ListCreateAPIView): + """ + get: + Return a list of all UniqueParts + (with optional query filter) + + post: + Create a new UniquePart + """ + + queryset = UniquePart.objects.all() serializer_class = UniquePartSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) - filter_backends = (django_filters.rest_framework.DjangoFilterBackend,) + filter_backends = (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): + """ + + get: + Return a single PartTrackingInfo object + + post: + Update a PartTrackingInfo object + + delete: + Remove a PartTrackingInfo object + """ queryset = PartTrackingInfo.objects.all() serializer_class = PartTrackingInfoSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) -class PartTrackingList(generics.ListCreateAPIView): +class PartTrackingFilter(FilterSet): + part = NumberFilter(name='part', lookup_expr='exact') + class Meta: + model = PartTrackingInfo + fields = ['part'] + + +class PartTrackingList(generics.ListCreateAPIView): + """ + + get: + Return a list of all PartTrackingInfo objects + (with optional query filter) + + post: + Create a new PartTrackingInfo object + """ + + queryset = PartTrackingInfo.objects.all() 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 + filter_backends = (DjangoFilterBackend,) + filter_class = PartTrackingFilter