From ba2f565e3596c942b19dda7a7b9a0c01bd7dc4eb Mon Sep 17 00:00:00 2001
From: Oliver Walters <oliver.henry.walters@gmail.com>
Date: Fri, 10 May 2019 20:11:52 +1000
Subject: [PATCH] docstring improvements

- Added Attribute docstring to all model objects
---
 InvenTree/InvenTree/models.py |  5 ++
 InvenTree/build/models.py     |  2 +-
 InvenTree/company/models.py   | 25 ++++++++-
 InvenTree/part/models.py      | 99 ++++++++++++++++++++++++-----------
 InvenTree/stock/models.py     | 53 ++++++++++---------
 5 files changed, 124 insertions(+), 60 deletions(-)

diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py
index a28b27a5eb..a7fc93bf3f 100644
--- a/InvenTree/InvenTree/models.py
+++ b/InvenTree/InvenTree/models.py
@@ -17,6 +17,11 @@ class InvenTreeTree(models.Model):
 
     - 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)
+
+    Attributes:
+        name: brief name
+        description: longer form description
+        parent: The item immediately above this one. An item with a null parent is a top-level item
     """
 
     class Meta:
diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py
index 0b658dee1a..af34d198fb 100644
--- a/InvenTree/build/models.py
+++ b/InvenTree/build/models.py
@@ -330,7 +330,7 @@ class BuildItem(models.Model):
 
     Attributes:
         build: Link to a Build object
-        stock: Link to a StockItem object
+        stock_item: Link to a StockItem object
         quantity: Number of units allocated
     """
 
diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py
index 9086d66f1f..06591e2eb9 100644
--- a/InvenTree/company/models.py
+++ b/InvenTree/company/models.py
@@ -42,6 +42,19 @@ 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).
+
+    Attributes:
+        name: Brief name of the company
+        description: Longer form description
+        website: URL for the company website
+        address: Postal address
+        phone: contact phone number
+        email: contact email address
+        URL: Secondary URL e.g. for link to internal Wiki page 
+        image: Company image / logo
+        notes: Extra notes about the company
+        is_customer: boolean value, is this company a customer
+        is_supplier: boolean value, is this company a supplier
     """
 
     name = models.CharField(max_length=100, blank=False, unique=True,
@@ -101,9 +114,19 @@ class Company(models.Model):
 
 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
+    A Company may have zero or more associated Contact objects.
+
+    Attributes:
+        company: Company link for this contact
+        name: Name of the contact
+        phone: contact phone number
+        email: contact email
+        role: position in company
     """
 
+    company = models.ForeignKey(Company, related_name='contacts',
+                                on_delete=models.CASCADE)
+
     name = models.CharField(max_length=100)
 
     phone = models.CharField(max_length=100, blank=True)
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index f91f4d3d7d..fb72da60b9 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -116,9 +116,30 @@ def rename_part_image(instance, filename):
 
 
 class Part(models.Model):
-    """ Represents an abstract part
-    Parts can be "stocked" in multiple warehouses,
-    and can be combined to form other parts
+    """ The Part object represents an abstract part, the 'concept' of an actual entity.
+
+    An actual physical instance of a Part is a StockItem which is treated separately.
+
+    Parts can be used to create other parts (as part of a Bill of Materials or BOM).
+
+    Attributes:
+        name: Brief name for this part
+        description: Longer form description of the part
+        category: The PartCategory to which this part belongs
+        IPN: Internal part number (optional)
+        URL: Link to an external page with more information about this part (e.g. internal Wiki)
+        image: Image of this part
+        default_location: Where the item is normally stored (may be null)
+        default_supplier: The default SupplierPart which should be used to procure and stock this part
+        minimum_stock: Minimum preferred quantity to keep in stock
+        units: Units of measure for this part (default='pcs')
+        salable: Can this part be sold to customers?
+        buildable: Can this part be build from other parts? 
+        consumable: Can this part be used to make other parts?
+        purchaseable: Can this part be purchased from suppliers?
+        trackable: Trackable parts can have unique serial numbers assigned, etc, etc
+        active: Is this part active? Parts are deactivated instead of being deleted
+        notes: Additional notes field for this part
     """
 
     def get_absolute_url(self):
@@ -133,26 +154,19 @@ class Part(models.Model):
         else:
             return static('/img/blank_image.png')
 
-    # Short name of the part
     name = models.CharField(max_length=100, unique=True, blank=False, help_text='Part name (must be unique)')
 
-    # Longer description of the part (optional)
     description = models.CharField(max_length=250, blank=False, help_text='Part description')
 
-    # Internal Part Number (optional)
-    # Potentially multiple parts map to the same internal IPN (variants?)
-    # So this does not have to be unique
-    IPN = models.CharField(max_length=100, blank=True, help_text='Internal Part Number')
-
-    # Provide a URL for an external link
-    URL = models.URLField(blank=True, help_text='Link to extenal URL')
-
-    # Part category - all parts must be assigned to a category
     category = models.ForeignKey(PartCategory, related_name='parts',
                                  null=True, blank=True,
                                  on_delete=models.DO_NOTHING,
                                  help_text='Part category')
 
+    IPN = models.CharField(max_length=100, blank=True, help_text='Internal Part Number')
+
+    URL = models.URLField(blank=True, help_text='Link to extenal URL')
+
     image = models.ImageField(upload_to=rename_part_image, max_length=255, null=True, blank=True)
 
     default_location = models.ForeignKey('stock.StockLocation', on_delete=models.SET_NULL,
@@ -183,23 +197,18 @@ class Part(models.Model):
         # Default case - no default category found
         return None
 
-    # Default supplier part
     default_supplier = models.ForeignKey('part.SupplierPart',
                                          on_delete=models.SET_NULL,
                                          blank=True, null=True,
                                          help_text='Default supplier part',
                                          related_name='default_parts')
 
-    # Minimum "allowed" stock level
     minimum_stock = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)], help_text='Minimum allowed stock level')
 
-    # Units of quantity for this part. Default is "pcs"
     units = models.CharField(max_length=20, default="pcs", blank=True, help_text='Stock keeping units for this part')
 
-    # Can this part be built from other parts?
     buildable = models.BooleanField(default=False, help_text='Can this part be built from other parts?')
 
-    # Can this part be used to make other parts?
     consumable = models.BooleanField(default=True, help_text='Can this part be used to build other parts?')
 
     # Is this part "trackable"?
@@ -434,6 +443,11 @@ def attach_file(instance, filename):
 class PartAttachment(models.Model):
     """ A PartAttachment links a file to a part
     Parts can have multiple files such as datasheets, etc
