From ed3ae30248fc8a314723d5cfccfaa590eb60e215 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 27 Apr 2019 22:18:07 +1000 Subject: [PATCH] Documentation for Part app --- InvenTree/build/api.py | 2 - InvenTree/build/serializers.py | 2 +- InvenTree/build/views.py | 2 +- InvenTree/company/serializers.py | 6 +++ InvenTree/company/views.py | 18 ++++++++ InvenTree/part/api.py | 39 +++++++++++++++++ InvenTree/part/forms.py | 9 ++++ InvenTree/part/models.py | 47 +++++++++++++++++++-- InvenTree/part/serializers.py | 9 ++++ InvenTree/part/urls.py | 4 ++ InvenTree/part/views.py | 72 ++++++++++++++++++++++++++++++-- 11 files changed, 199 insertions(+), 11 deletions(-) diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index c9e756839f..fedfe53008 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -17,8 +17,6 @@ from .serializers import BuildSerializer class BuildList(generics.ListCreateAPIView): """ API endpoint for accessing a list of Build objects. - - Provides two methods: - GET: Return list of objects (with filters) - POST: Create a new Build object diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 92534ef816..955a8d59f5 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -1,5 +1,5 @@ """ -Provides JSON serializers for Build API +JSON serializers for Build API """ # -*- coding: utf-8 -*- diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 756566e694..c8d40e80e6 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -1,5 +1,5 @@ """ -Provides Django views for interacting with Build objects +Django views for interacting with Build objects """ # -*- coding: utf-8 -*- diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index a87560a873..e9a24e0d2a 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -1,9 +1,14 @@ +""" +JSON serializers for Company app +""" + from rest_framework import serializers from .models import Company class CompanyBriefSerializer(serializers.ModelSerializer): + """ Serializer for Company object (limited detail) """ url = serializers.CharField(source='get_absolute_url', read_only=True) @@ -17,6 +22,7 @@ class CompanyBriefSerializer(serializers.ModelSerializer): class CompanySerializer(serializers.ModelSerializer): + """ Serializer for Company object (full detail) """ url = serializers.CharField(source='get_absolute_url', read_only=True) diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index 5a80e5ea15..b171d83f86 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -1,3 +1,8 @@ +""" +Django views for interacting with Company app +""" + + # -*- coding: utf-8 -*- from __future__ import unicode_literals @@ -12,12 +17,20 @@ from .forms import CompanyImageForm class CompanyIndex(ListView): + """ View for displaying list of companies + """ + model = Company template_name = 'company/index.html' context_object_name = 'companies' paginate_by = 50 def get_queryset(self): + """ Retrieve the Company queryset based on HTTP request parameters. + + - supplier: Filter by supplier + - customer: Filter by customer + """ queryset = Company.objects.all().order_by('name') if self.request.GET.get('supplier', None): @@ -30,6 +43,7 @@ class CompanyIndex(ListView): class CompanyDetail(DetailView): + """ Detail view for Company object """ context_obect_name = 'company' template_name = 'company/detail.html' queryset = Company.objects.all() @@ -37,6 +51,7 @@ class CompanyDetail(DetailView): class CompanyImage(AjaxUpdateView): + """ View for uploading an image for the Company """ model = Company ajax_template_name = 'modal_form.html' ajax_form_title = 'Update Company Image' @@ -49,6 +64,7 @@ class CompanyImage(AjaxUpdateView): class CompanyEdit(AjaxUpdateView): + """ View for editing a Company object """ model = Company form_class = EditCompanyForm context_object_name = 'company' @@ -62,6 +78,7 @@ class CompanyEdit(AjaxUpdateView): class CompanyCreate(AjaxCreateView): + """ View for creating a new Company object """ model = Company context_object_name = 'company' form_class = EditCompanyForm @@ -75,6 +92,7 @@ class CompanyCreate(AjaxCreateView): class CompanyDelete(AjaxDeleteView): + """ View for deleting a Company object """ model = Company success_url = '/company/' ajax_template_name = 'company/delete.html' diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 9b5aef436a..d9ee5e5566 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -1,3 +1,7 @@ +""" +Provides a JSON API for the Part app +""" + # -*- coding: utf-8 -*- from __future__ import unicode_literals @@ -26,6 +30,12 @@ class PartCategoryTree(TreeSerializer): class CategoryList(generics.ListCreateAPIView): + """ API endpoint for accessing a list of PartCategory objects. + + - GET: Return a list of PartCategory objects + - POST: Create a new PartCategory object + """ + queryset = PartCategory.objects.all() serializer_class = CategorySerializer @@ -56,11 +66,13 @@ class CategoryList(generics.ListCreateAPIView): class CategoryDetail(generics.RetrieveUpdateDestroyAPIView): + """ API endpoint for detail view of a single PartCategory object """ serializer_class = CategorySerializer queryset = PartCategory.objects.all() class PartDetail(generics.RetrieveUpdateDestroyAPIView): + """ API endpoint for detail view of a single Part object """ queryset = Part.objects.all() serializer_class = PartSerializer @@ -70,6 +82,11 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView): class PartList(generics.ListCreateAPIView): + """ API endpoint for accessing a list of Part objects + + - GET: Return list of objects + - POST: Create a new Part object + """ serializer_class = PartSerializer @@ -130,6 +147,11 @@ class PartList(generics.ListCreateAPIView): class BomList(generics.ListCreateAPIView): + """ API endpoing for accessing a list of BomItem objects + + - GET: Return list of BomItem objects + - POST: Create a new BomItem object + """ queryset = BomItem.objects.all() serializer_class = BomItemSerializer @@ -151,6 +173,7 @@ class BomList(generics.ListCreateAPIView): class BomDetail(generics.RetrieveUpdateDestroyAPIView): + """ API endpoint for detail view of a single BomItem object """ queryset = BomItem.objects.all() serializer_class = BomItemSerializer @@ -161,6 +184,11 @@ class BomDetail(generics.RetrieveUpdateDestroyAPIView): class SupplierPartList(generics.ListCreateAPIView): + """ API endpoint for list view of SupplierPart object + + - GET: Return list of SupplierPart objects + - POST: Create a new SupplierPart object + """ queryset = SupplierPart.objects.all() serializer_class = SupplierPartSerializer @@ -182,6 +210,12 @@ class SupplierPartList(generics.ListCreateAPIView): class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView): + """ API endpoint for detail view of SupplierPart object + + - GET: Retrieve detail view + - PATCH: Update object + - DELETE: Delete objec + """ queryset = SupplierPart.objects.all() serializer_class = SupplierPartSerializer @@ -192,6 +226,11 @@ class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView): class SupplierPriceBreakList(generics.ListCreateAPIView): + """ API endpoint for list view of SupplierPriceBreak object + + - GET: Retrieve list of SupplierPriceBreak objects + - POST: Create a new SupplierPriceBreak object + """ queryset = SupplierPriceBreak.objects.all() serializer_class = SupplierPriceBreakSerializer diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 4d7d05bba1..040444093d 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -1,3 +1,7 @@ +""" +Django Forms for interacting with Part objects +""" + # -*- coding: utf-8 -*- from __future__ import unicode_literals @@ -10,6 +14,7 @@ from .models import SupplierPart class PartImageForm(HelperForm): + """ Form for uploading a Part image """ class Meta: model = Part @@ -40,6 +45,7 @@ class BomExportForm(HelperForm): class EditPartForm(HelperForm): + """ Form for editing a Part object """ class Meta: model = Part @@ -64,6 +70,7 @@ class EditPartForm(HelperForm): class EditCategoryForm(HelperForm): + """ Form for editing a PartCategory object """ class Meta: model = PartCategory @@ -75,6 +82,7 @@ class EditCategoryForm(HelperForm): class EditBomItemForm(HelperForm): + """ Form for editing a BomItem object """ class Meta: model = BomItem @@ -88,6 +96,7 @@ class EditBomItemForm(HelperForm): class EditSupplierPartForm(HelperForm): + """ Form for editing a SupplierPart object """ class Meta: model = SupplierPart diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index dd30178f39..4022b7ec71 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1,3 +1,7 @@ +""" +Part database model definitions +""" + # -*- coding: utf-8 -*- from __future__ import unicode_literals @@ -47,11 +51,19 @@ class PartCategory(InvenTreeTree): @property def has_parts(self): + """ True if there are any parts in this category """ return self.parts.count() > 0 @receiver(pre_delete, sender=PartCategory, dispatch_uid='partcategory_delete_log') def before_delete_part_category(sender, instance, using, **kwargs): + """ Receives before_delete signal for PartCategory object + + Before deleting, update child Part and PartCategory objects: + + - For each child category, set the parent to the parent of *this* category + - For each part, set the 'category' to the parent of *this* category + """ # Update each part in this category to point to the parent category for part in instance.parts.all(): @@ -67,6 +79,16 @@ def before_delete_part_category(sender, instance, using, **kwargs): # Function to automatically rename a part image on upload # Format: part_pk. def rename_part_image(instance, filename): + """ Function for renaming a part image file + + Args: + instance: Instance of a Part object + filename: Name of original uploaded file + + Returns: + Cleaned filename in format part__img + """ + base = 'part_images' if filename.count('.') > 0: @@ -248,7 +270,8 @@ class Part(models.Model): @property def allocation_count(self): - """ Return true if any of this part is allocated + """ Return true if any of this part is allocated: + - To another build - To a customer order """ @@ -311,6 +334,15 @@ class Part(models.Model): def attach_file(instance, filename): + """ Function for storing a file for a PartAttachment + + Args: + instance: Instance of a PartAttachment object + filename: name of uploaded file + + Returns: + path to store file, format: 'part_file__filename' + """ # Construct a path to store a file attachment return os.path.join('part_files', str(instance.part.id), filename) @@ -356,6 +388,13 @@ class BomItem(models.Model): note = models.CharField(max_length=100, blank=True, help_text='Item notes') def clean(self): + """ Check validity of the BomItem model. + + Performs model checks beyond simple field validation. + + - A part cannot refer to itself in its BOM + - A part cannot refer to a part which refers to it + """ # A part cannot refer to itself in its BOM if self.part == self.sub_part: @@ -382,8 +421,9 @@ class BomItem(models.Model): class SupplierPart(models.Model): """ Represents a unique part as provided by a Supplier Each SupplierPart is identified by a MPN (Manufacturer Part Number) - Each SupplierPart is also linked to a Part object - - A Part may be available from multiple suppliers + Each SupplierPart is also linked to a Part object. + + A Part may be available from multiple suppliers """ def get_absolute_url(self): @@ -453,6 +493,7 @@ class SupplierPart(models.Model): def get_price(self, quantity, moq=True, multiples=True): """ Calculate the supplier price based on quantity price breaks. + - If no price breaks available, use the single_price field - Don't forget to add in flat-fee cost (base_cost field) - If MOQ (minimum order quantity) is required, bump quantity diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 68066645bd..0344e46fe7 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -1,3 +1,7 @@ +""" +JSON serializers for Part app +""" + from rest_framework import serializers from .models import Part, PartCategory, BomItem @@ -7,6 +11,7 @@ from InvenTree.serializers import InvenTreeModelSerializer class CategorySerializer(serializers.ModelSerializer): + """ Serializer for PartCategory """ url = serializers.CharField(source='get_absolute_url', read_only=True) @@ -23,6 +28,7 @@ class CategorySerializer(serializers.ModelSerializer): class PartBriefSerializer(serializers.ModelSerializer): + """ Serializer for Part (brief detail) """ url = serializers.CharField(source='get_absolute_url', read_only=True) @@ -68,6 +74,7 @@ class PartSerializer(serializers.ModelSerializer): class BomItemSerializer(InvenTreeModelSerializer): + """ Serializer for BomItem object """ # url = serializers.CharField(source='get_absolute_url', read_only=True) @@ -89,6 +96,7 @@ class BomItemSerializer(InvenTreeModelSerializer): class SupplierPartSerializer(serializers.ModelSerializer): + """ Serializer for SupplierPart object """ url = serializers.CharField(source='get_absolute_url', read_only=True) @@ -112,6 +120,7 @@ class SupplierPartSerializer(serializers.ModelSerializer): class SupplierPriceBreakSerializer(serializers.ModelSerializer): + """ Serializer for SupplierPriceBreak object """ class Meta: model = SupplierPriceBreak diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 14a15625f9..96c830f556 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -1,3 +1,7 @@ +""" +URL lookup for Part app +""" + from django.conf.urls import url, include from . import views diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 4f7175301a..db9f66994c 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1,3 +1,7 @@ +""" +Django views for interacting with Part app +""" + # -*- coding: utf-8 -*- from __future__ import unicode_literals @@ -21,10 +25,12 @@ from .forms import EditSupplierPartForm from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView -from InvenTree.helpers import DownloadFile +from InvenTree.helpers import DownloadFile, str2bool class PartIndex(ListView): + """ View for displaying list of Part objects + """ model = Part template_name = 'part/category.html' context_object_name = 'parts' @@ -45,8 +51,12 @@ class PartIndex(ListView): class PartCreate(AjaxCreateView): - """ Create a new part - - Optionally provide a category object as initial data + """ View for creating a new Part object. + + Options for providing initial conditions: + + - Provide a category object as initial data + - Copy an existing Part """ model = Part form_class = EditPartForm @@ -64,6 +74,10 @@ class PartCreate(AjaxCreateView): # If a category is provided in the URL, pass that to the page context def get_context_data(self, **kwargs): + """ Provide extra context information for the form to display: + + - Add category information (if provided) + """ context = super(PartCreate, self).get_context_data(**kwargs) # Add category information to the page @@ -76,6 +90,11 @@ class PartCreate(AjaxCreateView): # Pre-fill the category field if a valid category is provided def get_initial(self): + """ Get initial data for the new Part object: + + - If a category is provided, pre-fill the Category field + - If 'copy' parameter is provided, copy from referenced Part + """ # Is the client attempting to copy an existing part? part_to_copy = self.request.GET.get('copy', None) @@ -98,15 +117,22 @@ class PartCreate(AjaxCreateView): class PartDetail(DetailView): + """ Detail view for Part object + """ + context_object_name = 'part' queryset = Part.objects.all() template_name = 'part/detail.html' # Add in some extra context information based on query params def get_context_data(self, **kwargs): + """ Provide extra context data to template + + - If '?editing=True', set 'editing_enabled' context variable + """ context = super(PartDetail, self).get_context_data(**kwargs) - if self.request.GET.get('edit', '').lower() in ['true', 'yes', '1']: + if str2bool(self.request.GET.get('edit', '')): context['editing_enabled'] = 1 else: context['editing_enabled'] = 0 @@ -115,6 +141,7 @@ class PartDetail(DetailView): class PartImage(AjaxUpdateView): + """ View for uploading Part image """ model = Part ajax_template_name = 'modal_form.html' @@ -128,6 +155,8 @@ class PartImage(AjaxUpdateView): class PartEdit(AjaxUpdateView): + """ View for editing Part object """ + model = Part template_name = 'part/edit.html' form_class = EditPartForm @@ -214,6 +243,8 @@ class BomDownload(AjaxView): class PartDelete(AjaxDeleteView): + """ View to delete a Part object """ + model = Part template_name = 'part/delete.html' ajax_template_name = 'part/partial_delete.html' @@ -229,6 +260,7 @@ class PartDelete(AjaxDeleteView): class CategoryDetail(DetailView): + """ Detail view for PartCategory """ model = PartCategory context_object_name = 'category' queryset = PartCategory.objects.all() @@ -236,6 +268,7 @@ class CategoryDetail(DetailView): class CategoryEdit(AjaxUpdateView): + """ Update view to edit a PartCategory """ model = PartCategory template_name = 'part/category_edit.html' form_class = EditCategoryForm @@ -251,6 +284,7 @@ class CategoryEdit(AjaxUpdateView): class CategoryDelete(AjaxDeleteView): + """ Delete view to delete a PartCategory """ model = PartCategory template_name = 'part/category_delete.html' context_object_name = 'category' @@ -263,6 +297,7 @@ class CategoryDelete(AjaxDeleteView): class CategoryCreate(AjaxCreateView): + """ Create view to make a new PartCategory """ model = PartCategory ajax_form_action = reverse_lazy('category-create') ajax_form_title = 'Create new part category' @@ -271,6 +306,10 @@ class CategoryCreate(AjaxCreateView): form_class = EditCategoryForm def get_context_data(self, **kwargs): + """ Add extra context data to template. + + - If parent category provided, pass the category details to the template + """ context = super(CategoryCreate, self).get_context_data(**kwargs).copy() parent_id = self.request.GET.get('category', None) @@ -281,6 +320,10 @@ class CategoryCreate(AjaxCreateView): return context def get_initial(self): + """ Get initial data for new PartCategory + + - If parent provided, pre-fill the parent category + """ initials = super(CategoryCreate, self).get_initial().copy() parent_id = self.request.GET.get('category', None) @@ -292,12 +335,14 @@ class CategoryCreate(AjaxCreateView): class BomItemDetail(DetailView): + """ Detail view for BomItem """ context_object_name = 'item' queryset = BomItem.objects.all() template_name = 'part/bom-detail.html' class BomItemCreate(AjaxCreateView): + """ Create view for making a new BomItem object """ model = BomItem form_class = EditBomItemForm template_name = 'part/bom-create.html' @@ -305,6 +350,11 @@ class BomItemCreate(AjaxCreateView): ajax_form_title = 'Create BOM item' def get_initial(self): + """ Provide initial data for the BomItem: + + - If 'parent' provided, set the parent part field + """ + # Look for initial values initials = super(BomItemCreate, self).get_initial().copy() @@ -318,6 +368,8 @@ class BomItemCreate(AjaxCreateView): class BomItemEdit(AjaxUpdateView): + """ Update view for editing BomItem """ + model = BomItem form_class = EditBomItemForm template_name = 'part/bom-edit.html' @@ -326,6 +378,7 @@ class BomItemEdit(AjaxUpdateView): class BomItemDelete(AjaxDeleteView): + """ Delete view for removing BomItem """ model = BomItem template_name = 'part/bom-delete.html' context_object_name = 'item' @@ -333,6 +386,7 @@ class BomItemDelete(AjaxDeleteView): class SupplierPartDetail(DetailView): + """ Detail view for SupplierPart """ model = SupplierPart template_name = 'company/partdetail.html' context_object_name = 'part' @@ -340,6 +394,8 @@ class SupplierPartDetail(DetailView): class SupplierPartEdit(AjaxUpdateView): + """ Update view for editing SupplierPart """ + model = SupplierPart template_name = 'company/partedit.html' context_object_name = 'part' @@ -349,6 +405,8 @@ class SupplierPartEdit(AjaxUpdateView): class SupplierPartCreate(AjaxCreateView): + """ Create view for making new SupplierPart """ + model = SupplierPart form_class = EditSupplierPartForm ajax_template_name = 'modal_form.html' @@ -356,6 +414,11 @@ class SupplierPartCreate(AjaxCreateView): context_object_name = 'part' def get_initial(self): + """ Provide initial data for new SupplierPart: + + - If 'supplier_id' provided, pre-fill supplier field + - If 'part_id' provided, pre-fill part field + """ initials = super(SupplierPartCreate, self).get_initial().copy() supplier_id = self.request.GET.get('supplier', None) @@ -374,6 +437,7 @@ class SupplierPartCreate(AjaxCreateView): class SupplierPartDelete(AjaxDeleteView): + """ Delete view for removing a SupplierPart """ model = SupplierPart success_url = '/supplier/' template_name = 'company/partdelete.html'