diff --git a/.gitignore b/.gitignore
index b4aa808107..6229f979df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,8 +29,6 @@ local_settings.py
# Sphinx files
docs/_build
-docs/_static
-docs/_templates
# Local media storage (only when running in development mode)
InvenTree/media
diff --git a/InvenTree/InvenTree/__init__.py b/InvenTree/InvenTree/__init__.py
index e69de29bb2..521e0a60fc 100644
--- a/InvenTree/InvenTree/__init__.py
+++ b/InvenTree/InvenTree/__init__.py
@@ -0,0 +1,5 @@
+"""
+The InvenTree module provides high-level management and functionality.
+
+It provides a number of helper functions and generic classes which are used by InvenTree apps.
+"""
diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py
index 002a83e896..ba203b2f1b 100644
--- a/InvenTree/InvenTree/forms.py
+++ b/InvenTree/InvenTree/forms.py
@@ -1,3 +1,7 @@
+"""
+Helper forms which subclass Django forms to provide additional functionality
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -6,6 +10,7 @@ from crispy_forms.helper import FormHelper
class HelperForm(forms.ModelForm):
+ """ Provides simple integration of crispy_forms extension. """
def __init__(self, *args, **kwargs):
super(forms.ModelForm, self).__init__(*args, **kwargs)
diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py
index a492ca353d..295937af08 100644
--- a/InvenTree/InvenTree/helpers.py
+++ b/InvenTree/InvenTree/helpers.py
@@ -1,3 +1,7 @@
+"""
+Provides helper functions used throughout the InvenTree project
+"""
+
import io
from wsgiref.util import FileWrapper
@@ -5,7 +9,14 @@ from django.http import StreamingHttpResponse
def str2bool(text, test=True):
- """ Test if a string 'looks' like a boolean value
+ """ Test if a string 'looks' like a boolean value.
+
+ Args:
+ text: Input text
+ test (default = True): Set which boolean value to look for
+
+ Returns:
+ True if the text looks like the selected boolean value
"""
if test:
return str(text).lower() in ['1', 'y', 'yes', 't', 'true', 'ok', ]
@@ -13,21 +24,36 @@ def str2bool(text, test=True):
return str(text).lower() in ['0', 'n', 'no', 'none', 'f', 'false', ]
-def WrapWithQuotes(text):
- # TODO - Make this better
- if not text.startswith('"'):
- text = '"' + text
+def WrapWithQuotes(text, quote='"'):
+ """ Wrap the supplied text with quotes
- if not text.endswith('"'):
- text = text + '"'
+ Args:
+ text: Input text to wrap
+ quote: Quote character to use for wrapping (default = "")
+
+ Returns:
+ Supplied text wrapped in quote char
+ """
+
+ if not text.startswith(quote):
+ text = quote + text
+
+ if not text.endswith(quote):
+ text = text + quote
return text
def DownloadFile(data, filename, content_type='application/text'):
- """
- Create a dynamic file for the user to download.
- @param data is the raw file data
+ """ Create a dynamic file for the user to download.
+
+ Args:
+ data: Raw file data (string or bytes)
+ filename: Filename for the file download
+ content_type: Content type for the download
+
+ Return:
+ A StreamingHttpResponse object wrapping the supplied data
"""
filename = WrapWithQuotes(filename)
diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py
index 2b49f7b908..61fb50831b 100644
--- a/InvenTree/InvenTree/models.py
+++ b/InvenTree/InvenTree/models.py
@@ -1,9 +1,12 @@
+"""
+Generic models which provide extra functionality over base Django model types.
+"""
+
from __future__ import unicode_literals
from django.db import models
from django.contrib.contenttypes.models import ContentType
from rest_framework.exceptions import ValidationError
-from .helpers import str2bool
from django.db.models.signals import pre_delete
from django.dispatch import receiver
@@ -11,6 +14,7 @@ from django.dispatch import receiver
class InvenTreeTree(models.Model):
""" Provides an abstracted self-referencing tree model for data categories.
+
- Each Category has one parent Category, which can be blank (for a top-level Category).
- Each Category can have zero-or-more child Categor(y/ies)
"""
@@ -69,10 +73,12 @@ class InvenTreeTree(models.Model):
@property
def has_children(self):
+ """ True if there are any children under this item """
return self.children.count() > 0
@property
def children(self):
+ """ Return the children of this item """
contents = ContentType.objects.get_for_model(type(self))
childs = contents.get_all_objects_for_this_type(parent=self.id)
@@ -100,11 +106,10 @@ class InvenTreeTree(models.Model):
@property
def parentpath(self):
- """ Return the parent path of this category
+ """ Get the parent path of this category
- Todo:
- This function is recursive and expensive.
- It should be reworked such that only a single db call is required
+ Returns:
+ List of category names from the top level to the parent of this category
"""
if self.parent:
@@ -114,10 +119,21 @@ class InvenTreeTree(models.Model):
@property
def path(self):
+ """ Get the complete part of this category.
+
+ e.g. ["Top", "Second", "Third", "This"]
+
+ Returns:
+ List of category names from the top level to this category
+ """
return self.parentpath + [self]
@property
def pathstring(self):
+ """ Get a string representation for the path of this item.
+
+ e.g. "Top/Second/Third/This"
+ """
return '/'.join([item.name for item in self.path])
def __setattr__(self, attrname, val):
@@ -157,38 +173,19 @@ class InvenTreeTree(models.Model):
super(InvenTreeTree, self).__setattr__(attrname, val)
def __str__(self):
- """ String representation of a category is the full path to that category
-
- Todo:
- This is recursive - Make it not so.
- """
+ """ String representation of a category is the full path to that category """
return self.pathstring
@receiver(pre_delete, sender=InvenTreeTree, dispatch_uid='tree_pre_delete_log')
def before_delete_tree_item(sender, instance, using, **kwargs):
+ """ Receives pre_delete signal from InvenTreeTree object.
+
+ Before an item is deleted, update each child object to point to the parent of the object being deleted.
+ """
# Update each tree item below this one
for child in instance.children.all():
child.parent = instance.parent
child.save()
-
-
-def FilterChildren(queryset, parent):
- """ Filter a queryset, limit to only objects that are a child of the given parent
- Filter is passed in the URL string, e.g. '/?parent=123'
- To accommodate for items without a parent, top-level items can be specified as:
- none / false / null / top / 0
- """
-
- if not parent:
- return queryset
- elif str2bool(parent, False):
- return queryset.filter(parent=None)
- else:
- parent_id = int(parent)
- if parent_id == 0:
- return queryset.filter(parent=None)
- else:
- return queryset.filter(parent=parent_id)
diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py
index 4ab1fd694d..7149668cc4 100644
--- a/InvenTree/InvenTree/serializers.py
+++ b/InvenTree/InvenTree/serializers.py
@@ -1,3 +1,8 @@
+"""
+Serializers used in various InvenTree apps
+"""
+
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -7,6 +12,7 @@ from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
+ """ Serializer for User - provides all fields """
class Meta:
model = User
@@ -14,6 +20,7 @@ class UserSerializer(serializers.ModelSerializer):
class UserSerializerBrief(serializers.ModelSerializer):
+ """ Serializer for User - provides limited information """
class Meta:
model = User
diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py
index c33689be61..ccc828de55 100644
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -1,3 +1,10 @@
+"""
+Top-level URL lookup for InvenTree application.
+
+Passes URL lookup downstream to each app as required.
+"""
+
+
from django.conf.urls import url, include
from django.contrib import admin
from django.contrib.auth import views as auth_views
diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index c160398caf..551f624cb9 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -1,3 +1,10 @@
+"""
+Various Views which provide extra functionality over base Django Views.
+
+In particular these views provide base functionality for rendering Django forms
+as JSON objects and passing them to modal forms (using jQuery / bootstrap).
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -12,6 +19,8 @@ from rest_framework import views
class TreeSerializer(views.APIView):
+ """ JSON View for serializing a Tree object.
+ """
def itemToJson(self, item):
@@ -52,20 +61,34 @@ class TreeSerializer(views.APIView):
class AjaxMixin(object):
+ """ AjaxMixin provides basic functionality for rendering a Django form to JSON.
+ Handles jsonResponse rendering, and adds extra data for the modal forms to process
+ on the client side.
+ """
ajax_form_action = ''
ajax_form_title = ''
def get_data(self):
+ """ Get extra context data (default implementation is empty dict)
+
+ Returns:
+ dict object (empty)
+ """
return {}
- def getAjaxTemplate(self):
- if hasattr(self, 'ajax_template_name'):
- return self.ajax_template_name
- else:
- return self.template_name
-
def renderJsonResponse(self, request, form=None, data={}, context={}):
+ """ Render a JSON response based on specific class context.
+
+ Args:
+ request: HTTP request object (e.g. GET / POST)
+ form: Django form object (may be None)
+ data: Extra JSON data to pass to client
+ context: Extra context data to pass to template rendering
+
+ Returns:
+ JSON response object
+ """
if form:
context['form'] = form
@@ -73,7 +96,7 @@ class AjaxMixin(object):
data['title'] = self.ajax_form_title
data['html_form'] = render_to_string(
- self.getAjaxTemplate(),
+ self.ajax_template_name,
context,
request=request
)
@@ -88,7 +111,8 @@ class AjaxMixin(object):
class AjaxView(AjaxMixin, View):
- """ Bare-bones AjaxView """
+ """ An 'AJAXified' View for displaying an object
+ """
# By default, point to the modal_form template
# (this can be overridden by a child class)
@@ -201,7 +225,7 @@ class AjaxDeleteView(AjaxMixin, DeleteView):
data = {'id': self.get_object().id,
'delete': False,
'title': self.ajax_form_title,
- 'html_data': render_to_string(self.getAjaxTemplate(),
+ 'html_data': render_to_string(self.ajax_template_name,
self.get_context_data(),
request=request)
}
@@ -229,15 +253,24 @@ class AjaxDeleteView(AjaxMixin, DeleteView):
class IndexView(TemplateView):
+ """ View for InvenTree index page """
template_name = 'InvenTree/index.html'
class SearchView(TemplateView):
+ """ View for InvenTree search page.
+
+ Displays results of search query
+ """
template_name = 'InvenTree/search.html'
def post(self, request, *args, **kwargs):
+ """ Handle POST request (which contains search query).
+
+ Pass the search query to the page template
+ """
context = self.get_context_data()
diff --git a/InvenTree/build/__init__.py b/InvenTree/build/__init__.py
index e69de29bb2..747a20f837 100644
--- a/InvenTree/build/__init__.py
+++ b/InvenTree/build/__init__.py
@@ -0,0 +1,5 @@
+"""
+The Build module is responsible for managing "Build" transactions.
+
+A Build consumes parts from stock to create new parts
+"""
diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py
index 41d46bab49..393bd1f073 100644
--- a/InvenTree/build/api.py
+++ b/InvenTree/build/api.py
@@ -1,3 +1,7 @@
+"""
+JSON API for the Build app
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -11,7 +15,12 @@ from .models import Build
from .serializers import BuildSerializer
-class BuildList(generics.ListAPIView):
+class BuildList(generics.ListCreateAPIView):
+ """ API endpoint for accessing a list of Build objects.
+
+ - GET: Return list of objects (with filters)
+ - POST: Create a new Build object
+ """
queryset = Build.objects.all()
serializer_class = BuildSerializer
diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py
index 1775b5c3b5..6254f0745c 100644
--- a/InvenTree/build/forms.py
+++ b/InvenTree/build/forms.py
@@ -1,3 +1,7 @@
+"""
+Django Forms for interacting with Build objects
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -7,6 +11,8 @@ from .models import Build
class EditBuildForm(HelperForm):
+ """ Form for editing a Build object.
+ """
class Meta:
model = Build
diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py
index c72aa5209b..880c1b08b1 100644
--- a/InvenTree/build/models.py
+++ b/InvenTree/build/models.py
@@ -1,3 +1,7 @@
+"""
+Build database model definitions
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -9,60 +13,60 @@ from django.core.validators import MinValueValidator
class Build(models.Model):
- """ A Build object organises the creation of new parts from the component parts
- It uses the part BOM to generate new parts.
- Parts are then taken from stock
+ """ A Build object organises the creation of new parts from the component parts.
"""
def get_absolute_url(self):
return reverse('build-detail', kwargs={'pk': self.id})
+ part = models.ForeignKey('part.Part', on_delete=models.CASCADE,
+ related_name='builds',
+ limit_choices_to={'buildable': True},
+ )
+ """ A reference to the part being built - only parts marked as 'buildable' may be selected """
+
+ #: Brief title describing the build
+ title = models.CharField(max_length=100, help_text='Brief description of the build')
+
+ #: Number of output parts to build
+ quantity = models.PositiveIntegerField(default=1,
+ validators=[MinValueValidator(1)],
+ help_text='Number of parts to build')
+
# Build status codes
PENDING = 10 # Build is pending / active
HOLDING = 20 # Build is currently being held
CANCELLED = 30 # Build was cancelled
COMPLETE = 40 # Build is complete
+ #: Build status codes
BUILD_STATUS_CODES = {PENDING: _("Pending"),
HOLDING: _("Holding"),
CANCELLED: _("Cancelled"),
COMPLETE: _("Complete"),
}
- batch = models.CharField(max_length=100, blank=True, null=True,
- help_text='Batch code for this build output')
-
- # Status of the build
+ #: Status of the build (ref BUILD_STATUS_CODES)
status = models.PositiveIntegerField(default=PENDING,
choices=BUILD_STATUS_CODES.items(),
validators=[MinValueValidator(0)])
- # Date the build model was 'created'
+ #: Batch number for the build (optional)
+ batch = models.CharField(max_length=100, blank=True, null=True,
+ help_text='Batch code for this build output')
+
+ #: Date the build model was 'created'
creation_date = models.DateField(auto_now=True, editable=False)
- # Date the build was 'completed'
+ #: Date the build was 'completed' (and parts removed from stock)
completion_date = models.DateField(null=True, blank=True)
- # Brief build title
- title = models.CharField(max_length=100, help_text='Brief description of the build')
-
- # A reference to the part being built
- # Only 'buildable' parts can be selected
- part = models.ForeignKey('part.Part', on_delete=models.CASCADE,
- related_name='builds',
- limit_choices_to={'buildable': True},
- )
-
- # How many parts to build?
- quantity = models.PositiveIntegerField(default=1,
- validators=[MinValueValidator(1)],
- help_text='Number of parts to build')
-
- # Notes can be attached to each build output
+ #: Notes attached to each build output
notes = models.TextField(blank=True)
@property
def required_parts(self):
+ """ Returns a dict of parts required to build this part (BOM) """
parts = []
for item in self.part.bom_items.all():
@@ -77,8 +81,7 @@ class Build(models.Model):
@property
def can_build(self):
- """ Return true if there are enough parts to supply build
- """
+ """ Return true if there are enough parts to supply build """
for item in self.required_parts:
if item['part'].total_stock < item['quantity']:
@@ -88,10 +91,10 @@ class Build(models.Model):
@property
def is_active(self):
- """ Is this build active?
- An active build is either:
- - Pending
- - Holding
+ """ Is this build active? An active build is either:
+
+ - PENDING
+ - HOLDING
"""
return self.status in [
@@ -101,4 +104,5 @@ class Build(models.Model):
@property
def is_complete(self):
+ """ Returns True if the build status is COMPLETE """
return self.status == self.COMPLETE
diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py
index ac36fd5b41..955a8d59f5 100644
--- a/InvenTree/build/serializers.py
+++ b/InvenTree/build/serializers.py
@@ -1,3 +1,7 @@
+"""
+JSON serializers for Build API
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -7,6 +11,7 @@ from .models import Build
class BuildSerializer(serializers.ModelSerializer):
+ """ Serializes a Build object """
url = serializers.CharField(source='get_absolute_url', read_only=True)
status_text = serializers.CharField(source='get_status_display', read_only=True)
diff --git a/InvenTree/build/urls.py b/InvenTree/build/urls.py
index a8e4435e24..fea3b6596c 100644
--- a/InvenTree/build/urls.py
+++ b/InvenTree/build/urls.py
@@ -1,3 +1,7 @@
+"""
+URL lookup for Build app
+"""
+
from django.conf.urls import url, include
from . import views
diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py
index f3f7bd79f3..c8d40e80e6 100644
--- a/InvenTree/build/views.py
+++ b/InvenTree/build/views.py
@@ -1,3 +1,7 @@
+"""
+Django views for interacting with Build objects
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -13,11 +17,14 @@ from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView
class BuildIndex(ListView):
+ """ View for displaying list of Builds
+ """
model = Build
template_name = 'build/index.html'
context_object_name = 'builds'
def get_queryset(self):
+ """ Return all Build objects (order by date, newest first) """
return Build.objects.order_by('status', '-completion_date')
def get_context_data(self, **kwargs):
@@ -35,6 +42,9 @@ class BuildIndex(ListView):
class BuildCancel(AjaxView):
+ """ View to cancel a Build.
+ Provides a cancellation information dialog
+ """
model = Build
template_name = 'build/cancel.html'
ajax_form_title = 'Cancel Build'
@@ -42,6 +52,7 @@ class BuildCancel(AjaxView):
fields = []
def post(self, request, *args, **kwargs):
+ """ Handle POST request. Mark the build status as CANCELLED """
build = get_object_or_404(Build, pk=self.kwargs['pk'])
@@ -51,24 +62,28 @@ class BuildCancel(AjaxView):
return self.renderJsonResponse(request, None)
def get_data(self):
+ """ Provide JSON context data. """
return {
'info': 'Build was cancelled'
}
class BuildDetail(DetailView):
+ """ Detail view of a single Build object. """
model = Build
template_name = 'build/detail.html'
context_object_name = 'build'
class BuildAllocate(DetailView):
+ """ View for allocating parts to a Build """
model = Build
context_object_name = 'build'
template_name = 'build/allocate.html'
class BuildCreate(AjaxCreateView):
+ """ View to create a new Build object """
model = Build
context_object_name = 'build'
form_class = EditBuildForm
@@ -76,6 +91,11 @@ class BuildCreate(AjaxCreateView):
ajax_template_name = 'modal_form.html'
def get_initial(self):
+ """ Get initial parameters for Build creation.
+
+ If 'part' is specified in the GET query, initialize the Build with the specified Part
+ """
+
initials = super(BuildCreate, self).get_initial().copy()
part_id = self.request.GET.get('part', None)
@@ -92,6 +112,8 @@ class BuildCreate(AjaxCreateView):
class BuildUpdate(AjaxUpdateView):
+ """ View for editing a Build object """
+
model = Build
form_class = EditBuildForm
context_object_name = 'build'
diff --git a/InvenTree/company/__init__.py b/InvenTree/company/__init__.py
index e69de29bb2..6c89578ac3 100644
--- a/InvenTree/company/__init__.py
+++ b/InvenTree/company/__init__.py
@@ -0,0 +1,8 @@
+"""
+The Company module is responsible for managing Company interactions.
+
+A company can be either (or both):
+
+- Supplier
+- Customer
+"""
diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py
index d90cfdb1ee..dfa3ebfb77 100644
--- a/InvenTree/company/api.py
+++ b/InvenTree/company/api.py
@@ -1,3 +1,7 @@
+"""
+Provides a JSON API for the Company app
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -12,6 +16,13 @@ from .serializers import CompanySerializer
class CompanyList(generics.ListCreateAPIView):
+ """ API endpoint for accessing a list of Company objects
+
+ Provides two methods:
+
+ - GET: Return list of objects
+ - POST: Create a new Company object
+ """
serializer_class = CompanySerializer
queryset = Company.objects.all()
@@ -44,6 +55,7 @@ class CompanyList(generics.ListCreateAPIView):
class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
+ """ API endpoint for detail of a single Company object """
queryset = Company.objects.all()
serializer_class = CompanySerializer
diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py
index 7fca7c7e72..bf7057a1ea 100644
--- a/InvenTree/company/forms.py
+++ b/InvenTree/company/forms.py
@@ -1,3 +1,7 @@
+"""
+Django Forms for interacting with Company app
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -7,6 +11,7 @@ from .models import Company
class EditCompanyForm(HelperForm):
+ """ Form for editing a Company object """
class Meta:
model = Company
@@ -26,6 +31,7 @@ class EditCompanyForm(HelperForm):
class CompanyImageForm(HelperForm):
+ """ Form for uploading a Company image """
class Meta:
model = Company
diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py
index fe97d9a90c..dcee37959d 100644
--- a/InvenTree/company/models.py
+++ b/InvenTree/company/models.py
@@ -1,3 +1,7 @@
+"""
+Company database model definitions
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -8,6 +12,16 @@ from django.urls import reverse
def rename_company_image(instance, filename):
+ """ Function to rename a company image after upload
+
+ Args:
+ instance: Company object
+ filename: uploaded image filename
+
+ Returns:
+ New image filename
+ """
+
base = 'company_images'
if filename.count('.') > 0:
@@ -24,6 +38,9 @@ def rename_company_image(instance, filename):
class Company(models.Model):
+ """ A Company object represents an external company.
+ It may be a supplier or a customer (or both).
+ """
name = models.CharField(max_length=100, unique=True,
help_text='Company name')
@@ -54,21 +71,28 @@ class Company(models.Model):
is_supplier = models.BooleanField(default=True)
def __str__(self):
+ """ Get string representation of a Company """
return "{n} - {d}".format(n=self.name, d=self.description)
def get_absolute_url(self):
+ """ Get the web URL for the detail view for this Company """
return reverse('company-detail', kwargs={'pk': self.id})
@property
def part_count(self):
+ """ The number of parts supplied by this company """
return self.parts.count()
@property
def has_parts(self):
+ """ Return True if this company supplies any parts """
return self.part_count > 0
class Contact(models.Model):
+ """ A Contact represents a person who works at a particular company.
+ A Company may have zero or more associated Contact objects
+ """
name = models.CharField(max_length=100)
diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py
index a87560a873..88792f7f64 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/urls.py b/InvenTree/company/urls.py
index 13c478f28a..2c5018ebec 100644
--- a/InvenTree/company/urls.py
+++ b/InvenTree/company/urls.py
@@ -1,3 +1,8 @@
+"""
+URL lookup for Company app
+"""
+
+
from django.conf.urls import url, include
from django.views.generic.base import RedirectView
diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py
index 5a80e5ea15..f61da1fffa 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/keygen.py b/InvenTree/keygen.py
index 02909b0d79..c96df22794 100644
--- a/InvenTree/keygen.py
+++ b/InvenTree/keygen.py
@@ -1,7 +1,5 @@
"""
-Module keygen
-=============
-This module generates a Django SECRET_KEY file to be used by manage.py
+Generates a Django SECRET_KEY file to be used by manage.py
"""
import random
@@ -15,11 +13,13 @@ KEY_DIR = os.path.dirname(os.path.realpath(__file__))
def generate_key(length=50):
- """
- Generate a random string
+ """ Generate a random string
- :param length: Number of characters in returned string (default=50)
- :returns: Randomized secret key string
+ Args:
+ length: Number of characters in returned string (default = 50)
+
+ Returns:
+ Randomized secret key string
"""
options = string.digits + string.ascii_letters + string.punctuation
diff --git a/InvenTree/part/__init__.py b/InvenTree/part/__init__.py
index e69de29bb2..da73be7753 100644
--- a/InvenTree/part/__init__.py
+++ b/InvenTree/part/__init__.py
@@ -0,0 +1,10 @@
+"""
+The Part module is responsible for Part management.
+
+It includes models for:
+
+- PartCategory
+- Part
+- SupplierPart
+- BomItem
+"""
diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py
index 9b5aef436a..8f93cede7c 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'
diff --git a/InvenTree/stock/__init__.py b/InvenTree/stock/__init__.py
index e69de29bb2..6970329be1 100644
--- a/InvenTree/stock/__init__.py
+++ b/InvenTree/stock/__init__.py
@@ -0,0 +1,9 @@
+"""
+The Stock module is responsible for Stock management.
+
+It includes models for:
+
+- StockLocation
+- StockItem
+- StockItemTracking
+"""
diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index 215a9e311f..c352605ad2 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -1,3 +1,7 @@
+"""
+JSON API for the Stock app
+"""
+
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from django_filters import NumberFilter
@@ -26,7 +30,7 @@ class StockCategoryTree(TreeSerializer):
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
- """
+ """ API detail endpoint for Stock object
get:
Return a single StockItem object
@@ -44,6 +48,11 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
class StockFilter(FilterSet):
+ """ FilterSet for advanced stock filtering.
+
+ Allows greater-than / less-than filtering for stock quantity
+ """
+
min_stock = NumberFilter(name='quantity', lookup_expr='gte')
max_stock = NumberFilter(name='quantity', lookup_expr='lte')
@@ -53,12 +62,11 @@ class StockFilter(FilterSet):
class StockStocktake(APIView):
- """
- Stocktake API endpoint provides stock update of multiple items simultaneously
+ """ Stocktake API endpoint provides stock update of multiple items simultaneously.
The 'action' field tells the type of stock action to perform:
- * 'stocktake' - Count the stock item(s)
- * 'remove' - Remove the quantity provided from stock
- * 'add' - Add the quantity provided from stock
+ - stocktake: Count the stock item(s)
+ - remove: Remove the quantity provided from stock
+ - add: Add the quantity provided from stock
"""
permission_classes = [
@@ -129,6 +137,7 @@ class StockStocktake(APIView):
class StockMove(APIView):
+ """ API endpoint for performing stock movements """
permission_classes = [
permissions.IsAuthenticatedOrReadOnly,
@@ -183,6 +192,11 @@ class StockMove(APIView):
class StockLocationList(generics.ListCreateAPIView):
+ """ API endpoint for list view of StockLocation objects:
+
+ - GET: Return list of StockLocation objects
+ - POST: Create a new StockLocation
+ """
queryset = StockLocation.objects.all()
@@ -204,14 +218,10 @@ class StockLocationList(generics.ListCreateAPIView):
class StockList(generics.ListCreateAPIView):
- """
+ """ API endpoint for list view of Stock objects
- get:
- Return a list of all StockItem objects
- (with optional query filters)
-
- post:
- Create a new StockItem
+ - GET: Return a list of all StockItem objects (with optional query filters)
+ - POST: Create a new StockItem
"""
def get_queryset(self):
@@ -268,6 +278,7 @@ class StockList(generics.ListCreateAPIView):
class StockStocktakeEndpoint(generics.UpdateAPIView):
+ """ API endpoint for performing stocktake """
queryset = StockItem.objects.all()
serializer_class = StockQuantitySerializer
@@ -283,6 +294,13 @@ class StockStocktakeEndpoint(generics.UpdateAPIView):
class StockTrackingList(generics.ListCreateAPIView):
+ """ API endpoint for list view of StockItemTracking objects.
+
+ StockItemTracking objects are read-only
+ (they are created by internal model functionality)
+
+ - GET: Return list of StockItemTracking objects
+ """
queryset = StockItemTracking.objects.all()
serializer_class = StockTrackingSerializer
@@ -312,17 +330,11 @@ class StockTrackingList(generics.ListCreateAPIView):
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
- """
-
- get:
- Return a single StockLocation object
-
- post:
- Update a StockLocation object
-
- delete:
- Remove a StockLocation object
+ """ API endpoint for detail view of StockLocation object
+ - GET: Return a single StockLocation object
+ - PATCH: Update a StockLocation object
+ - DELETE: Remove a StockLocation object
"""
queryset = StockLocation.objects.all()
diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py
index bd0254e720..a94156ad29 100644
--- a/InvenTree/stock/forms.py
+++ b/InvenTree/stock/forms.py
@@ -1,3 +1,7 @@
+"""
+Django Forms for interacting with Stock app
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -8,6 +12,7 @@ from .models import StockLocation, StockItem
class EditStockLocationForm(HelperForm):
+ """ Form for editing a StockLocation """
class Meta:
model = StockLocation
@@ -19,6 +24,7 @@ class EditStockLocationForm(HelperForm):
class CreateStockItemForm(HelperForm):
+ """ Form for creating a new StockItem """
class Meta:
model = StockItem
@@ -38,6 +44,7 @@ class CreateStockItemForm(HelperForm):
class MoveStockItemForm(forms.ModelForm):
+ """ Form for moving a StockItem to a new location """
note = forms.CharField(label='Notes', required=True, help_text='Add note (required)')
@@ -60,6 +67,7 @@ class StocktakeForm(forms.ModelForm):
class EditStockItemForm(HelperForm):
+ """ Form for editing a StockItem object """
class Meta:
model = StockItem
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 4b1505d2d0..636efeb293 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -1,3 +1,8 @@
+"""
+Stock database model definitions
+"""
+
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py
index 64baeb5330..0a863aa7f9 100644
--- a/InvenTree/stock/serializers.py
+++ b/InvenTree/stock/serializers.py
@@ -1,3 +1,7 @@
+"""
+JSON serializers for Stock app
+"""
+
from rest_framework import serializers
from .models import StockItem, StockLocation
@@ -44,8 +48,8 @@ class StockItemSerializerBrief(serializers.ModelSerializer):
class StockItemSerializer(serializers.ModelSerializer):
- """
- Serializer for a StockItem
+ """ Serializer for a StockItem:
+
- Includes serialization for the linked part
- Includes serialization for the item location
"""
@@ -112,6 +116,7 @@ class LocationSerializer(serializers.ModelSerializer):
class StockTrackingSerializer(serializers.ModelSerializer):
+ """ Serializer for StockItemTracking model """
url = serializers.CharField(source='get_absolute_url', read_only=True)
diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py
index 503c9e809c..6b3fc0ff2d 100644
--- a/InvenTree/stock/urls.py
+++ b/InvenTree/stock/urls.py
@@ -1,3 +1,7 @@
+"""
+URL lookup for Stock app
+"""
+
from django.conf.urls import url, include
from . import views
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py
index fc1d2726aa..70d3f7ded3 100644
--- a/InvenTree/stock/views.py
+++ b/InvenTree/stock/views.py
@@ -1,3 +1,7 @@
+"""
+Django views for interacting with Stock app
+"""
+
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
@@ -19,8 +23,7 @@ from .forms import StocktakeForm
class StockIndex(ListView):
- """
- StockIndex view loads all StockLocation and StockItem object
+ """ StockIndex view loads all StockLocation and StockItem object
"""
model = StockItem
template_name = 'stock/location.html'
diff --git a/docs/conf.py b/docs/conf.py
index 78bcb74673..d1b8f1c5b8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -28,15 +28,28 @@ copyright = '2019, InvenTree'
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'autoapi.extension'
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',
+ 'autoapi.extension',
]
+napoleon_google_docstring = True
+napoleon_numpy_docstring = False
+
autoapi_dirs = [
'../InvenTree',
+ '../Inventree/build',
+ '../InvenTree/company',
+ '../InvenTree/InvenTree',
+ '../InvenTree/part',
+ '../InvenTree/stock',
+ '../InvenTree/users',
]
autoapi_options = [
'members',
+ 'private-members',
+ 'special-members',
]
autoapi_type = 'python'
@@ -44,11 +57,21 @@ autoapi_type = 'python'
autoapi_ignore = [
'*migrations*',
'**/test*.py',
- '**/manage.py'
+ '**/manage.py',
+ '**/apps.py',
+ '**/admin.py',
+ '**/middleware.py',
+ '**/utils.py',
+ '**/wsgi.py',
+ '**/templates/',
]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+autoapi_template_dir = 'templates'
+autoapi_root = 'docs'
+autoapi_add_toctree_entry = False
+
+templates_path = ['templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -73,3 +96,11 @@ html_theme = 'sphinx_rtd_theme'
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
+
+# Table of contents in sidebar
+html_sidebars = {'**': [
+ 'globaltoc.html',
+ 'relations.html',
+ 'sourcelink.html',
+ 'searchbox.html'
+]}
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index 9c43b19929..114ecaf27b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,20 +1,12 @@
-.. InvenTree documentation master file, created by
- sphinx-quickstart on Sat Apr 27 15:45:39 2019.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
+InvenTree's Django Documentation
+================================
-Welcome to InvenTree's documentation!
-=====================================
+This documentation is auto-generated from the `InvenTree codebase `_
.. toctree::
+ :titlesonly:
:maxdepth: 2
:caption: Contents:
+ :hidden:
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+ InvenTree
diff --git a/docs/introduction.rst b/docs/introduction.rst
new file mode 100644
index 0000000000..e117010749
--- /dev/null
+++ b/docs/introduction.rst
@@ -0,0 +1,18 @@
+InvenTree
+=========
+
+InvenTree is an open source inventory management system which provides powerful low-level part management and stock tracking functionality.
+
+The core of the InvenTree software is a Python/Django database backend whi
+
+
+Django Apps
+===========
+
+The InvenTree Django ecosystem provides the following 'apps' for core functionality:
+
+* `InvenTree `_ - High level management functions
+* `Build `_ - Part build projects
+* `Company `_ - Company management (suppliers / customers)
+* `Part `_ - Part management
+* `Stock `_ - Stock management
\ No newline at end of file
diff --git a/docs/templates/layout.html b/docs/templates/layout.html
new file mode 100644
index 0000000000..620814d1b3
--- /dev/null
+++ b/docs/templates/layout.html
@@ -0,0 +1,7 @@
+{% extends "!layout.html" %}
+
+ {% block menu %}
+ {{ super() }}
+ Module Index
+ Index
+ {% endblock %}
\ No newline at end of file
diff --git a/docs/templates/python/module.rst b/docs/templates/python/module.rst
new file mode 100644
index 0000000000..ec51763131
--- /dev/null
+++ b/docs/templates/python/module.rst
@@ -0,0 +1,97 @@
+{% if not obj.display %}
+:orphan:
+
+{% endif %}
+:mod:`{{ obj.name }}`
+======={{ "=" * obj.name|length }}
+
+.. py:module:: {{ obj.name }}
+
+{% if obj.docstring %}
+.. autoapi-nested-parse::
+
+ {{ obj.docstring|prepare_docstring|indent(3) }}
+
+{% endif %}
+
+{% block subpackages %}
+{% set visible_subpackages = obj.subpackages|selectattr("display")|list %}
+{% if visible_subpackages %}
+Subpackages
+-----------
+.. toctree::
+ :titlesonly:
+ :maxdepth: 3
+
+{% for subpackage in visible_subpackages %}
+ {{ subpackage.short_name }}/index.rst
+{% endfor %}
+
+
+{% endif %}
+{% endblock %}
+{% block submodules %}
+{% set visible_submodules = obj.submodules|selectattr("display")|list %}
+{% if visible_submodules %}
+Submodules
+----------
+
+The {{ obj.name }} module contains the following submodules
+
+.. toctree::
+ :titlesonly:
+ :maxdepth: 1
+
+{% for submodule in visible_submodules %}
+ {{ submodule.short_name }}/index.rst
+{% endfor %}
+
+
+{% endif %}
+{% endblock %}
+{% block content %}
+{% set visible_children = obj.children|selectattr("display")|list %}
+{% if visible_children %}
+{{ obj.type|title }} Contents
+{{ "-" * obj.type|length }}---------
+
+{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %}
+{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %}
+{% if include_summaries and (visible_classes or visible_functions) %}
+{% block classes %}
+{% if visible_classes %}
+Classes
+~~~~~~~
+
+.. autoapisummary::
+
+{% for klass in visible_classes %}
+ {{ klass.id }}
+{% endfor %}
+
+
+{% endif %}
+{% endblock %}
+
+{% block functions %}
+{% if visible_functions %}
+Functions
+~~~~~~~~~
+
+.. autoapisummary::
+
+{% for function in visible_functions %}
+ {{ function.id }}
+{% endfor %}
+
+
+{% endif %}
+{% endblock %}
+{% endif %}
+{% for obj_item in visible_children %}
+{% if obj.all is none or obj_item.short_name in obj.all %}
+{{ obj_item.rendered|indent(0) }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endblock %}