+
+    Attributes:
+        part: Link to a Part object
+        attachment: File
+        comment: String descriptor for the attachment
     """
 
     part = models.ForeignKey(Part, on_delete=models.CASCADE,
@@ -454,6 +468,10 @@ class PartStar(models.Model):
 
     It is used to designate a Part as 'starred' (or favourited) for a given User,
     so that the user can track a list of their favourite parts.
+
+    Attributes:
+        part: Link to a Part object
+        user: Link to a User object
     """
 
     part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='starred_users')
@@ -467,7 +485,13 @@ class PartStar(models.Model):
 class BomItem(models.Model):
     """ A BomItem links a part to its component items.
     A part can have a BOM (bill of materials) which defines
-    which parts are required (and in what quatity) to make it
+    which parts are required (and in what quatity) to make it.
+
+    Attributes:
+        part: Link to the parent part (the part that will be produced)
+        sub_part: Link to the child part (the part that will be consumed)
+        quantity: Number of 'sub_parts' consumed to produce one 'part'
+        note: Note field for this BOM item
     """
 
     def get_absolute_url(self):
@@ -530,8 +554,23 @@ 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
+
+    Attributes:
+        part: Link to the master Part
+        supplier: Company that supplies this SupplierPart object
+        SKU: Stock keeping unit (supplier part number)
+        manufacturer: Manufacturer name
+        MPN: Manufacture part number
+        URL: Link to external website for this part
+        description: Descriptive notes field
+        note: Longer form note field
+        single_price: Default price for a single unit
+        base_cost: Base charge added to order independent of quantity e.g. "Reeling Fee"
+        multiple: Multiple that the part is provided in
+        minimum: MOQ (minimum order quantity) required for purchase
+        lead_time: Supplier lead time
+        packaging: packaging that the part is supplied in, e.g. "Reel"
     """
 
     def get_absolute_url(self):
@@ -540,8 +579,6 @@ class SupplierPart(models.Model):
     class Meta:
         unique_together = ('part', 'supplier', 'SKU')
 
-    # Link to an actual part
-# The part will have a field 'supplier_parts' which links to the supplier part options
     part = models.ForeignKey(Part, on_delete=models.CASCADE,
                              related_name='supplier_parts',
                              limit_choices_to={'purchaseable': True},
@@ -564,25 +601,18 @@ class SupplierPart(models.Model):
 
     description = models.CharField(max_length=250, blank=True, help_text='Supplier part description')
 
-    # Note attached to this BOM line item
     note = models.CharField(max_length=100, blank=True, help_text='Notes')
 
-    # Default price for a single unit
     single_price = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text='Price for single quantity')
 
-    # Base charge added to order independent of quantity e.g. "Reeling Fee"
     base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text='Minimum charge (e.g. stocking fee)')
 
-    # packaging that the part is supplied in, e.g. "Reel"
     packaging = models.CharField(max_length=50, blank=True, help_text='Part packaging')
-
-    # multiple that the part is provided in
+    
     multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text='Order multiple')
 
-    # Mimumum number required to order
     minimum = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text='Minimum order quantity (MOQ)')
 
-    # lead time for parts that cannot be delivered immediately
     lead_time = models.DurationField(blank=True, null=True)
 
     @property
@@ -654,9 +684,14 @@ class SupplierPart(models.Model):
 
 
 class SupplierPriceBreak(models.Model):
-    """ Represents a quantity price break for a SupplierPart
+    """ Represents a quantity price break for a SupplierPart.
     - Suppliers can offer discounts at larger quantities
     - SupplierPart(s) may have zero-or-more associated SupplierPriceBreak(s)
+
+    Attributes:
+        part: Link to a SupplierPart object that this price break applies to
+        quantity: Quantity required for price break
+        cost: Cost at specified quantity
     """
 
     part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='price_breaks')
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 67d92c87e7..b4216c46c1 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -77,10 +77,23 @@ def before_delete_stock_location(sender, instance, using, **kwargs):
 
 class StockItem(models.Model):
     """
-    A 'StockItem' instance represents a quantity of physical instances of a part.
-    It may exist in a StockLocation, or as part of a sub-assembly installed into another StockItem
-    StockItems may be tracked using batch or serial numbers.
-    If a serial number is assigned, then StockItem cannot have a quantity other than 1
+    A StockItem object represents a quantity of physical instances of a part.
+    
+    Attributes:
+        part: Link to the master abstract part that this StockItem is an instance of
+        supplier_part: Link to a specific SupplierPart (optional)
+        location: Where this StockItem is located
+        quantity: Number of stocked units
+        batch: Batch number for this StockItem
+        URL: Optional URL to link to external resource
+        updated: Date that this stock item was last updated (auto)
+        stocktake_date: Date of last stocktake for this item
+        stocktake_user: User that performed the most recent stocktake
+        review_needed: Flag if StockItem needs review
+        delete_on_deplete: If True, StockItem will be deleted when the stock level gets to zero
+        status: Status of this StockItem (ref: ITEM_STATUS_CODES)
+        notes: Extra notes field
+        infinite: If True this StockItem can never be exhausted
     """
 
     def save(self, *args, **kwargs):
