diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 57eddd53bd..4ab1fd694d 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from rest_framework import serializers -from rest_framework import generics from django.contrib.auth.models import User @@ -24,13 +23,18 @@ class UserSerializerBrief(serializers.ModelSerializer): ] -class DraftRUDView(generics.RetrieveAPIView, generics.UpdateAPIView, generics.DestroyAPIView): +class InvenTreeModelSerializer(serializers.ModelSerializer): + """ + Inherits the standard Django ModelSerializer class, + but also ensures that the underlying model class data are checked on validation. + """ - def perform_update(self, serializer): + def validate(self, data): + # Run any native validation checks first (may throw an ValidationError) + data = super(serializers.ModelSerializer, self).validate(data) - ctx_data = serializer._context['request'].data + # Now ensure the underlying model is correct + instance = self.Meta.model(**data) + instance.clean() - if ctx_data.get('_is_final', False) in [True, u'true', u'True', 1]: - super(generics.UpdateAPIView, self).perform_update(serializer) - else: - pass + return data diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index b5a0f328da..c33689be61 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -11,7 +11,7 @@ from stock.urls import stock_urls from build.urls import build_urls -from part.api import part_api_urls +from part.api import part_api_urls, bom_api_urls from company.api import company_api_urls from stock.api import stock_api_urls from build.api import build_api_urls @@ -30,6 +30,7 @@ admin.site.site_header = "InvenTree Admin" apipatterns = [ url(r'^part/', include(part_api_urls)), + url(r'^bom/', include(bom_api_urls)), url(r'^company/', include(company_api_urls)), url(r'^stock/', include(stock_api_urls)), url(r'^build/', include(build_api_urls)), diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 455a0e76e1..d90cfdb1ee 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -43,7 +43,19 @@ class CompanyList(generics.ListCreateAPIView): ordering = 'name' +class CompanyDetail(generics.RetrieveUpdateDestroyAPIView): + + queryset = Company.objects.all() + serializer_class = CompanySerializer + + permission_classes = [ + permissions.IsAuthenticatedOrReadOnly, + ] + + company_api_urls = [ + url(r'^(?P\d+)/?', CompanyDetail.as_view(), name='api-company-detail'), + url(r'^.*$', CompanyList.as_view(), name='api-company-list'), ] diff --git a/InvenTree/company/templates/company/detail_part.html b/InvenTree/company/templates/company/detail_part.html index c3d9f0a8b3..1db55c12d5 100644 --- a/InvenTree/company/templates/company/detail_part.html +++ b/InvenTree/company/templates/company/detail_part.html @@ -50,10 +50,10 @@ }, { sortable: true, - field: 'part', + field: 'part_name', title: 'Part', formatter: function(value, row, index, field) { - return renderLink(value.name, value.url + 'suppliers/'); + return renderLink(value, '/part/' + row.part + '/suppliers/'); } }, { diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 5601850e1c..96606c643a 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -17,7 +17,6 @@ from .serializers import SupplierPartSerializer, SupplierPriceBreakSerializer from .serializers import CategorySerializer from InvenTree.views import TreeSerializer -from InvenTree.serializers import DraftRUDView class PartCategoryTree(TreeSerializer): @@ -56,7 +55,7 @@ class CategoryList(generics.ListCreateAPIView): ] -class PartDetail(DraftRUDView): +class PartDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Part.objects.all() serializer_class = PartSerializer @@ -125,7 +124,7 @@ class PartList(generics.ListCreateAPIView): ] -class BomList(generics.ListAPIView): +class BomList(generics.ListCreateAPIView): queryset = BomItem.objects.all() serializer_class = BomItemSerializer @@ -146,7 +145,17 @@ class BomList(generics.ListAPIView): ] -class SupplierPartList(generics.ListAPIView): +class BomDetail(generics.RetrieveUpdateDestroyAPIView): + + queryset = BomItem.objects.all() + serializer_class = BomItemSerializer + + permission_classes = [ + permissions.IsAuthenticatedOrReadOnly, + ] + + +class SupplierPartList(generics.ListCreateAPIView): queryset = SupplierPart.objects.all() serializer_class = SupplierPartSerializer @@ -167,6 +176,16 @@ class SupplierPartList(generics.ListAPIView): ] +class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView): + + queryset = SupplierPart.objects.all() + serializer_class = SupplierPartSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + read_only_fields = [ + ] + + class SupplierPriceBreakList(generics.ListCreateAPIView): queryset = SupplierPriceBreak.objects.all() @@ -189,16 +208,31 @@ cat_api_urls = [ url(r'^$', CategoryList.as_view(), name='api-part-category-list'), ] +supplier_part_api_urls = [ + + url(r'^(?P\d+)/?', SupplierPartDetail.as_view(), name='api-supplier-part-detail'), + + # Catch anything else + url(r'^.*$', SupplierPartList.as_view(), name='api-part-supplier-list'), +] + part_api_urls = [ url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'), url(r'^category/', include(cat_api_urls)), + url(r'^supplier/', include(supplier_part_api_urls)), url(r'^price-break/?', SupplierPriceBreakList.as_view(), name='api-part-supplier-price'), - url(r'^supplier/?', SupplierPartList.as_view(), name='api-part-supplier-list'), - url(r'^bom/?', BomList.as_view(), name='api-bom-list'), url(r'^(?P\d+)/', PartDetail.as_view(), name='api-part-detail'), url(r'^.*$', PartList.as_view(), name='api-part-list'), ] + +bom_api_urls = [ + # BOM Item Detail + url('^(?P\d+)/?', BomDetail.as_view(), name='api-bom-detail'), + + # Catch-all + url(r'^.*$', BomList.as_view(), name='api-bom-list'), +] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 6c9dc4ec54..fa04b48c9b 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -337,7 +337,7 @@ class BomItem(models.Model): """ def get_absolute_url(self): - return reverse('bom-detail', kwargs={'pk': self.id}) + return reverse('bom-item-detail', kwargs={'pk': self.id}) # A link to the parent part # Each part will get a reverse lookup field 'bom_items' @@ -359,11 +359,12 @@ class BomItem(models.Model): # A part cannot refer to itself in its BOM if self.part == self.sub_part: - raise ValidationError(_('A part cannot contain itself as a BOM item')) + raise ValidationError({'sub_part': _('Part cannot be added to its own Bill of Materials')}) + # Test for simple recursion for item in self.sub_part.bom_items.all(): if self.part == item.sub_part: - raise ValidationError(_("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(p1=str(self.part), p2=str(self.sub_part)))) + raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(p1=str(self.part), p2=str(self.sub_part)))}) class Meta: verbose_name = "BOM Item" diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 378dbe4732..68066645bd 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -3,7 +3,7 @@ from rest_framework import serializers from .models import Part, PartCategory, BomItem from .models import SupplierPart, SupplierPriceBreak -from company.serializers import CompanyBriefSerializer +from InvenTree.serializers import InvenTreeModelSerializer class CategorySerializer(serializers.ModelSerializer): @@ -67,20 +67,22 @@ class PartSerializer(serializers.ModelSerializer): ] -class BomItemSerializer(serializers.ModelSerializer): +class BomItemSerializer(InvenTreeModelSerializer): - url = serializers.CharField(source='get_absolute_url', read_only=True) + # url = serializers.CharField(source='get_absolute_url', read_only=True) - part = PartBriefSerializer(many=False, read_only=True) - sub_part = PartBriefSerializer(many=False, read_only=True) + part_detail = PartBriefSerializer(source='part', many=False, read_only=True) + sub_part_detail = PartBriefSerializer(source='sub_part', many=False, read_only=True) class Meta: model = BomItem fields = [ 'pk', - 'url', + # 'url', 'part', + 'part_detail', 'sub_part', + 'sub_part_detail', 'quantity', 'note', ] @@ -90,8 +92,9 @@ class SupplierPartSerializer(serializers.ModelSerializer): url = serializers.CharField(source='get_absolute_url', read_only=True) - part = PartBriefSerializer(many=False, read_only=True) - supplier = CompanyBriefSerializer(many=False, read_only=True) + part_name = serializers.CharField(source='part.name', read_only=True) + + supplier_name = serializers.CharField(source='supplier.name', read_only=True) class Meta: model = SupplierPart @@ -99,7 +102,9 @@ class SupplierPartSerializer(serializers.ModelSerializer): 'pk', 'url', 'part', + 'part_name', 'supplier', + 'supplier_name', 'SKU', 'manufacturer', 'MPN', diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 7524874a28..f0bdc49df2 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -14,6 +14,7 @@