Add missing args to docstrings

This commit is contained in:
Matthias 2022-05-28 20:27:20 +02:00
parent ff9873f92c
commit c24882bf66
No known key found for this signature in database
GPG Key ID: AB6D0E6C4CB65093
15 changed files with 165 additions and 106 deletions

View File

@ -162,7 +162,7 @@ def normalize(d):
def increment(n): def increment(n):
"""Attempt to increment an integer (or a string that looks like an integer!) """Attempt to increment an integer (or a string that looks like an integer).
e.g. e.g.
@ -332,7 +332,7 @@ def GetExportFormats():
] ]
def DownloadFile(data, filename, content_type='application/text', inline=False): def DownloadFile(data, filename, content_type='application/text', inline=False) -> StreamingHttpResponse:
"""Create a dynamic file for the user to download. """Create a dynamic file for the user to download.
Args: Args:
@ -362,7 +362,7 @@ def DownloadFile(data, filename, content_type='application/text', inline=False):
def extract_serial_numbers(serials, expected_quantity, next_number: int): def extract_serial_numbers(serials, expected_quantity, next_number: int):
"""Attempt to extract serial numbers from an input string: """Attempt to extract serial numbers from an input string.
Requirements: Requirements:
- Serial numbers can be either strings, or integers - Serial numbers can be either strings, or integers

View File

@ -211,7 +211,7 @@ class AjaxMixin(InvenTreeRoleMixin):
ajax_form_title = '' ajax_form_title = ''
def get_form_title(self): def get_form_title(self):
"""Default implementation - return the ajax_form_title variable""" """Default implementation - return the ajax_form_title variable."""
return self.ajax_form_title return self.ajax_form_title
def get_param(self, name, method='GET'): def get_param(self, name, method='GET'):
@ -230,7 +230,7 @@ class AjaxMixin(InvenTreeRoleMixin):
return self.request.GET.get(name, None) return self.request.GET.get(name, None)
def get_data(self): def get_data(self):
"""Get extra context data (default implementation is empty dict) """Get extra context data (default implementation is empty dict).
Returns: Returns:
dict object (empty) dict object (empty)
@ -444,6 +444,9 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
Args: Args:
object - The current object, to be updated object - The current object, to be updated
form - The validated form form - The validated form
Returns:
object instance for supplied form
""" """
self.object = form.save() self.object = form.save()

View File

