From 39a6bcdf3e896c2ccbf3d1345acf572736971e6e Mon Sep 17 00:00:00 2001
From: Oliver Walters <oliver.henry.walters@gmail.com>
Date: Fri, 14 Apr 2017 11:46:18 +1000
Subject: [PATCH] Improved Part API

---
 InvenTree/part/models.py      | 37 +++++++++++++++-------------
 InvenTree/part/serializers.py | 12 +++++++++-
 InvenTree/part/urls.py        | 34 ++++++++++++++++++--------
 InvenTree/part/views.py       | 45 +++++++++++++++++++++++++++++++----
 4 files changed, 96 insertions(+), 32 deletions(-)

diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index c479037743..16f136287e 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -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,31 @@ 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 +171,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,
diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py
index 774815ac1a..06ad724b50 100644
--- a/InvenTree/part/serializers.py
+++ b/InvenTree/part/serializers.py
@@ -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')
diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py
index 01db39c149..4fe5a369a4 100644
--- a/InvenTree/part/urls.py
+++ b/InvenTree/part/urls.py
@@ -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()),
 
+    # Part parameters
+    url(r'^parameters/', include(partparampatterns)),
+
+    # Part templates
+    url(r'^templates/', include(parttemplatepatterns)),
+
     # List of all parts
     url(r'^$', views.PartList.as_view())
 ]
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 55d5255789..289f38eb43 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -1,9 +1,10 @@
 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,13 +15,33 @@ 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,)
 
@@ -32,7 +53,7 @@ class PartList(generics.ListCreateAPIView):
     permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
 
 
-class PartCategoryDetail(generics.RetrieveUpdateAPIView):
+class PartCategoryDetail(generics.RetrieveUpdateDestroyAPIView):
     """ Return information on a single PartCategory
     """
     queryset = PartCategory.objects.all()
@@ -47,3 +68,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,)