@@ -171,46 +184,33 @@ class StockItem(models.Model):
             }
         )
 
-    # The 'master' copy of the part of which this stock item is an instance
     part = models.ForeignKey('part.Part', on_delete=models.CASCADE, related_name='locations', help_text='Base part')
 
-    # The 'supplier part' used in this instance. May be null if no supplier parts are defined the master part
     supplier_part = models.ForeignKey('part.SupplierPart', blank=True, null=True, on_delete=models.SET_NULL,
                                       help_text='Select a matching supplier part for this stock item')
 
-    # Where the part is stored. If the part has been used to build another stock item, the location may not make sense
     location = models.ForeignKey(StockLocation, on_delete=models.DO_NOTHING,
                                  related_name='stock_items', blank=True, null=True,
                                  help_text='Where is this stock item located?')
 
-    # If this StockItem belongs to another StockItem (e.g. as part of a sub-assembly)
     belongs_to = models.ForeignKey('self', on_delete=models.DO_NOTHING,
                                    related_name='owned_parts', blank=True, null=True,
                                    help_text='Is this item installed in another item?')
 
-    # The StockItem may be assigned to a particular customer
     customer = models.ForeignKey('company.Company', on_delete=models.SET_NULL,
                                  related_name='stockitems', blank=True, null=True,
                                  help_text='Item assigned to customer?')
 
-    # Optional serial number
     serial = models.PositiveIntegerField(blank=True, null=True,
                                          help_text='Serial number for this item')
-
-    # Optional URL to link to external resource
+ 
     URL = models.URLField(max_length=125, blank=True)
 
-    # Optional batch information
     batch = models.CharField(max_length=100, blank=True, null=True,
                              help_text='Batch code for this stock item')
 
-    # If this part was produced by a build, point to that build here
-    # build = models.ForeignKey('build.Build', on_delete=models.SET_NULL, blank=True, null=True)
-
-    # Quantity of this stock item. Value may be overridden by other settings
     quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)], default=1)
 
-    # Last time this item was updated (set automagically)
     updated = models.DateField(auto_now=True)
 
     # last time the stock was checked / counted
@@ -409,33 +409,34 @@ class StockItem(models.Model):
 
 
 class StockItemTracking(models.Model):
-    """ Stock tracking entry
+    """ Stock tracking entry - breacrumb for keeping track of automated stock transactions
+
+    Attributes:
+        item: Link to StockItem
+        date: Date that this tracking info was created
+        title: Title of this tracking info (generated by system)
+        notes: Associated notes (input by user)
+        user: The user associated with this tracking info
+        quantity: The StockItem quantity at this point in time
     """
 
     def get_absolute_url(self):
         return '/stock/track/{pk}'.format(pk=self.id)
         # return reverse('stock-tracking-detail', kwargs={'pk': self.id})
 
-    # Stock item
     item = models.ForeignKey(StockItem, on_delete=models.CASCADE,
                              related_name='tracking_info')
 
-    # Date this entry was created (cannot be edited)
     date = models.DateTimeField(auto_now_add=True, editable=False)
 
-    # Short-form title for this tracking entry
     title = models.CharField(blank=False, max_length=250)
 
-    # Optional longer description
     notes = models.TextField(blank=True)
 
-    # Which user created this tracking entry?
     user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
 
-    # Was this tracking note auto-generated by the system?
     system = models.BooleanField(default=False)
 
-    # Keep track of the StockItem quantity throughout the tracking history
     quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)], default=1)
 
     # TODO