@ -340,9 +340,12 @@ class Build(MPTTModel, ReferenceIndexingMixin):
@property @property
def is_overdue(self): def is_overdue(self):
"""Returns true if this build is "overdue": """Returns true if this build is "overdue".
Makes use of the OVERDUE_FILTER to avoid code duplication Makes use of the OVERDUE_FILTER to avoid code duplication
Returns:
bool: Is the build overdue
""" """
query = Build.objects.filter(pk=self.pk) query = Build.objects.filter(pk=self.pk)
query = query.filter(Build.OVERDUE_FILTER) query = query.filter(Build.OVERDUE_FILTER)
@ -574,9 +577,9 @@ class Build(MPTTModel, ReferenceIndexingMixin):
def unallocateStock(self, bom_item=None, output=None): def unallocateStock(self, bom_item=None, output=None):
"""Unallocate stock from this Build. """Unallocate stock from this Build.
Arguments: Args:
- bom_item: Specify a particular BomItem to unallocate stock against bom_item: Specify a particular BomItem to unallocate stock against
- output: Specify a particular StockItem (output) to unallocate stock against output: Specify a particular StockItem (output) to unallocate stock against
""" """
allocations = BuildItem.objects.filter( allocations = BuildItem.objects.filter(
build=self, build=self,
@ -693,8 +696,9 @@ class Build(MPTTModel, ReferenceIndexingMixin):
@transaction.atomic @transaction.atomic
def delete_output(self, output): def delete_output(self, output):
"""Remove a build output from the database: """Remove a build output from the database.
Executes:
- Unallocate any build items against the output - Unallocate any build items against the output
- Delete the output StockItem - Delete the output StockItem
""" """
@ -882,8 +886,8 @@ class Build(MPTTModel, ReferenceIndexingMixin):
"""Get the quantity of a part required to complete the particular build output. """Get the quantity of a part required to complete the particular build output.
Args: Args:
part: The Part object bom_item: The Part object
output - The particular build output (StockItem) output: The particular build output (StockItem)
""" """
quantity = bom_item.quantity quantity = bom_item.quantity
@ -895,14 +899,14 @@ class Build(MPTTModel, ReferenceIndexingMixin):
return quantity return quantity
def allocated_bom_items(self, bom_item, output=None): def allocated_bom_items(self, bom_item, output=None):
"""Return all BuildItem objects which allocate stock of <bom_item> to <output> """Return all BuildItem objects which allocate stock of <bom_item> to <output>.
Note that the bom_item may allow variants, or direct substitutes, Note that the bom_item may allow variants, or direct substitutes,
making things difficult. making things difficult.
Args: Args:
bom_item - The BomItem object bom_item: The BomItem object
output - Build output (StockItem). output: Build output (StockItem).
""" """
allocations = BuildItem.objects.filter( allocations = BuildItem.objects.filter(
build=self, build=self,
@ -991,7 +995,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
@property @property
def required_parts(self): def required_parts(self):
"""Returns a list of parts required to build this part (BOM)""" """Returns a list of parts required to build this part (BOM)."""
parts = [] parts = []
for item in self.bom_items: for item in self.bom_items:

View File

@ -1,3 +1,4 @@
from ctypes import Union
from django.test import TestCase from django.test import TestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -193,12 +194,12 @@ class BuildTest(BuildTestBase):
quantity=99 quantity=99
) )
def allocate_stock(self, output, allocations): def allocate_stock(self, output: Union[StockItem, None], allocations: dict[StockItem, int]) -> None:
"""Allocate stock to this build, against a particular output. """Allocate stock to this build, against a particular output.
Args: Args:
output - StockItem object (or None) output (Union[StockItem, None]): StockItem object or None
allocations - Map of {StockItem: quantity} allocations (dict[StockItem, int]): Map of `{StockItem: quantity}`
""" """
for item, quantity in allocations.items(): for item, quantity in allocations.items():
BuildItem.objects.create( BuildItem.objects.create(

View File

@ -94,8 +94,11 @@ class FileManager:
"""Try to match a header (from the file) to a list of known headers. """Try to match a header (from the file) to a list of known headers.
Args: Args:
header - Header name to look for header (Any): Header name to look for
threshold - Match threshold for fuzzy search threshold (int, optional): Match threshold for fuzzy search. Defaults to 80.
Returns:
Any: Matched headers
""" """
# Replace null values with empty string # Replace null values with empty string
if header is None: if header is None:

View File

@ -477,7 +477,7 @@ class BaseInvenTreeSetting(models.Model):
pass pass
def choices(self): def choices(self):
"""Return the available choices for this setting (or None if no choices are defined)""" """Return the available choices for this setting (or None if no choices are defined)."""
return self.__class__.get_setting_choices(self.key, **self.get_kwargs()) return self.__class__.get_setting_choices(self.key, **self.get_kwargs())
def valid_options(self): def valid_options(self):
@ -1519,7 +1519,7 @@ class PriceBreak(models.Model):
"""Convert the unit-price at this price break to the specified currency code. """Convert the unit-price at this price break to the specified currency code.
Args: Args:
currency_code - The currency code to convert to (e.g "USD" or "AUD") currency_code: The currency code to convert to (e.g "USD" or "AUD")
""" """
try: try:
converted = convert_money(self.price, currency_code) converted = convert_money(self.price, currency_code)

View File

@ -231,7 +231,7 @@ class Company(models.Model):
return self.purchase_orders.filter(status__in=PurchaseOrderStatus.OPEN) return self.purchase_orders.filter(status__in=PurchaseOrderStatus.OPEN)
def pending_purchase_orders(self): def pending_purchase_orders(self):
"""Return purchase orders which are PENDING (not yet issued)""" """Return purchase orders which are PENDING (not yet issued)."""
return self.purchase_orders.filter(status=PurchaseOrderStatus.PENDING) return self.purchase_orders.filter(status=PurchaseOrderStatus.PENDING)
def closed_purchase_orders(self): def closed_purchase_orders(self):
@ -598,12 +598,12 @@ class SupplierPart(models.Model):
def unit_pricing(self): def unit_pricing(self):
return self.get_price(1) return self.get_price(1)
def add_price_break(self, quantity, price): def add_price_break(self, quantity, price) -> None:
"""Create a new price break for this part. """Create a new price break for this part.
Args: Args:
quantity - Numerical quantity quantity: Numerical quantity
price - Must be a Money object price: Must be a Money object
""" """
# Check if a price break at that quantity already exists... # Check if a price break at that quantity already exists...
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists(): if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():

View File

@ -329,16 +329,24 @@ class PurchaseOrder(Order):
return reverse('po-detail', kwargs={'pk': self.id}) return reverse('po-detail', kwargs={'pk': self.id})
@transaction.atomic @transaction.atomic
def add_line_item(self, supplier_part, quantity, group=True, reference='', purchase_price=None): def add_line_item(self, supplier_part, quantity, group: bool = True, reference: str = '', purchase_price=None):
"""Add a new line item to this purchase order. This function will check that: """Add a new line item to this purchase order.
This function will check that:
* The supplier part matches the supplier specified for this purchase order * The supplier part matches the supplier specified for this purchase order
* The quantity is greater than zero * The quantity is greater than zero
Args: Args:
supplier_part - The supplier_part to add supplier_part: The supplier_part to add
quantity - The number of items to add quantity : The number of items to add
group - If True, this new quantity will be added to an existing line item for the same supplier_part (if it exists) group (bool, optional): If True, this new quantity will be added to an existing line item for the same supplier_part (if it exists). Defaults to True.
reference (str, optional): Reference to item. Defaults to ''.
purchase_price (optional): Price of item. Defaults to None.
Raises:
ValidationError: quantity is smaller than 0
ValidationError: quantity is not type int
ValidationError: supplier is not supplier of purchase order
""" """
try: try:
quantity = int(quantity) quantity = int(quantity)
@ -416,7 +424,11 @@ class PurchaseOrder(Order):
return query.exists() return query.exists()
def can_cancel(self): def can_cancel(self):
"""A PurchaseOrder can only be cancelled under the following circumstances:""" """A PurchaseOrder can only be cancelled under the following circumstances.
- Status is PLACED
- Status is PENDING
"""
return self.status in [ return self.status in [
PurchaseOrderStatus.PLACED, PurchaseOrderStatus.PLACED,
PurchaseOrderStatus.PENDING PurchaseOrderStatus.PENDING
@ -655,7 +667,7 @@ class SalesOrder(Order):
@property @property
def is_overdue(self): def is_overdue(self):
"""Returns true if this SalesOrder is "overdue": """Returns true if this SalesOrder is "overdue".
Makes use of the OVERDUE_FILTER to avoid code duplication. Makes use of the OVERDUE_FILTER to avoid code duplication.
""" """
@ -692,7 +704,7 @@ class SalesOrder(Order):
return False return False
def is_completed(self): def is_completed(self):
"""Check if this order is "shipped" (all line items delivered)""" """Check if this order is "shipped" (all line items delivered)."""
return self.lines.count() > 0 and all([line.is_completed() for line in self.lines.all()]) return self.lines.count() > 0 and all([line.is_completed() for line in self.lines.all()])
def can_complete(self, raise_error=False): def can_complete(self, raise_error=False):
@ -749,8 +761,9 @@ class SalesOrder(Order):
@transaction.atomic @transaction.atomic
def cancel_order(self): def cancel_order(self):
"""Cancel this order (only if it is "pending") """Cancel this order (only if it is "pending").
Executes:
- Mark the order as 'cancelled' - Mark the order as 'cancelled'
- Delete any StockItems which have been allocated - Delete any StockItems which have been allocated
""" """
@ -1110,7 +1123,7 @@ class SalesOrderLineItem(OrderLineItem):
return self.allocated_quantity() > self.quantity return self.allocated_quantity() > self.quantity
def is_completed(self): def is_completed(self):
"""Return True if this line item is completed (has been fully shipped)""" """Return True if this line item is completed (has been fully shipped)."""
return self.shipped >= self.quantity return self.shipped >= self.quantity
@ -1222,8 +1235,9 @@ class SalesOrderShipment(models.Model):
@transaction.atomic @transaction.atomic
def complete_shipment(self, user, **kwargs): def complete_shipment(self, user, **kwargs):
"""Complete this particular shipment: """Complete this particular shipment.
Executes:
1. Update any stock items associated with this shipment 1. Update any stock items associated with this shipment
2. Update the "shipped" quantity of all associated line items 2. Update the "shipped" quantity of all associated line items
3. Set the "shipment_date" to now 3. Set the "shipment_date" to now
@ -1295,8 +1309,9 @@ class SalesOrderAllocation(models.Model):
return reverse('api-so-allocation-list') return reverse('api-so-allocation-list')
def clean(self): def clean(self):
"""Validate the SalesOrderAllocation object: """Validate the SalesOrderAllocation object.
Executes:
- Cannot allocate stock to a line item without a part reference - Cannot allocate stock to a line item without a part reference
- The referenced part must match the part associated with the line item - The referenced part must match the part associated with the line item
- Allocated quantity cannot exceed the quantity of the stock item - Allocated quantity cannot exceed the quantity of the stock item
@ -1385,8 +1400,9 @@ class SalesOrderAllocation(models.Model):
return self.item.purchase_order return self.item.purchase_order
def complete_allocation(self, user): def complete_allocation(self, user):
"""Complete this allocation (called when the parent SalesOrder is marked as "shipped"): """Complete this allocation (called when the parent SalesOrder is marked as "shipped").
Executes:
- Determine if the referenced StockItem needs to be "split" (if allocated quantity != stock quantity) - Determine if the referenced StockItem needs to be "split" (if allocated quantity != stock quantity)
- Mark the StockItem as belonging to the Customer (this will remove it from stock) - Mark the StockItem as belonging to the Customer (this will remove it from stock)
""" """

View File

@ -11,7 +11,7 @@ from company.models import ManufacturerPart, SupplierPart
from InvenTree.helpers import DownloadFile, GetExportFormats, normalize from InvenTree.helpers import DownloadFile, GetExportFormats, normalize
from .admin import BomItemResource from .admin import BomItemResource
from .models import BomItem from .models import BomItem, Part
def IsValidBOMFormat(fmt): def IsValidBOMFormat(fmt):
@ -20,7 +20,7 @@ def IsValidBOMFormat(fmt):
def MakeBomTemplate(fmt): def MakeBomTemplate(fmt):
"""Generate a Bill of Materials upload template file (for user download)""" """Generate a Bill of Materials upload template file (for user download)."""
fmt = fmt.strip().lower() fmt = fmt.strip().lower()
if not IsValidBOMFormat(fmt): if not IsValidBOMFormat(fmt):
@ -42,12 +42,21 @@ def MakeBomTemplate(fmt):
return DownloadFile(data, filename) return DownloadFile(data, filename)
def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=False, stock_data=False, supplier_data=False, manufacturer_data=False): def ExportBom(part: Part, fmt='csv', cascade: bool = False, max_levels: int = None, parameter_data=False, stock_data=False, supplier_data=False, manufacturer_data=False):
"""Export a BOM (Bill of Materials) for a given part. """Export a BOM (Bill of Materials) for a given part.
Args: Args:
fmt: File format (default = 'csv') part (Part): Part for which the BOM should be exported
cascade: If True, multi-level BOM output is supported. Otherwise, a flat top-level-only BOM is exported. fmt (str, optional): file format. Defaults to 'csv'.
cascade (bool, optional): If True, multi-level BOM output is supported. Otherwise, a flat top-level-only BOM is exported.. Defaults to False.
max_levels (int, optional): Levels of items that should be included. None for np sublevels. Defaults to None.
parameter_data (bool, optional): Additonal data that should be added. Defaults to False.
stock_data (bool, optional): Additonal data that should be added. Defaults to False.
supplier_data (bool, optional): Additonal data that should be added. Defaults to False.
manufacturer_data (bool, optional): Additonal data that should be added. Defaults to False.
Returns:
StreamingHttpResponse: Response that can be passed to the endpoint
""" """
if not IsValidBOMFormat(fmt): if not IsValidBOMFormat(fmt):
fmt = 'csv' fmt = 'csv'

View File

@ -1,5 +1,7 @@
"""Part database model definitions.""" """Part database model definitions."""
from __future__ import annotations
import decimal import decimal
import hashlib import hashlib
import logging import logging
@ -110,11 +112,14 @@ class PartCategory(MetadataMixin, InvenTreeTree):
verbose_name = _("Part Category") verbose_name = _("Part Category")
verbose_name_plural = _("Part Categories") verbose_name_plural = _("Part Categories")
def get_parts(self, cascade=True): def get_parts(self, cascade=True) -> set[Part]:
"""Return a queryset for all parts under this category. """Return a queryset for all parts under this category.
Args: Args:
cascade - If True, also look under subcategories (default = True) cascade (bool, optional): If True, also look under subcategories. Defaults to True.
Returns:
set[Part]: All matching parts
""" """
if cascade: if cascade:
"""Select any parts which exist in this category or any child categories.""" """Select any parts which exist in this category or any child categories."""
@ -129,7 +134,7 @@ class PartCategory(MetadataMixin, InvenTreeTree):
return self.partcount() return self.partcount()
def partcount(self, cascade=True, active=False): def partcount(self, cascade=True, active=False):
"""Return the total part count under this category (including children of child categories)""" """Return the total part count under this category (including children of child categories)."""
query = self.get_parts(cascade=cascade) query = self.get_parts(cascade=cascade)
if active: if active:
@ -1071,8 +1076,9 @@ class Part(MetadataMixin, MPTTModel):
@property @property
def net_stock(self): def net_stock(self):
"""Return the 'net' stock. It takes into account: """Return the 'net' stock.
It takes into account:
- Stock on hand (total_stock) - Stock on hand (total_stock)
- Stock on order (on_order) - Stock on order (on_order)
- Stock allocated (allocation_count) - Stock allocated (allocation_count)
@ -1370,12 +1376,12 @@ class Part(MetadataMixin, MPTTModel):
return queryset.prefetch_related('sub_part') return queryset.prefetch_related('sub_part')
def get_installed_part_options(self, include_inherited=True, include_variants=True): def get_installed_part_options(self, include_inherited: bool = True, include_variants: bool = True):
"""Return a set of all Parts which can be "installed" into this part, based on the BOM. """Return a set of all Parts which can be "installed" into this part, based on the BOM.
Arguments: Arguments:
include_inherited - If set, include BomItem entries defined for parent parts include_inherited (bool): If set, include BomItem entries defined for parent parts
include_variants - If set, include variant parts for BomItems which allow variants include_variants (bool): If set, include variant parts for BomItems which allow variants
""" """
parts = set() parts = set()
@ -1480,7 +1486,7 @@ class Part(MetadataMixin, MPTTModel):
return str(result_hash.digest()) return str(result_hash.digest())
def is_bom_valid(self): def is_bom_valid(self):
"""Check if the BOM is 'valid' - if the calculated checksum matches the stored value""" """Check if the BOM is 'valid' - if the calculated checksum matches the stored value."""
return self.get_bom_hash() == self.bom_checksum or not self.has_bom return self.get_bom_hash() == self.bom_checksum or not self.has_bom
@transaction.atomic @transaction.atomic
@ -1728,8 +1734,8 @@ class Part(MetadataMixin, MPTTModel):
"""Create a new price break for this part. """Create a new price break for this part.
Args: Args:
quantity - Numerical quantity quantity: Numerical quantity
price - Must be a Money object price: Must be a Money object
""" """
# Check if a price break at that quantity already exists... # Check if a price break at that quantity already exists...
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists(): if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
@ -1774,8 +1780,8 @@ class Part(MetadataMixin, MPTTModel):
"""Copy the BOM from another part. """Copy the BOM from another part.
Args: Args:
other - The part to copy the BOM from other: The part to copy the BOM from
clear - Remove existing BOM items first (default=True) clear (bool, optional): Remove existing BOM items first. Defaults to True.
""" """
# Ignore if the other part is actually this part? # Ignore if the other part is actually this part?
if other == self: if other == self:
@ -2017,10 +2023,9 @@ class Part(MetadataMixin, MPTTModel):
@property @property
def can_convert(self): def can_convert(self):
"""Check if this Part can be "converted" to a different variant: """Check if this Part can be "converted" to a different variant.
It can be converted if: It can be converted if:
a) It has non-virtual variant parts underneath it a) It has non-virtual variant parts underneath it
b) It has non-virtual template parts above it b) It has non-virtual template parts above it
c) It has non-virtual sibling variants c) It has non-virtual sibling variants
@ -2064,8 +2069,9 @@ class Part(MetadataMixin, MPTTModel):
return filtered_parts return filtered_parts
def get_related_parts(self): def get_related_parts(self):
"""Return list of tuples for all related parts: """Return list of tuples for all related parts.
Includes:
- first value is PartRelated object - first value is PartRelated object
- second value is matching Part object - second value is matching Part object
""" """
@ -2408,7 +2414,7 @@ class PartCategoryParameterTemplate(models.Model):
] ]
def __str__(self): def __str__(self):
"""String representation of a PartCategoryParameterTemplate (admin interface)""" """String representation of a PartCategoryParameterTemplate (admin interface)."""
if self.default_value: if self.default_value:
return f'{self.category.name} | {self.parameter_template.name} | {self.default_value}' return f'{self.category.name} | {self.parameter_template.name} | {self.default_value}'
else: else:
@ -2489,11 +2495,12 @@ class BomItem(models.Model, DataImportMixin):
return reverse('api-bom-list') return reverse('api-bom-list')
def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True): def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
"""Return a list of valid parts which can be allocated against this BomItem: """Return a list of valid parts which can be allocated against this BomItem.
- Include the referenced sub_part Includes:
- Include any directly specvified substitute parts - The referenced sub_part
- If allow_variants is True, allow all variants of sub_part - Any directly specvified substitute parts
- If allow_variants is True, all variants of sub_part
""" """
# Set of parts we will allow # Set of parts we will allow
parts = set() parts = set()
@ -2591,10 +2598,9 @@ class BomItem(models.Model, DataImportMixin):
) )
def get_item_hash(self): def get_item_hash(self):
"""Calculate the checksum hash of this BOM line item: """Calculate the checksum hash of this BOM line item.
The hash is calculated from the following fields: The hash is calculated from the following fields:
- Part.full_name (if the part name changes, the BOM checksum is invalidated) - Part.full_name (if the part name changes, the BOM checksum is invalidated)
- Quantity - Quantity
- Reference field - Reference field
@ -2794,8 +2800,9 @@ class BomItemSubstitute(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def validate_unique(self, exclude=None): def validate_unique(self, exclude=None):
"""Ensure that this BomItemSubstitute is "unique": """Ensure that this BomItemSubstitute is "unique".
Ensure:
- It cannot point to the same "part" as the "sub_part" of the parent "bom_item" - It cannot point to the same "part" as the "sub_part" of the parent "bom_item"
""" """
super().validate_unique(exclude=exclude) super().validate_unique(exclude=exclude)
@ -2830,7 +2837,7 @@ class BomItemSubstitute(models.Model):
class PartRelated(models.Model): class PartRelated(models.Model):
"""Store and handle related parts (eg. mating connector, crimps, etc.)""" """Store and handle related parts (eg. mating connector, crimps, etc.)."""
part_1 = models.ForeignKey(Part, related_name='related_parts_1', part_1 = models.ForeignKey(Part, related_name='related_parts_1',
verbose_name=_('Part 1'), on_delete=models.DO_NOTHING) verbose_name=_('Part 1'), on_delete=models.DO_NOTHING)

View File

@ -52,7 +52,7 @@ class BarcodeMixin:
"""Initialize the BarcodePlugin instance. """Initialize the BarcodePlugin instance.
Args: Args:
barcode_data - The raw barcode data barcode_data: The raw barcode data
""" """
self.data = barcode_data self.data = barcode_data

View File

@ -10,16 +10,17 @@ from plugin.registry import registry
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
def print_label(plugin_slug, label_image, label_instance=None, user=None): def print_label(plugin_slug: str, label_image, label_instance=None, user=None):
"""Print label with the provided plugin. """Print label with the provided plugin.
This task is nominally handled by the background worker. This task is nominally handled by the background worker.
If the printing fails (throws an exception) then the user is notified. If the printing fails (throws an exception) then the user is notified.
Arguments: Args:
plugin_slug: The unique slug (key) of the plugin plugin_slug (str): The unique slug (key) of the plugin
label_image: A PIL.Image image object to be printed label_image (_type_): A PIL.Image image object to be printed
label_instance (Union[LabelTemplate, None], optional): The template instance that should be printed. Defaults to None.
user (Union[User, None], optional): User that should be informed of errors. Defaults to None.
""" """
logger.info(f"Plugin '{plugin_slug}' is printing a label") logger.info(f"Plugin '{plugin_slug}' is printing a label")

View File

@ -46,13 +46,13 @@ class MetadataMixin(models.Model):
return self.metadata.get(key, backup_value) return self.metadata.get(key, backup_value)
def set_metadata(self, key: str, data, commit=True): def set_metadata(self, key: str, data, commit: bool = True):
"""Save the provided metadata under the provided key. """Save the provided metadata under the provided key.
Args: Args:
key: String key for saving metadata key (str): Key for saving metadata
data: Data object to save - must be able to be rendered as a JSON string data (Any): Data object to save - must be able to be rendered as a JSON string
overwrite: If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted commit (bool, optional): If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted. Defaults to True.
""" """
if self.metadata is None: if self.metadata is None:
# Handle a null field value # Handle a null field value

View File

@ -1,5 +1,7 @@
"""Stock database model definitions.""" """Stock database model definitions."""
from __future__ import annotations
import os import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
@ -129,8 +131,12 @@ class StockLocation(MetadataMixin, InvenTreeTree):
) )
@property @property
def barcode(self): def barcode(self) -> str:
"""Brief payload data (e.g. for labels)""" """Get Brief payload data (e.g. for labels).
Returns:
str: Brief pyload data
"""
return self.format_barcode(brief=True) return self.format_barcode(brief=True)
def get_stock_items(self, cascade=True): def get_stock_items(self, cascade=True):
@ -335,8 +341,9 @@ class StockItem(MetadataMixin, MPTTModel):
return None return None
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""Save this StockItem to the database. Performs a number of checks: """Save this StockItem to the database.
Performs a number of checks:
- Unique serial number requirement - Unique serial number requirement
- Adds a transaction note when the item is first created. - Adds a transaction note when the item is first created.
""" """
@ -433,10 +440,9 @@ class StockItem(MetadataMixin, MPTTModel):
raise ValidationError({"serial": _("StockItem with this serial number already exists")}) raise ValidationError({"serial": _("StockItem with this serial number already exists")})
def clean(self): def clean(self):
"""Validate the StockItem object (separate to field validation) """Validate the StockItem object (separate to field validation).
The following validation checks are performed: The following validation checks are performed:
- The 'part' and 'supplier_part.part' fields cannot point to the same Part object - The 'part' and 'supplier_part.part' fields cannot point to the same Part object
- The 'part' does not belong to itself - The 'part' does not belong to itself
- Quantity must be 1 if the StockItem has a serial number - Quantity must be 1 if the StockItem has a serial number
@ -558,7 +564,11 @@ class StockItem(MetadataMixin, MPTTModel):
@property @property
def barcode(self): def barcode(self):
"""Brief payload data (e.g. for labels)""" """Get Brief payload data (e.g. for labels).
Returns:
str: Brief pyload data
"""
return self.format_barcode(brief=True) return self.format_barcode(brief=True)
uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field")) uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field"))
@ -825,8 +835,9 @@ class StockItem(MetadataMixin, MPTTModel):
return self.expiry_date < today return self.expiry_date < today
def clearAllocations(self): def clearAllocations(self):
"""Clear all order allocations for this StockItem: """Clear all order allocations for this StockItem.
Clears:
- SalesOrder allocations - SalesOrder allocations
- Build allocations - Build allocations
""" """
@ -966,8 +977,9 @@ class StockItem(MetadataMixin, MPTTModel):
return max(self.quantity - self.allocation_count(), 0) return max(self.quantity - self.allocation_count(), 0)
def can_delete(self): def can_delete(self):
"""Can this stock item be deleted? It can NOT be deleted under the following circumstances: """Can this stock item be deleted?
It can NOT be deleted under the following circumstances:
- Has installed stock items - Has installed stock items
- Is installed inside another StockItem - Is installed inside another StockItem
- It has been assigned to a SalesOrder - It has been assigned to a SalesOrder
@ -981,13 +993,16 @@ class StockItem(MetadataMixin, MPTTModel):
return True return True
def get_installed_items(self, cascade=False): def get_installed_items(self, cascade: bool = False) -> set[StockItem]:
"""Return all stock items which are *installed* in this one! """Return all stock items which are *installed* in this one!
Args:
cascade - Include items which are installed in items which are installed in items
Note: This function is recursive, and may result in a number of database hits! Note: This function is recursive, and may result in a number of database hits!
Args:
cascade (bool, optional): Include items which are installed in items which are installed in items. Defaults to False.
Returns:
set[StockItem]: Sll stock items which are installed
""" """
installed = set() installed = set()
@ -1157,15 +1172,14 @@ class StockItem(MetadataMixin, MPTTModel):
def has_tracking_info(self): def has_tracking_info(self):
return self.tracking_info_count > 0 return self.tracking_info_count > 0
def add_tracking_entry(self, entry_type, user, deltas=None, notes='', **kwargs): def add_tracking_entry(self, entry_type: int, user: User, deltas: dict = None, notes: str = '', **kwargs):
"""Add a history tracking entry for this StockItem. """Add a history tracking entry for this StockItem.
Args: Args:
entry_type - Integer code describing the "type" of historical action (see StockHistoryCode) entry_type (int): Code describing the "type" of historical action (see StockHistoryCode)
user - The user performing this action user (User): The user performing this action
deltas - A map of the changes made to the model deltas (dict, optional): A map of the changes made to the model. Defaults to None.
notes - User notes associated with this tracking entry notes (str, optional): URL associated with this tracking entry. Defaults to ''.
url - Optional URL associated with this tracking entry
""" """
if deltas is None: if deltas is None:
deltas = {} deltas = {}
@ -1915,7 +1929,7 @@ class StockItemAttachment(InvenTreeAttachment):
class StockItemTracking(models.Model): class StockItemTracking(models.Model):
"""Stock tracking entry - used for tracking history of a particular StockItem """Stock tracking entry - used for tracking history of a particular StockItem.
Note: 2021-05-11 Note: 2021-05-11
The legacy StockTrackingItem model contained very litle information about the "history" of the item. The legacy StockTrackingItem model contained very litle information about the "history" of the item.

View File

@ -348,12 +348,12 @@ def update_group_roles(group, debug=False):
permissions_to_delete = set() permissions_to_delete = set()
def add_model(name, action, allowed): def add_model(name, action, allowed):
"""Add a new model to the pile: """Add a new model to the pile.
Args: Args:
name - The name of the model e.g. part_part name: The name of the model e.g. part_part
action - The permission action e.g. view action: The permission action e.g. view
allowed - Whether or not the action is allowed allowed: Whether or not the action is allowed
""" """
if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover
raise ValueError("Action {a} is invalid".format(a=action)) raise ValueError("Action {a} is invalid".format(a=action))
@ -400,7 +400,7 @@ def update_group_roles(group, debug=False):
"""Find the permission object in the database, from the simplified permission string. """Find the permission object in the database, from the simplified permission string.
Args: Args:
permission_string - a simplified permission_string e.g. 'part.view_partcategory' permission_string: a simplified permission_string e.g. 'part.view_partcategory'
Returns the permission object in the database associated with the permission string Returns the permission object in the database associated with the permission string
""" """
@ -521,10 +521,11 @@ class Owner(models.Model):
@classmethod @classmethod
def get_owners_matching_user(cls, user): def get_owners_matching_user(cls, user):
"""Return all "owner" objects matching the provided user: """Return all "owner" objects matching the provided user.
A) An exact match for the user Includes:
B) Any groups that the user is a part of - An exact match for the user
- Any groups that the user is a part of
""" """
user_type = ContentType.objects.get(app_label='auth', model='user') user_type = ContentType.objects.get(app_label='auth', model='user')
group_type = ContentType.objects.get(app_label='auth', model='group') group_type = ContentType.objects.get(app_label='auth', model='group')