reformat comments 1

This commit is contained in:
Matthias 2022-05-28 02:04:02 +02:00
parent 959e4bb28e
commit 9b40fddf7c
No known key found for this signature in database
GPG Key ID: AB6D0E6C4CB65093
22 changed files with 262 additions and 682 deletions

View File

@ -567,8 +567,7 @@ def rename_asset(instance, filename):
class ReportAsset(models.Model):
"""
Asset file for use in report templates.
"""Asset file for use in report templates.
For example, an image to use in a header file.
Uploaded asset files appear in MEDIA_ROOT/report/assets,
and can be loaded in a template using the {% report_asset <filename> %} tag.

View File

@ -1,6 +1,4 @@
"""
Template tags for rendering various barcodes
"""
"""Template tags for rendering various barcodes"""
import base64
from io import BytesIO
@ -14,12 +12,10 @@ register = template.Library()
def image_data(img, fmt='PNG'):
"""
Convert an image into HTML renderable data
"""Convert an image into HTML renderable data
Returns a string ``data:image/FMT;base64,xxxxxxxxx`` which can be rendered to an <img> tag
"""
buffered = BytesIO()
img.save(buffered, format=fmt)
@ -30,8 +26,7 @@ def image_data(img, fmt='PNG'):
@register.simple_tag()
def qrcode(data, **kwargs):
"""
Return a byte-encoded QR code image
"""Return a byte-encoded QR code image
Optional kwargs
---------------
@ -39,7 +34,6 @@ def qrcode(data, **kwargs):
fill_color: Fill color (default = black)
back_color: Background color (default = white)
"""
# Construct "default" values
params = dict(
box_size=20,
@ -63,10 +57,7 @@ def qrcode(data, **kwargs):
@register.simple_tag()
def barcode(data, barcode_class='code128', **kwargs):
"""
Render a barcode
"""
"""Render a barcode"""
constructor = python_barcode.get_barcode_class(barcode_class)
data = str(data).zfill(constructor.digits)

View File

@ -1,6 +1,4 @@
"""
Custom template tags for report generation
"""
"""Custom template tags for report generation"""
import os
@ -19,10 +17,7 @@ register = template.Library()
@register.simple_tag()
def asset(filename):
"""
Return fully-qualified path for an upload report asset file.
"""
"""Return fully-qualified path for an upload report asset file."""
# If in debug mode, return URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
@ -38,10 +33,7 @@ def asset(filename):
@register.simple_tag()
def part_image(part):
"""
Return a fully-qualified path for a part image
"""
"""Return a fully-qualified path for a part image"""
# If in debug mode, return URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
@ -75,10 +67,7 @@ def part_image(part):
@register.simple_tag()
def company_image(company):
"""
Return a fully-qualified path for a company image
"""
"""Return a fully-qualified path for a company image"""
# If in debug mode, return the URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
@ -108,15 +97,13 @@ def company_image(company):
@register.simple_tag()
def internal_link(link, text):
"""
Make a <a></a> href which points to an InvenTree URL.
"""Make a <a></a> href which points to an InvenTree URL.
Important Note: This only works if the INVENTREE_BASE_URL parameter is set!
If the INVENTREE_BASE_URL parameter is not configured,
the text will be returned (unlinked)
"""
text = str(text)
url = InvenTree.helpers.construct_absolute_url(link)

View File

@ -36,10 +36,7 @@ class ReportTest(InvenTreeAPITestCase):
super().setUp()
def copyReportTemplate(self, filename, description):
"""
Copy the provided report template into the required media directory
"""
"""Copy the provided report template into the required media directory"""
src_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'templates',
@ -81,10 +78,7 @@ class ReportTest(InvenTreeAPITestCase):
)
def test_list_endpoint(self):
"""
Test that the LIST endpoint works for each report
"""
"""Test that the LIST endpoint works for each report"""
if not self.list_url:
return
@ -135,10 +129,7 @@ class TestReportTest(ReportTest):
return super().setUp()
def test_print(self):
"""
Printing tests for the TestReport
"""
"""Printing tests for the TestReport"""
report = self.model.objects.first()
url = reverse(self.print_url, kwargs={'pk': report.pk})
@ -177,10 +168,7 @@ class BuildReportTest(ReportTest):
return super().setUp()
def test_print(self):
"""
Printing tests for the BuildReport
"""
"""Printing tests for the BuildReport"""
report = self.model.objects.first()
url = reverse(self.print_url, kwargs={'pk': report.pk})

View File

@ -1,6 +1,4 @@
"""
This script calculates translation coverage for various languages
"""
"""This script calculates translation coverage for various languages"""
import json
import os

View File

@ -1,5 +1,4 @@
"""
The Stock module is responsible for Stock management.
"""The Stock module is responsible for Stock management.
It includes models for:

View File

@ -15,7 +15,7 @@ from .models import (StockItem, StockItemAttachment, StockItemTestResult,
class LocationResource(ModelResource):
""" Class for managing StockLocation data import/export """
"""Class for managing StockLocation data import/export"""
parent = Field(attribute='parent', widget=widgets.ForeignKeyWidget(StockLocation))
@ -42,9 +42,7 @@ class LocationResource(ModelResource):
class LocationInline(admin.TabularInline):
"""
Inline for sub-locations
"""
"""Inline for sub-locations"""
model = StockLocation
@ -66,7 +64,7 @@ class LocationAdmin(ImportExportModelAdmin):
class StockItemResource(ModelResource):
""" Class for managing StockItem data import/export """
"""Class for managing StockItem data import/export"""
# Custom managers for ForeignKey fields
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))

View File

@ -1,6 +1,4 @@
"""
JSON API for the Stock app
"""
"""JSON API for the Stock app"""
from collections import OrderedDict
from datetime import datetime, timedelta
@ -39,7 +37,7 @@ from stock.models import (StockItem, StockItemAttachment, StockItemTestResult,
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
""" API detail endpoint for Stock object
"""API detail endpoint for Stock object
get:
Return a single StockItem object
@ -89,7 +87,7 @@ class StockMetadata(generics.RetrieveUpdateAPIView):
class StockItemContextMixin:
""" Mixin class for adding StockItem object to serializer context """
"""Mixin class for adding StockItem object to serializer context"""
def get_serializer_context(self):
@ -105,17 +103,14 @@ class StockItemContextMixin:
class StockItemSerialize(StockItemContextMixin, generics.CreateAPIView):
"""
API endpoint for serializing a stock item
"""
"""API endpoint for serializing a stock item"""
queryset = StockItem.objects.none()
serializer_class = StockSerializers.SerializeStockItemSerializer
class StockItemInstall(StockItemContextMixin, generics.CreateAPIView):
"""
API endpoint for installing a particular stock item into this stock item.
"""API endpoint for installing a particular stock item into this stock item.
- stock_item.part must be in the BOM for this part
- stock_item must currently be "in stock"
@ -127,17 +122,14 @@ class StockItemInstall(StockItemContextMixin, generics.CreateAPIView):
class StockItemUninstall(StockItemContextMixin, generics.CreateAPIView):
"""
API endpoint for removing (uninstalling) items from this item
"""
"""API endpoint for removing (uninstalling) items from this item"""
queryset = StockItem.objects.none()
serializer_class = StockSerializers.UninstallStockItemSerializer
class StockAdjustView(generics.CreateAPIView):
"""
A generic class for handling stocktake actions.
"""A generic class for handling stocktake actions.
Subclasses exist for:
@ -159,41 +151,31 @@ class StockAdjustView(generics.CreateAPIView):
class StockCount(StockAdjustView):
"""
Endpoint for counting stock (performing a stocktake).
"""
"""Endpoint for counting stock (performing a stocktake)."""
serializer_class = StockSerializers.StockCountSerializer
class StockAdd(StockAdjustView):
"""
Endpoint for adding a quantity of stock to an existing StockItem
"""
"""Endpoint for adding a quantity of stock to an existing StockItem"""
serializer_class = StockSerializers.StockAddSerializer
class StockRemove(StockAdjustView):
"""
Endpoint for removing a quantity of stock from an existing StockItem.
"""
"""Endpoint for removing a quantity of stock from an existing StockItem."""
serializer_class = StockSerializers.StockRemoveSerializer
class StockTransfer(StockAdjustView):
"""
API endpoint for performing stock movements
"""
"""API endpoint for performing stock movements"""
serializer_class = StockSerializers.StockTransferSerializer
class StockAssign(generics.CreateAPIView):
"""
API endpoint for assigning stock to a particular customer
"""
"""API endpoint for assigning stock to a particular customer"""
queryset = StockItem.objects.all()
serializer_class = StockSerializers.StockAssignmentSerializer
@ -208,9 +190,7 @@ class StockAssign(generics.CreateAPIView):
class StockMerge(generics.CreateAPIView):
"""
API endpoint for merging multiple stock items
"""
"""API endpoint for merging multiple stock items"""
queryset = StockItem.objects.none()
serializer_class = StockSerializers.StockMergeSerializer
@ -222,8 +202,7 @@ class StockMerge(generics.CreateAPIView):
class StockLocationList(generics.ListCreateAPIView):
"""
API endpoint for list view of StockLocation objects:
"""API endpoint for list view of StockLocation objects:
- GET: Return list of StockLocation objects
- POST: Create a new StockLocation
@ -233,11 +212,9 @@ class StockLocationList(generics.ListCreateAPIView):
serializer_class = StockSerializers.LocationSerializer
def filter_queryset(self, queryset):
"""
Custom filtering:
"""Custom filtering:
- Allow filtering by "null" parent to retrieve top-level stock locations
"""
queryset = super().filter_queryset(queryset)
params = self.request.query_params
@ -319,10 +296,7 @@ class StockLocationList(generics.ListCreateAPIView):
class StockLocationTree(generics.ListAPIView):
"""
API endpoint for accessing a list of StockLocation objects,
ready for rendering as a tree
"""
"""API endpoint for accessing a list of StockLocation objects, ready for rendering as a tree"""
queryset = StockLocation.objects.all()
serializer_class = StockSerializers.LocationTreeSerializer
@ -337,9 +311,7 @@ class StockLocationTree(generics.ListAPIView):
class StockFilter(rest_filters.FilterSet):
"""
FilterSet for StockItem LIST API
"""
"""FilterSet for StockItem LIST API"""
# Part name filters
name = rest_filters.CharFilter(label='Part name (case insensitive)', field_name='part__name', lookup_expr='iexact')
@ -372,12 +344,10 @@ class StockFilter(rest_filters.FilterSet):
available = rest_filters.BooleanFilter(label='Available', method='filter_available')
def filter_available(self, queryset, name, value):
"""
Filter by whether the StockItem is "available" or not.
"""Filter by whether the StockItem is "available" or not.
Here, "available" means that the allocated quantity is less than the total quantity
"""
if str2bool(value):
# The 'quantity' field is greater than the calculated 'allocated' field
queryset = queryset.filter(Q(quantity__gt=F('allocated')))
@ -401,10 +371,7 @@ class StockFilter(rest_filters.FilterSet):
serialized = rest_filters.BooleanFilter(label='Has serial number', method='filter_serialized')
def filter_serialized(self, queryset, name, value):
"""
Filter by whether the StockItem has a serial number (or not)
"""
"""Filter by whether the StockItem has a serial number (or not)"""
q = Q(serial=None) | Q(serial='')
if str2bool(value):
@ -417,10 +384,7 @@ class StockFilter(rest_filters.FilterSet):
has_batch = rest_filters.BooleanFilter(label='Has batch code', method='filter_has_batch')
def filter_has_batch(self, queryset, name, value):
"""
Filter by whether the StockItem has a batch code (or not)
"""
"""Filter by whether the StockItem has a batch code (or not)"""
q = Q(batch=None) | Q(batch='')
if str2bool(value):
@ -433,12 +397,10 @@ class StockFilter(rest_filters.FilterSet):
tracked = rest_filters.BooleanFilter(label='Tracked', method='filter_tracked')
def filter_tracked(self, queryset, name, value):
"""
Filter by whether this stock item is *tracked*, meaning either:
"""Filter by whether this stock item is *tracked*, meaning either:
- It has a serial number
- It has a batch code
"""
q_batch = Q(batch=None) | Q(batch='')
q_serial = Q(serial=None) | Q(serial='')
@ -452,10 +414,7 @@ class StockFilter(rest_filters.FilterSet):
installed = rest_filters.BooleanFilter(label='Installed in other stock item', method='filter_installed')
def filter_installed(self, queryset, name, value):
"""
Filter stock items by "belongs_to" field being empty
"""
"""Filter stock items by "belongs_to" field being empty"""
if str2bool(value):
queryset = queryset.exclude(belongs_to=None)
else:
@ -502,7 +461,7 @@ class StockFilter(rest_filters.FilterSet):
class StockList(APIDownloadMixin, generics.ListCreateAPIView):
""" API endpoint for list view of Stock objects
"""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
@ -520,15 +479,13 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView):
return ctx
def create(self, request, *args, **kwargs):
"""
Create a new StockItem object via the API.
"""Create a new StockItem object via the API.
We override the default 'create' implementation.
If a location is *not* specified, but the linked *part* has a default location,
we can pre-fill the location automatically.
"""
user = request.user
# Copy the request data, to side-step "mutability" issues
@ -643,8 +600,8 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView):
return Response(response_data, status=status.HTTP_201_CREATED, headers=self.get_success_headers(serializer.data))
def download_queryset(self, queryset, export_format):
"""
Download this queryset as a file.
"""Download this queryset as a file.
Uses the APIDownloadMixin mixin class
"""
dataset = StockItemResource().export(queryset=queryset)
@ -659,13 +616,11 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView):
return DownloadFile(filedata, filename)
def list(self, request, *args, **kwargs):
"""
Override the 'list' method, as the StockLocation objects
"""Override the 'list' method, as the StockLocation objects
are very expensive to serialize.
So, we fetch and serialize the required StockLocation objects only as required.
"""
queryset = self.filter_queryset(self.get_queryset())
params = request.query_params
@ -775,9 +730,7 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView):
return queryset
def filter_queryset(self, queryset):
"""
Custom filtering for the StockItem queryset
"""
"""Custom filtering for the StockItem queryset"""
params = self.request.query_params
@ -1090,9 +1043,7 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView):
class StockAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
"""
API endpoint for listing (and creating) a StockItemAttachment (file upload)
"""
"""API endpoint for listing (and creating) a StockItemAttachment (file upload)"""
queryset = StockItemAttachment.objects.all()
serializer_class = StockSerializers.StockItemAttachmentSerializer
@ -1109,27 +1060,21 @@ class StockAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
class StockAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
"""
Detail endpoint for StockItemAttachment
"""
"""Detail endpoint for StockItemAttachment"""
queryset = StockItemAttachment.objects.all()
serializer_class = StockSerializers.StockItemAttachmentSerializer
class StockItemTestResultDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Detail endpoint for StockItemTestResult
"""
"""Detail endpoint for StockItemTestResult"""
queryset = StockItemTestResult.objects.all()
serializer_class = StockSerializers.StockItemTestResultSerializer
class StockItemTestResultList(generics.ListCreateAPIView):
"""
API endpoint for listing (and creating) a StockItemTestResult object.
"""
"""API endpoint for listing (and creating) a StockItemTestResult object."""
queryset = StockItemTestResult.objects.all()
serializer_class = StockSerializers.StockItemTestResultSerializer
@ -1205,8 +1150,7 @@ class StockItemTestResultList(generics.ListCreateAPIView):
return self.serializer_class(*args, **kwargs)
def perform_create(self, serializer):
"""
Create a new test result object.
"""Create a new test result object.
Also, check if an attachment was uploaded alongside the test result,
and save it to the database if it were.
@ -1219,16 +1163,14 @@ class StockItemTestResultList(generics.ListCreateAPIView):
class StockTrackingDetail(generics.RetrieveAPIView):
"""
Detail API endpoint for StockItemTracking model
"""
"""Detail API endpoint for StockItemTracking model"""
queryset = StockItemTracking.objects.all()
serializer_class = StockSerializers.StockTrackingSerializer
class StockTrackingList(generics.ListAPIView):
""" API endpoint for list view of StockItemTracking objects.
"""API endpoint for list view of StockItemTracking objects.
StockItemTracking objects are read-only
(they are created by internal model functionality)
@ -1320,7 +1262,7 @@ class StockTrackingList(generics.ListAPIView):
return Response(data)
def create(self, request, *args, **kwargs):
""" Create a new StockItemTracking object
"""Create a new StockItemTracking object
Here we override the default 'create' implementation,
to save the user information associated with the request object.
@ -1374,7 +1316,7 @@ class LocationMetadata(generics.RetrieveUpdateAPIView):
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail view of StockLocation object
"""API endpoint for detail view of StockLocation object
- GET: Return a single StockLocation object
- PATCH: Update a StockLocation object

View File

@ -1,6 +1,4 @@
"""
Django Forms for interacting with Stock app
"""
"""Django Forms for interacting with Stock app"""
from InvenTree.forms import HelperForm
@ -8,8 +6,7 @@ from .models import StockItem, StockItemTracking
class ReturnStockItemForm(HelperForm):
"""
Form for manually returning a StockItem into stock
"""Form for manually returning a StockItem into stock
TODO: This could be a simple API driven form!
"""
@ -22,8 +19,7 @@ class ReturnStockItemForm(HelperForm):
class ConvertStockItemForm(HelperForm):
"""
Form for converting a StockItem to a variant of its current part.
"""Form for converting a StockItem to a variant of its current part.
TODO: Migrate this form to the modern API forms interface
"""
@ -36,8 +32,7 @@ class ConvertStockItemForm(HelperForm):
class TrackingEntryForm(HelperForm):
"""
Form for creating / editing a StockItemTracking object.
"""Form for creating / editing a StockItemTracking object.
Note: 2021-05-11 - This form is not currently used - should delete?
"""

View File

@ -1,6 +1,4 @@
"""
Stock database model definitions
"""
"""Stock database model definitions"""
import os
from datetime import datetime, timedelta
@ -40,17 +38,16 @@ from users.models import Owner
class StockLocation(MetadataMixin, InvenTreeTree):
""" Organization tree for StockItem objects
"""Organization tree for StockItem objects.
A "StockLocation" can be considered a warehouse, or storage location
Stock locations can be heirarchical as required
"""
def delete(self, *args, **kwargs):
"""
Custom model deletion routine, which updates any child locations or items.
"""Custom model deletion routine, which updates any child locations or items.
This must be handled within a transaction.atomic(), otherwise the tree structure is damaged
"""
with transaction.atomic():
parent = self.parent
@ -84,12 +81,10 @@ class StockLocation(MetadataMixin, InvenTreeTree):
related_name='stock_locations')
def get_location_owner(self):
"""
Get the closest "owner" for this location.
"""Get the closest "owner" for this location.
Start at this location, and traverse "up" the location tree until we find an owner
"""
for loc in self.get_ancestors(include_self=True, ascending=True):
if loc.owner is not None:
return loc.owner
@ -97,10 +92,7 @@ class StockLocation(MetadataMixin, InvenTreeTree):
return None
def check_ownership(self, user):
"""
Check if the user "owns" (is one of the owners of) the location.
"""
"""Check if the user "owns" (is one of the owners of) the location."""
# Superuser accounts automatically "own" everything
if user.is_superuser:
return True
@ -124,8 +116,7 @@ class StockLocation(MetadataMixin, InvenTreeTree):
return reverse('stock-location-detail', kwargs={'pk': self.id})
def format_barcode(self, **kwargs):
""" Return a JSON string for formatting a barcode for this StockLocation object """
"""Return a JSON string for formatting a barcode for this StockLocation object"""
return InvenTree.helpers.MakeBarcode(
'stocklocation',
self.pk,
@ -138,18 +129,15 @@ class StockLocation(MetadataMixin, InvenTreeTree):
@property
def barcode(self):
"""
Brief payload data (e.g. for labels)
"""
"""Brief payload data (e.g. for labels)"""
return self.format_barcode(brief=True)
def get_stock_items(self, cascade=True):
""" Return a queryset for all stock items under this category.
"""Return a queryset for all stock items under this category.
Args:
cascade: If True, also look under sublocations (default = True)
"""
if cascade:
query = StockItem.objects.filter(location__in=self.getUniqueChildren(include_self=True))
else:
@ -158,13 +146,11 @@ class StockLocation(MetadataMixin, InvenTreeTree):
return query
def stock_item_count(self, cascade=True):
""" Return the number of StockItem objects which live in or under this category
"""
"""Return the number of StockItem objects which live in or under this category"""
return self.get_stock_items(cascade).count()
def has_items(self, cascade=True):
""" Return True if there are StockItems existing in this category.
"""Return True if there are StockItems existing in this category.
Args:
cascade: If True, also search an sublocations (default = True)
@ -173,15 +159,14 @@ class StockLocation(MetadataMixin, InvenTreeTree):
@property
def item_count(self):
""" Simply returns the number of stock items in this location.
Required for tree view serializer.
"""
"""Simply returns the number of stock items in this location.
Required for tree view serializer."""
return self.stock_item_count()
class StockItemManager(TreeManager):
"""
Custom database manager for the StockItem class.
"""Custom database manager for the StockItem class.
StockItem querysets will automatically prefetch related fields.
"""
@ -205,13 +190,11 @@ class StockItemManager(TreeManager):
def generate_batch_code():
"""
Generate a default 'batch code' for a new StockItem.
"""Generate a default 'batch code' for a new StockItem.
This uses the value of the 'STOCK_BATCH_CODE_TEMPLATE' setting (if configured),
which can be passed through a simple template.
"""
batch_template = common.models.InvenTreeSetting.get_setting('STOCK_BATCH_CODE_TEMPLATE', '')
now = datetime.now()
@ -231,8 +214,7 @@ def generate_batch_code():
class StockItem(MetadataMixin, MPTTModel):
"""
A StockItem object represents a quantity of physical instances of a part.
"""A StockItem object represents a quantity of physical instances of a part.
Attributes:
parent: Link to another StockItem from which this StockItem was created
@ -290,11 +272,10 @@ class StockItem(MetadataMixin, MPTTModel):
EXPIRED_FILTER = IN_STOCK_FILTER & ~Q(expiry_date=None) & Q(expiry_date__lt=datetime.now().date())
def update_serial_number(self):
"""
Update the 'serial_int' field, to be an integer representation of the serial number.
"""Update the 'serial_int' field, to be an integer representation of the serial number.
This is used for efficient numerical sorting
"""
serial = getattr(self, 'serial', '')
# Default value if we cannot convert to an integer
@ -309,8 +290,7 @@ class StockItem(MetadataMixin, MPTTModel):
self.serial_int = serial_int
def get_next_serialized_item(self, include_variants=True, reverse=False):
"""
Get the "next" serial number for the part this stock item references.
"""Get the "next" serial number for the part this stock item references.
e.g. if this stock item has a serial number 100, we may return the stock item with serial number 101
@ -322,9 +302,7 @@ class StockItem(MetadataMixin, MPTTModel):
Returns:
A StockItem object matching the requirements, or None
"""
if not self.serialized:
return None
@ -358,13 +336,11 @@ class StockItem(MetadataMixin, MPTTModel):
return None
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
- Adds a transaction note when the item is first created.
"""
self.validate_unique()
self.clean()
@ -433,16 +409,15 @@ class StockItem(MetadataMixin, MPTTModel):
@property
def serialized(self):
""" Return True if this StockItem is serialized """
"""Return True if this StockItem is serialized"""
return self.serial is not None and len(str(self.serial).strip()) > 0 and self.quantity == 1
def validate_unique(self, exclude=None):
"""
Test that this StockItem is "unique".
"""Test that this StockItem is "unique".
If the StockItem is serialized, the same serial number.
cannot exist for the same part (or part tree).
"""
super(StockItem, self).validate_unique(exclude)
# If the serial number is set, make sure it is not a duplicate
@ -459,7 +434,7 @@ class StockItem(MetadataMixin, MPTTModel):
raise ValidationError({"serial": _("StockItem with this serial number already exists")})
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:
@ -467,7 +442,6 @@ class StockItem(MetadataMixin, MPTTModel):
- The 'part' does not belong to itself
- Quantity must be 1 if the StockItem has a serial number
"""
super().clean()
# Strip serial number field
@ -563,7 +537,7 @@ class StockItem(MetadataMixin, MPTTModel):
return self.part.full_name
def format_barcode(self, **kwargs):
""" Return a JSON string for formatting a barcode for this StockItem.
"""Return a JSON string for formatting a barcode for this StockItem.
Can be used to perform lookup of a stockitem using barcode
Contains the following data:
@ -572,7 +546,6 @@ class StockItem(MetadataMixin, MPTTModel):
Voltagile data (e.g. stock quantity) should be looked up using the InvenTree API (as it may change)
"""
return InvenTree.helpers.MakeBarcode(
"stockitem",
self.id,
@ -586,9 +559,7 @@ class StockItem(MetadataMixin, MPTTModel):
@property
def barcode(self):
"""
Brief payload data (e.g. for labels)
"""
"""Brief payload data (e.g. for labels)"""
return self.format_barcode(brief=True)
uid = models.CharField(blank=True, max_length=128, help_text=("Unique identifier field"))
@ -753,11 +724,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def convert_to_variant(self, variant, user, notes=None):
"""
Convert this StockItem instance to a "variant",
i.e. change the "part" reference field
"""
"""Convert this StockItem instance to a "variant", i.e. change the "part" reference field"""
if not variant:
# Ignore null values
return
@ -779,14 +746,12 @@ class StockItem(MetadataMixin, MPTTModel):
)
def get_item_owner(self):
"""
Return the closest "owner" for this StockItem.
"""Return the closest "owner" for this StockItem.
- If the item has an owner set, return that
- If the item is "in stock", check the StockLocation
- Otherwise, return None
"""
if self.owner is not None:
return self.owner
@ -799,10 +764,7 @@ class StockItem(MetadataMixin, MPTTModel):
return None
def check_ownership(self, user):
"""
Check if the user "owns" (or is one of the owners of) the item
"""
"""Check if the user "owns" (or is one of the owners of) the item"""
# Superuser accounts automatically "own" everything
if user.is_superuser:
return True
@ -821,8 +783,7 @@ class StockItem(MetadataMixin, MPTTModel):
return user in owner.get_related_owners(include_group=True)
def is_stale(self):
"""
Returns True if this Stock item is "stale".
"""Returns True if this Stock item is "stale".
To be "stale", the following conditions must be met:
@ -830,7 +791,6 @@ class StockItem(MetadataMixin, MPTTModel):
- Expiry date will "expire" within the configured stale date
- The StockItem is otherwise "in stock"
"""
if self.expiry_date is None:
return False
@ -849,8 +809,7 @@ class StockItem(MetadataMixin, MPTTModel):
return self.expiry_date < expiry_date
def is_expired(self):
"""
Returns True if this StockItem is "expired".
"""Returns True if this StockItem is "expired".
To be "expired", the following conditions must be met:
@ -858,7 +817,6 @@ class StockItem(MetadataMixin, MPTTModel):
- Expiry date is "in the past"
- The StockItem is otherwise "in stock"
"""
if self.expiry_date is None:
return False
@ -870,13 +828,11 @@ class StockItem(MetadataMixin, MPTTModel):
return self.expiry_date < today
def clearAllocations(self):
"""
Clear all order allocations for this StockItem:
"""Clear all order allocations for this StockItem:
- SalesOrder allocations
- Build allocations
"""
# Delete outstanding SalesOrder allocations
self.sales_order_allocations.all().delete()
@ -884,8 +840,7 @@ class StockItem(MetadataMixin, MPTTModel):
self.allocations.all().delete()
def allocateToCustomer(self, customer, quantity=None, order=None, user=None, notes=None):
"""
Allocate a StockItem to a customer.
"""Allocate a StockItem to a customer.
This action can be called by the following processes:
- Completion of a SalesOrder
@ -898,7 +853,6 @@ class StockItem(MetadataMixin, MPTTModel):
user: User that performed the action
notes: Notes field
"""
if quantity is None:
quantity = self.quantity
@ -936,10 +890,7 @@ class StockItem(MetadataMixin, MPTTModel):
return item
def returnFromCustomer(self, location, user=None, **kwargs):
"""
Return stock item from customer, back into the specified location.
"""
"""Return stock item from customer, back into the specified location."""
notes = kwargs.get('notes', '')
tracking_info = {}
@ -972,10 +923,7 @@ class StockItem(MetadataMixin, MPTTModel):
infinite = models.BooleanField(default=False)
def is_allocated(self):
"""
Return True if this StockItem is allocated to a SalesOrder or a Build
"""
"""Return True if this StockItem is allocated to a SalesOrder or a Build"""
# TODO - For now this only checks if the StockItem is allocated to a SalesOrder
# TODO - In future, once the "build" is working better, check this too
@ -988,10 +936,7 @@ class StockItem(MetadataMixin, MPTTModel):
return False
def build_allocation_count(self):
"""
Return the total quantity allocated to builds
"""
"""Return the total quantity allocated to builds"""
query = self.allocations.aggregate(q=Coalesce(Sum('quantity'), Decimal(0)))
total = query['q']
@ -1002,10 +947,7 @@ class StockItem(MetadataMixin, MPTTModel):
return total
def sales_order_allocation_count(self):
"""
Return the total quantity allocated to SalesOrders
"""
"""Return the total quantity allocated to SalesOrders"""
query = self.sales_order_allocations.aggregate(q=Coalesce(Sum('quantity'), Decimal(0)))
total = query['q']
@ -1016,31 +958,24 @@ class StockItem(MetadataMixin, MPTTModel):
return total
def allocation_count(self):
"""
Return the total quantity allocated to builds or orders
"""
"""Return the total quantity allocated to builds or orders"""
bo = self.build_allocation_count()
so = self.sales_order_allocation_count()
return bo + so
def unallocated_quantity(self):
"""
Return the quantity of this StockItem which is *not* allocated
"""
"""Return the quantity of this StockItem which is *not* allocated"""
return max(self.quantity - self.allocation_count(), 0)
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
- Is installed inside another StockItem
- It has been assigned to a SalesOrder
- It has been assigned to a BuildOrder
"""
if self.installed_item_count() > 0:
return False
@ -1050,15 +985,13 @@ class StockItem(MetadataMixin, MPTTModel):
return True
def get_installed_items(self, cascade=False):
"""
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!
"""
installed = set()
items = StockItem.objects.filter(belongs_to=self)
@ -1085,16 +1018,12 @@ class StockItem(MetadataMixin, MPTTModel):
return installed
def installed_item_count(self):
"""
Return the number of stock items installed inside this one.
"""
"""Return the number of stock items installed inside this one."""
return self.installed_parts.count()
@transaction.atomic
def installStockItem(self, other_item, quantity, user, notes):
"""
Install another stock item into this stock item.
"""Install another stock item into this stock item.
Args
other_item: The stock item to install into this stock item
@ -1102,7 +1031,6 @@ class StockItem(MetadataMixin, MPTTModel):
user: The user performing the operation
notes: Any notes associated with the operation
"""
# Cannot be already installed in another stock item!
if self.belongs_to is not None:
return False
@ -1139,15 +1067,13 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def uninstall_into_location(self, location, user, notes):
"""
Uninstall this stock item from another item, into a location.
"""Uninstall this stock item from another item, into a location.
Args:
location: The stock location where the item will be moved
user: The user performing the operation
notes: Any notes associated with the operation
"""
# If the stock item is not installed in anything, ignore
if self.belongs_to is None:
return False
@ -1184,24 +1110,22 @@ class StockItem(MetadataMixin, MPTTModel):
@property
def children(self):
""" Return a list of the child items which have been split from this stock item """
"""Return a list of the child items which have been split from this stock item"""
return self.get_descendants(include_self=False)
@property
def child_count(self):
""" Return the number of 'child' items associated with this StockItem.
A child item is one which has been split from this one.
"""
"""Return the number of 'child' items associated with this StockItem.
A child item is one which has been split from this one."""
return self.children.count()
@property
def in_stock(self):
"""
Returns True if this item is in stock.
"""Returns True if this item is in stock.
See also: IN_STOCK_FILTER
"""
query = StockItem.objects.filter(pk=self.pk)
query = query.filter(StockItem.IN_STOCK_FILTER)
@ -1210,14 +1134,12 @@ class StockItem(MetadataMixin, MPTTModel):
@property
def can_adjust_location(self):
"""
Returns True if the stock location can be "adjusted" for this part
"""Returns True if the stock location can be "adjusted" for this part
Cannot be adjusted if:
- Has been delivered to a customer
- Has been installed inside another StockItem
"""
if self.customer is not None:
return False
@ -1238,8 +1160,7 @@ class StockItem(MetadataMixin, MPTTModel):
return self.tracking_info_count > 0
def add_tracking_entry(self, entry_type, user, deltas=None, notes='', **kwargs):
"""
Add a history tracking entry for this StockItem
"""Add a history tracking entry for this StockItem
Args:
entry_type - Integer code describing the "type" of historical action (see StockHistoryCode)
@ -1276,7 +1197,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def serializeStock(self, quantity, serials, user, notes='', location=None):
""" Split this stock item into unique serial numbers.
"""Split this stock item into unique serial numbers.
- Quantity can be less than or equal to the quantity of the stock item
- Number of serial numbers must match the quantity
@ -1289,7 +1210,6 @@ class StockItem(MetadataMixin, MPTTModel):
notes: Optional notes for tracking
location: If specified, serialized items will be placed in the given location
"""
# Cannot serialize stock that is already serialized!
if self.serialized:
return
@ -1360,8 +1280,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def copyHistoryFrom(self, other):
""" Copy stock history from another StockItem """
"""Copy stock history from another StockItem"""
for item in other.tracking_info.all():
item.item = self
@ -1370,8 +1289,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def copyTestResultsFrom(self, other, filters={}):
""" Copy all test results from another StockItem """
"""Copy all test results from another StockItem"""
for result in other.test_results.all().filter(**filters):
# Create a copy of the test result by nulling-out the pk
@ -1380,10 +1298,7 @@ class StockItem(MetadataMixin, MPTTModel):
result.save()
def can_merge(self, other=None, raise_error=False, **kwargs):
"""
Check if this stock item can be merged into another stock item
"""
"""Check if this stock item can be merged into another stock item"""
allow_mismatched_suppliers = kwargs.get('allow_mismatched_suppliers', False)
allow_mismatched_status = kwargs.get('allow_mismatched_status', False)
@ -1437,8 +1352,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def merge_stock_items(self, other_items, raise_error=False, **kwargs):
"""
Merge another stock item into this one; the two become one!
"""Merge another stock item into this one; the two become one!
*This* stock item subsumes the other, which is essentially deleted:
@ -1446,7 +1360,6 @@ class StockItem(MetadataMixin, MPTTModel):
- Tracking history for the *other* item is deleted
- Any allocations (build order, sales order) are moved to this StockItem
"""
if len(other_items) == 0:
return
@ -1499,7 +1412,8 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def splitStock(self, quantity, location, user, **kwargs):
""" Split this stock item into two items, in the same location.
"""Split this stock item into two items, in the same location.
Stock tracking notes for this StockItem will be duplicated,
and added to the new StockItem.
@ -1511,7 +1425,6 @@ class StockItem(MetadataMixin, MPTTModel):
The provided quantity will be subtracted from this item and given to the new one.
The new item will have a different StockItem ID, while this will remain the same.
"""
notes = kwargs.get('notes', '')
code = kwargs.get('code', StockHistoryCode.SPLIT_FROM_PARENT)
@ -1576,7 +1489,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def move(self, location, notes, user, **kwargs):
""" Move part to a new location.
"""Move part to a new location.
If less than the available quantity is to be moved,
a new StockItem is created, with the defined quantity,
@ -1590,7 +1503,6 @@ class StockItem(MetadataMixin, MPTTModel):
kwargs:
quantity: If provided, override the quantity (default = total stock quantity)
"""
try:
quantity = Decimal(kwargs.get('quantity', self.quantity))
except InvalidOperation:
@ -1636,7 +1548,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def updateQuantity(self, quantity):
""" Update stock quantity for this item.
"""Update stock quantity for this item.
If the quantity has reached zero, this StockItem will be deleted.
@ -1644,7 +1556,6 @@ class StockItem(MetadataMixin, MPTTModel):
- True if the quantity was saved
- False if the StockItem was deleted
"""
# Do not adjust quantity of a serialized part
if self.serialized:
return
@ -1669,11 +1580,10 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def stocktake(self, count, user, notes=''):
""" Perform item stocktake.
"""Perform item stocktake.
When the quantity of an item is counted,
record the date of stocktake
"""
try:
count = Decimal(count)
except InvalidOperation:
@ -1700,11 +1610,10 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def add_stock(self, quantity, user, notes=''):
""" Add items to stock
"""Add items to stock
This function can be called by initiating a ProjectRun,
or by manually adding the items to the stock location
"""
# Cannot add items to a serialized part
if self.serialized:
return False
@ -1734,10 +1643,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def take_stock(self, quantity, user, notes='', code=StockHistoryCode.STOCK_REMOVE):
"""
Remove items from stock
"""
"""Remove items from stock"""
# Cannot remove items from a serialized part
if self.serialized:
return False
@ -1787,13 +1693,7 @@ class StockItem(MetadataMixin, MPTTModel):
@transaction.atomic
def clear_test_results(self, **kwargs):
"""
Remove all test results
kwargs:
TODO
"""
"""Remove all test results"""
# All test results
results = self.test_results.all()
@ -1802,15 +1702,13 @@ class StockItem(MetadataMixin, MPTTModel):
results.delete()
def getTestResults(self, test=None, result=None, user=None):
"""
Return all test results associated with this StockItem.
"""Return all test results associated with this StockItem.
Optionally can filter results by:
- Test name
- Test result
- User
"""
results = self.test_results
if test:
@ -1828,15 +1726,13 @@ class StockItem(MetadataMixin, MPTTModel):
return results
def testResultMap(self, **kwargs):
"""
Return a map of test-results using the test name as the key.
"""Return a map of test-results using the test name as the key.
Where multiple test results exist for a given name,
the *most recent* test is used.
This map is useful for rendering to a template (e.g. a test report),
as all named tests are accessible.
"""
# Do we wish to include test results from installed items?
include_installed = kwargs.pop('include_installed', False)
@ -1867,15 +1763,11 @@ class StockItem(MetadataMixin, MPTTModel):
return result_map
def testResultList(self, **kwargs):
"""
Return a list of test-result objects for this StockItem
"""
"""Return a list of test-result objects for this StockItem"""
return self.testResultMap(**kwargs).values()
def requiredTestStatus(self):
"""
Return the status of the tests required for this StockItem.
"""Return the status of the tests required for this StockItem.
return:
A dict containing the following items:
@ -1883,7 +1775,6 @@ class StockItem(MetadataMixin, MPTTModel):
- passed: Number of tests that have passed
- failed: Number of tests that have failed
"""
# All the tests required by the part object
required = self.part.getRequiredTests()
@ -1912,31 +1803,21 @@ class StockItem(MetadataMixin, MPTTModel):
@property
def required_test_count(self):
"""
Return the number of 'required tests' for this StockItem
"""
"""Return the number of 'required tests' for this StockItem"""
return self.part.getRequiredTests().count()
def hasRequiredTests(self):
"""
Return True if there are any 'required tests' associated with this StockItem
"""
"""Return True if there are any 'required tests' associated with this StockItem"""
return self.part.getRequiredTests().count() > 0
def passedAllRequiredTests(self):
"""
Returns True if this StockItem has passed all required tests
"""
"""Returns True if this StockItem has passed all required tests"""
status = self.requiredTestStatus()
return status['passed'] >= status['total']
def available_test_reports(self):
"""
Return a list of TestReport objects which match this StockItem.
"""
"""Return a list of TestReport objects which match this StockItem."""
reports = []
item_query = StockItem.objects.filter(pk=self.pk)
@ -1955,17 +1836,11 @@ class StockItem(MetadataMixin, MPTTModel):
@property
def has_test_reports(self):
"""
Return True if there are test reports available for this stock item
"""
"""Return True if there are test reports available for this stock item"""
return len(self.available_test_reports()) > 0
def available_labels(self):
"""
Return a list of Label objects which match this StockItem
"""
"""Return a list of Label objects which match this StockItem"""
labels = []
item_query = StockItem.objects.filter(pk=self.pk)
@ -1984,22 +1859,17 @@ class StockItem(MetadataMixin, MPTTModel):
@property
def has_labels(self):
"""
Return True if there are any label templates available for this stock item
"""
"""Return True if there are any label templates available for this stock item"""
return len(self.available_labels()) > 0
@receiver(pre_delete, sender=StockItem, dispatch_uid='stock_item_pre_delete_log')
def before_delete_stock_item(sender, instance, using, **kwargs):
"""
Receives pre_delete signal from StockItem object.
"""Receives pre_delete signal from StockItem object.
Before a StockItem is deleted, ensure that each child object is updated,
to point to the new parent item.
"""
# Update each StockItem parent field
for child in instance.children.all():
child.parent = instance.parent
@ -2008,9 +1878,7 @@ def before_delete_stock_item(sender, instance, using, **kwargs):
@receiver(post_delete, sender=StockItem, dispatch_uid='stock_item_post_delete_log')
def after_delete_stock_item(sender, instance: StockItem, **kwargs):
"""
Function to be executed after a StockItem object is deleted
"""
"""Function to be executed after a StockItem object is deleted"""
from part import tasks as part_tasks
if not InvenTree.ready.isImportingData():
@ -2020,9 +1888,7 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs):
@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')
def after_save_stock_item(sender, instance: StockItem, created, **kwargs):
"""
Hook function to be executed after StockItem object is saved/updated
"""
"""Hook function to be executed after StockItem object is saved/updated"""
from part import tasks as part_tasks
if not InvenTree.ready.isImportingData():
@ -2050,8 +1916,7 @@ class StockItemAttachment(InvenTreeAttachment):
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
The legacy StockTrackingItem model contained very litle information about the "history" of the item.
@ -2114,8 +1979,7 @@ def rename_stock_item_test_result_attachment(instance, filename):
class StockItemTestResult(models.Model):
"""
A StockItemTestResult records results of custom tests against individual StockItem objects.
"""A StockItemTestResult records results of custom tests against individual StockItem objects.
This is useful for tracking unit acceptance tests, and particularly useful when integrated
with automated testing setups.

View File

@ -1,6 +1,4 @@
"""
JSON serializers for Stock app
"""
"""JSON serializers for Stock app"""
from datetime import datetime, timedelta
from decimal import Decimal
@ -29,9 +27,7 @@ from .models import (StockItem, StockItemAttachment, StockItemTestResult,
class LocationBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
"""
Provides a brief serializer for a StockLocation object
"""
"""Provides a brief serializer for a StockLocation object"""
class Meta:
model = StockLocation
@ -43,7 +39,7 @@ class LocationBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
""" Brief serializers for a StockItem """
"""Brief serializers for a StockItem"""
location_name = serializers.CharField(source='location', read_only=True)
part_name = serializers.CharField(source='part.full_name', read_only=True)
@ -71,7 +67,7 @@ class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
""" Serializer for a StockItem:
"""Serializer for a StockItem:
- Includes serialization for the linked part
- Includes serialization for the item location
@ -88,11 +84,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
@staticmethod
def annotate_queryset(queryset):
"""
Add some extra annotations to the queryset,
performing database queries as efficiently as possible.
"""
"""Add some extra annotations to the queryset, performing database queries as efficiently as possible."""
# Annotate the queryset with the total allocated to sales orders
queryset = queryset.annotate(
allocated=Coalesce(
@ -257,8 +249,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
class SerializeStockItemSerializer(serializers.Serializer):
"""
A DRF serializer for "serializing" a StockItem.
"""A DRF serializer for "serializing" a StockItem.
(Sorry for the confusing naming...)
@ -284,9 +275,7 @@ class SerializeStockItemSerializer(serializers.Serializer):
)
def validate_quantity(self, quantity):
"""
Validate that the quantity value is correct
"""
"""Validate that the quantity value is correct"""
item = self.context['item']
@ -323,9 +312,7 @@ class SerializeStockItemSerializer(serializers.Serializer):
)
def validate(self, data):
"""
Check that the supplied serial numbers are valid
"""
"""Check that the supplied serial numbers are valid"""
data = super().validate(data)
@ -381,9 +368,7 @@ class SerializeStockItemSerializer(serializers.Serializer):
class InstallStockItemSerializer(serializers.Serializer):
"""
Serializer for installing a stock item into a given part
"""
"""Serializer for installing a stock item into a given part"""
stock_item = serializers.PrimaryKeyRelatedField(
queryset=StockItem.objects.all(),
@ -401,9 +386,7 @@ class InstallStockItemSerializer(serializers.Serializer):
)
def validate_stock_item(self, stock_item):
"""
Validate the selected stock item
"""
"""Validate the selected stock item"""
if not stock_item.in_stock:
# StockItem must be in stock to be "installed"
@ -419,7 +402,7 @@ class InstallStockItemSerializer(serializers.Serializer):
return stock_item
def save(self):
""" Install the selected stock item into this one """
"""Install the selected stock item into this one"""
data = self.validated_data
@ -438,9 +421,7 @@ class InstallStockItemSerializer(serializers.Serializer):
class UninstallStockItemSerializer(serializers.Serializer):
"""
API serializers for uninstalling an installed item from a stock item
"""
"""API serializers for uninstalling an installed item from a stock item"""
class Meta:
fields = [
@ -480,9 +461,7 @@ class UninstallStockItemSerializer(serializers.Serializer):
class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
"""
Serializer for a simple tree view
"""
"""Serializer for a simple tree view"""
class Meta:
model = StockLocation
@ -494,8 +473,7 @@ class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
""" Detailed information about a stock location
"""
"""Detailed information about a stock location"""
url = serializers.CharField(source='get_absolute_url', read_only=True)
@ -519,7 +497,7 @@ class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer):
""" Serializer for StockItemAttachment model """
"""Serializer for StockItemAttachment model"""
def __init__(self, *args, **kwargs):
user_detail = kwargs.pop('user_detail', False)
@ -556,7 +534,7 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer
class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer):
""" Serializer for the StockItemTestResult model """
"""Serializer for the StockItemTestResult model"""
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
@ -597,7 +575,7 @@ class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializ
class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
""" Serializer for StockItemTracking model """
"""Serializer for StockItemTracking model"""
def __init__(self, *args, **kwargs):
@ -644,8 +622,7 @@ class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
class StockAssignmentItemSerializer(serializers.Serializer):
"""
Serializer for a single StockItem with in StockAssignment request.
"""Serializer for a single StockItem with in StockAssignment request.
Here, the particular StockItem is being assigned (manually) to a customer
@ -688,8 +665,7 @@ class StockAssignmentItemSerializer(serializers.Serializer):
class StockAssignmentSerializer(serializers.Serializer):
"""
Serializer for assigning one (or more) stock items to a customer.
"""Serializer for assigning one (or more) stock items to a customer.
This is a manual assignment process, separate for (for example) a Sales Order
"""
@ -765,8 +741,7 @@ class StockAssignmentSerializer(serializers.Serializer):
class StockMergeItemSerializer(serializers.Serializer):
"""
Serializer for a single StockItem within the StockMergeSerializer class.
"""Serializer for a single StockItem within the StockMergeSerializer class.
Here, the individual StockItem is being checked for merge compatibility.
"""
@ -793,9 +768,7 @@ class StockMergeItemSerializer(serializers.Serializer):
class StockMergeSerializer(serializers.Serializer):
"""
Serializer for merging two (or more) stock items together
"""
"""Serializer for merging two (or more) stock items together"""
class Meta:
fields = [
@ -879,8 +852,8 @@ class StockMergeSerializer(serializers.Serializer):
return data
def save(self):
"""
Actually perform the stock merging action.
"""Actually perform the stock merging action.
At this point we are confident that the merge can take place
"""
@ -908,8 +881,7 @@ class StockMergeSerializer(serializers.Serializer):
class StockAdjustmentItemSerializer(serializers.Serializer):
"""
Serializer for a single StockItem within a stock adjument request.
"""Serializer for a single StockItem within a stock adjument request.
Fields:
- item: StockItem object
@ -940,9 +912,7 @@ class StockAdjustmentItemSerializer(serializers.Serializer):
class StockAdjustmentSerializer(serializers.Serializer):
"""
Base class for managing stock adjustment actions via the API
"""
"""Base class for managing stock adjustment actions via the API"""
class Meta:
fields = [
@ -972,9 +942,7 @@ class StockAdjustmentSerializer(serializers.Serializer):
class StockCountSerializer(StockAdjustmentSerializer):
"""
Serializer for counting stock items
"""
"""Serializer for counting stock items"""
def save(self):
@ -998,9 +966,7 @@ class StockCountSerializer(StockAdjustmentSerializer):
class StockAddSerializer(StockAdjustmentSerializer):
"""
Serializer for adding stock to stock item(s)
"""
"""Serializer for adding stock to stock item(s)"""
def save(self):
@ -1023,9 +989,7 @@ class StockAddSerializer(StockAdjustmentSerializer):
class StockRemoveSerializer(StockAdjustmentSerializer):
"""
Serializer for removing stock from stock item(s)
"""
"""Serializer for removing stock from stock item(s)"""
def save(self):
@ -1048,9 +1012,7 @@ class StockRemoveSerializer(StockAdjustmentSerializer):
class StockTransferSerializer(StockAdjustmentSerializer):
"""
Serializer for transferring (moving) stock item(s)
"""
"""Serializer for transferring (moving) stock item(s)"""
location = serializers.PrimaryKeyRelatedField(
queryset=StockLocation.objects.all(),

View File

@ -1,6 +1,4 @@
"""
Unit testing for the Stock API
"""
"""Unit testing for the Stock API"""
import io
import os
@ -47,9 +45,7 @@ class StockAPITestCase(InvenTreeAPITestCase):
class StockLocationTest(StockAPITestCase):
"""
Series of API tests for the StockLocation API
"""
"""Series of API tests for the StockLocation API"""
list_url = reverse('api-location-list')
def setUp(self):
@ -76,16 +72,12 @@ class StockLocationTest(StockAPITestCase):
class StockItemListTest(StockAPITestCase):
"""
Tests for the StockItem API LIST endpoint
"""
"""Tests for the StockItem API LIST endpoint"""
list_url = reverse('api-stock-list')
def get_stock(self, **kwargs):
"""
Filter stock and return JSON object
"""
"""Filter stock and return JSON object"""
response = self.client.get(self.list_url, format='json', data=kwargs)
@ -95,18 +87,14 @@ class StockItemListTest(StockAPITestCase):
return response.data
def test_get_stock_list(self):
"""
List *all* StockItem objects.
"""
"""List *all* StockItem objects."""
response = self.get_stock()
self.assertEqual(len(response), 29)
def test_filter_by_part(self):
"""
Filter StockItem by Part reference
"""
"""Filter StockItem by Part reference"""
response = self.get_stock(part=25)
@ -117,17 +105,13 @@ class StockItemListTest(StockAPITestCase):
self.assertEqual(len(response), 12)
def test_filter_by_IPN(self):
"""
Filter StockItem by IPN reference
"""
"""Filter StockItem by IPN reference"""
response = self.get_stock(IPN="R.CH")
self.assertEqual(len(response), 3)
def test_filter_by_location(self):
"""
Filter StockItem by StockLocation reference
"""
"""Filter StockItem by StockLocation reference"""
response = self.get_stock(location=5)
self.assertEqual(len(response), 1)
@ -142,9 +126,7 @@ class StockItemListTest(StockAPITestCase):
self.assertEqual(len(response), 18)
def test_filter_by_depleted(self):
"""
Filter StockItem by depleted status
"""
"""Filter StockItem by depleted status"""
response = self.get_stock(depleted=1)
self.assertEqual(len(response), 1)
@ -153,9 +135,7 @@ class StockItemListTest(StockAPITestCase):
self.assertEqual(len(response), 28)
def test_filter_by_in_stock(self):
"""
Filter StockItem by 'in stock' status
"""
"""Filter StockItem by 'in stock' status"""
response = self.get_stock(in_stock=1)
self.assertEqual(len(response), 26)
@ -164,9 +144,7 @@ class StockItemListTest(StockAPITestCase):
self.assertEqual(len(response), 3)
def test_filter_by_status(self):
"""
Filter StockItem by 'status' field
"""
"""Filter StockItem by 'status' field"""
codes = {
StockStatus.OK: 27,
@ -183,17 +161,13 @@ class StockItemListTest(StockAPITestCase):
self.assertEqual(len(response), num)
def test_filter_by_batch(self):
"""
Filter StockItem by batch code
"""
"""Filter StockItem by batch code"""
response = self.get_stock(batch='B123')
self.assertEqual(len(response), 1)
def test_filter_by_serialized(self):
"""
Filter StockItem by serialized status
"""
"""Filter StockItem by serialized status"""
response = self.get_stock(serialized=1)
self.assertEqual(len(response), 12)
@ -208,9 +182,7 @@ class StockItemListTest(StockAPITestCase):
self.assertIsNone(item['serial'])
def test_filter_by_has_batch(self):
"""
Test the 'has_batch' filter, which tests if the stock item has been assigned a batch code
"""
"""Test the 'has_batch' filter, which tests if the stock item has been assigned a batch code"""
with_batch = self.get_stock(has_batch=1)
without_batch = self.get_stock(has_batch=0)
@ -227,8 +199,7 @@ class StockItemListTest(StockAPITestCase):
self.assertTrue(item['batch'] in [None, ''])
def test_filter_by_tracked(self):
"""
Test the 'tracked' filter.
"""Test the 'tracked' filter.
This checks if the stock item has either a batch code *or* a serial number
"""
@ -248,9 +219,7 @@ class StockItemListTest(StockAPITestCase):
self.assertTrue(item['batch'] in blank and item['serial'] in blank)
def test_filter_by_expired(self):
"""
Filter StockItem by expiry status
"""
"""Filter StockItem by expiry status"""
# First, we can assume that the 'stock expiry' feature is disabled
response = self.get_stock(expired=1)
@ -289,9 +258,7 @@ class StockItemListTest(StockAPITestCase):
self.assertEqual(len(response), 25)
def test_paginate(self):
"""
Test that we can paginate results correctly
"""
"""Test that we can paginate results correctly"""
for n in [1, 5, 10]:
response = self.get_stock(limit=n)
@ -321,9 +288,7 @@ class StockItemListTest(StockAPITestCase):
return dataset
def test_export(self):
"""
Test exporting of Stock data via the API
"""
"""Test exporting of Stock data via the API"""
dataset = self.export_data({})
@ -361,9 +326,7 @@ class StockItemListTest(StockAPITestCase):
class StockItemTest(StockAPITestCase):
"""
Series of API tests for the StockItem API
"""
"""Series of API tests for the StockItem API"""
list_url = reverse('api-stock-list')
@ -376,8 +339,7 @@ class StockItemTest(StockAPITestCase):
StockLocation.objects.create(name='C', description='location c', parent=top)
def test_create_default_location(self):
"""
Test the default location functionality,
"""Test the default location functionality,
if a 'location' is not specified in the creation request.
"""
@ -423,9 +385,7 @@ class StockItemTest(StockAPITestCase):
self.assertEqual(response.data['location'], None)
def test_stock_item_create(self):
"""
Test creation of a StockItem via the API
"""
"""Test creation of a StockItem via the API"""
# POST with an empty part reference
@ -476,9 +436,7 @@ class StockItemTest(StockAPITestCase):
)
def test_creation_with_serials(self):
"""
Test that serialized stock items can be created via the API,
"""
"""Test that serialized stock items can be created via the API."""
trackable_part = part.models.Part.objects.create(
name='My part',
@ -587,9 +545,7 @@ class StockItemTest(StockAPITestCase):
self.assertEqual(response.data['expiry_date'], expiry.isoformat())
def test_purchase_price(self):
"""
Test that we can correctly read and adjust purchase price information via the API
"""
"""Test that we can correctly read and adjust purchase price information via the API"""
url = reverse('api-stock-detail', kwargs={'pk': 1})
@ -648,7 +604,7 @@ class StockItemTest(StockAPITestCase):
self.assertEqual(data['purchase_price_currency'], 'NZD')
def test_install(self):
""" Test that stock item can be installed into antoher item, via the API """
"""Test that stock item can be installed into antoher item, via the API"""
# Select the "parent" stock item
parent_part = part.models.Part.objects.get(pk=100)
@ -731,15 +687,10 @@ class StockItemTest(StockAPITestCase):
class StocktakeTest(StockAPITestCase):
"""
Series of tests for the Stocktake API
"""
"""Series of tests for the Stocktake API"""
def test_action(self):
"""
Test each stocktake action endpoint,
for validation
"""
"""Test each stocktake action endpoint, for validation"""
for endpoint in ['api-stock-count', 'api-stock-add', 'api-stock-remove']:
@ -796,9 +747,7 @@ class StocktakeTest(StockAPITestCase):
self.assertContains(response, 'Ensure this value is greater than or equal to 0', status_code=status.HTTP_400_BAD_REQUEST)
def test_transfer(self):
"""
Test stock transfers
"""
"""Test stock transfers"""
data = {
'items': [
@ -825,9 +774,7 @@ class StocktakeTest(StockAPITestCase):
class StockItemDeletionTest(StockAPITestCase):
"""
Tests for stock item deletion via the API
"""
"""Tests for stock item deletion via the API"""
def test_delete(self):
@ -974,8 +921,7 @@ class StockTestResultTest(StockAPITestCase):
class StockAssignTest(StockAPITestCase):
"""
Unit tests for the stock assignment API endpoint,
"""Unit tests for the stock assignment API endpoint,
where stock items are manually assigned to a customer
"""
@ -1083,9 +1029,7 @@ class StockAssignTest(StockAPITestCase):
class StockMergeTest(StockAPITestCase):
"""
Unit tests for merging stock items via the API
"""
"""Unit tests for merging stock items via the API"""
URL = reverse('api-stock-merge')
@ -1117,9 +1061,7 @@ class StockMergeTest(StockAPITestCase):
)
def test_missing_data(self):
"""
Test responses which are missing required data
"""
"""Test responses which are missing required data"""
# Post completely empty
@ -1145,9 +1087,7 @@ class StockMergeTest(StockAPITestCase):
self.assertIn('At least two stock items', str(data))
def test_invalid_data(self):
"""
Test responses which have invalid data
"""
"""Test responses which have invalid data"""
# Serialized stock items should be rejected
data = self.post(
@ -1229,9 +1169,7 @@ class StockMergeTest(StockAPITestCase):
self.assertIn('Stock items must refer to the same supplier part', str(data))
def test_valid_merge(self):
"""
Test valid merging of stock items
"""
"""Test valid merging of stock items"""
# Check initial conditions
n = StockItem.objects.filter(part=self.part).count()

View File

@ -1,4 +1,4 @@
""" Unit tests for Stock views (see views.py) """
"""Unit tests for Stock views (see views.py)"""
from django.urls import reverse
@ -22,7 +22,7 @@ class StockViewTestCase(InvenTreeTestCase):
class StockListTest(StockViewTestCase):
""" Tests for Stock list views """
"""Tests for Stock list views"""
def test_stock_index(self):
response = self.client.get(reverse('stock-index'))
@ -30,10 +30,10 @@ class StockListTest(StockViewTestCase):
class StockOwnershipTest(StockViewTestCase):
""" Tests for stock ownership views """
"""Tests for stock ownership views"""
def setUp(self):
""" Add another user for ownership tests """
"""Add another user for ownership tests"""
"""
TODO: Refactor this following test to use the new API form

View File

@ -13,9 +13,7 @@ from .models import (StockItem, StockItemTestResult, StockItemTracking,
class StockTest(InvenTreeTestCase):
"""
Tests to ensure that the stock location tree functions correcly
"""
"""Tests to ensure that the stock location tree functions correcly"""
fixtures = [
'category',
@ -44,10 +42,7 @@ class StockTest(InvenTreeTestCase):
StockItem.objects.rebuild()
def test_expiry(self):
"""
Test expiry date functionality for StockItem model.
"""
"""Test expiry date functionality for StockItem model."""
today = datetime.datetime.now().date()
item = StockItem.objects.create(
@ -78,10 +73,7 @@ class StockTest(InvenTreeTestCase):
self.assertTrue(item.is_expired())
def test_is_building(self):
"""
Test that the is_building flag does not count towards stock.
"""
"""Test that the is_building flag does not count towards stock."""
part = Part.objects.get(pk=1)
# Record the total stock count
@ -197,7 +189,6 @@ class StockTest(InvenTreeTestCase):
def test_move(self):
""" Test stock movement functions """
# Move 4,000 screws to the bathroom
it = StockItem.objects.get(pk=1)
self.assertNotEqual(it.location, self.bathroom)
@ -339,10 +330,7 @@ class StockTest(InvenTreeTestCase):
w2 = StockItem.objects.get(pk=101)
def test_serials(self):
"""
Tests for stock serialization
"""
"""Tests for stock serialization"""
p = Part.objects.create(
name='trackable part',
description='trackable part',
@ -373,10 +361,7 @@ class StockTest(InvenTreeTestCase):
self.assertTrue(item.serialized)
def test_big_serials(self):
"""
Unit tests for "large" serial numbers which exceed integer encoding
"""
"""Unit tests for "large" serial numbers which exceed integer encoding"""
p = Part.objects.create(
name='trackable part',
description='trackable part',
@ -451,11 +436,10 @@ class StockTest(InvenTreeTestCase):
self.assertEqual(item_prev.serial_int, 99)
def test_serialize_stock_invalid(self):
"""
Test manual serialization of parts.
"""Test manual serialization of parts.
Each of these tests should fail
"""
# Test serialization of non-serializable part
item = StockItem.objects.get(pk=1234)
@ -480,8 +464,7 @@ class StockTest(InvenTreeTestCase):
item.serializeStock(3, "hello", self.user)
def test_serialize_stock_valid(self):
""" Perform valid stock serializations """
"""Perform valid stock serializations"""
# There are 10 of these in stock
# Item will deplete when deleted
item = StockItem.objects.get(pk=100)
@ -517,8 +500,8 @@ class StockTest(InvenTreeTestCase):
item.serializeStock(2, [99, 100], self.user)
def test_location_tree(self):
"""
Unit tests for stock location tree structure (MPTT).
"""Unit tests for stock location tree structure (MPTT).
Ensure that the MPTT structure is rebuilt correctly,
and the corrent ancestor tree is observed.
@ -686,9 +669,7 @@ class StockTest(InvenTreeTestCase):
class VariantTest(StockTest):
"""
Tests for calculation stock counts against templates / variants
"""
"""Tests for calculation stock counts against templates / variants"""
def test_variant_stock(self):
# Check the 'Chair' variant
@ -769,9 +750,7 @@ class VariantTest(StockTest):
class TestResultTest(StockTest):
"""
Tests for the StockItemTestResult model.
"""
"""Tests for the StockItemTestResult model."""
def test_test_count(self):
item = StockItem.objects.get(pk=105)
@ -898,12 +877,10 @@ class TestResultTest(StockTest):
self.assertEqual(item3.test_results.count(), 4)
def test_installed_tests(self):
"""
Test test results for stock in stock.
"""Test test results for stock in stock.
Or, test "test results" for "stock items" installed "inside" a "stock item"
"""
# Get a "master" stock item
item = StockItem.objects.get(pk=105)

View File

@ -1,6 +1,4 @@
"""
URL lookup for Stock app
"""
"""URL lookup for Stock app"""
from django.urls import include, re_path

View File

@ -1,6 +1,4 @@
"""
Django views for interacting with Stock app
"""
"""Django views for interacting with Stock app"""
from datetime import datetime
@ -21,8 +19,7 @@ from .models import StockItem, StockItemTracking, StockLocation
class StockIndex(InvenTreeRoleMixin, InvenTreePluginViewMixin, ListView):
""" StockIndex view loads all StockLocation and StockItem object
"""
"""StockIndex view loads all StockLocation and StockItem object"""
model = StockItem
template_name = 'stock/location.html'
context_obect_name = 'locations'
@ -48,9 +45,7 @@ class StockIndex(InvenTreeRoleMixin, InvenTreePluginViewMixin, ListView):
class StockLocationDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
"""
Detailed view of a single StockLocation object
"""
"""Detailed view of a single StockLocation object"""
context_object_name = 'location'
template_name = 'stock/location.html'
@ -69,9 +64,7 @@ class StockLocationDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailVi
class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
"""
Detailed view of a single StockItem object
"""
"""Detailed view of a single StockItem object"""
context_object_name = 'item'
template_name = 'stock/item.html'
@ -103,7 +96,7 @@ class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
return data
def get(self, request, *args, **kwargs):
""" check if item exists else return to stock index """
"""Check if item exists else return to stock index"""
stock_pk = kwargs.get('pk', None)
@ -120,7 +113,7 @@ class StockItemDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
class StockLocationQRCode(QRCodeView):
""" View for displaying a QR code for a StockLocation object """
"""View for displaying a QR code for a StockLocation object"""
ajax_form_title = _("Stock Location QR code")
@ -136,9 +129,7 @@ class StockLocationQRCode(QRCodeView):
class StockItemReturnToStock(AjaxUpdateView):
"""
View for returning a stock item (which is assigned to a customer) to stock.
"""
"""View for returning a stock item (which is assigned to a customer) to stock."""
model = StockItem
ajax_form_title = _("Return to Stock")
@ -166,9 +157,7 @@ class StockItemReturnToStock(AjaxUpdateView):
class StockItemDeleteTestData(AjaxUpdateView):
"""
View for deleting all test data
"""
"""View for deleting all test data"""
model = StockItem
form_class = ConfirmForm
@ -203,7 +192,7 @@ class StockItemDeleteTestData(AjaxUpdateView):
class StockItemQRCode(QRCodeView):
""" View for displaying a QR code for a StockItem object """
"""View for displaying a QR code for a StockItem object"""
ajax_form_title = _("Stock Item QR Code")
role_required = 'stock.view'
@ -218,9 +207,7 @@ class StockItemQRCode(QRCodeView):
class StockItemConvert(AjaxUpdateView):
"""
View for 'converting' a StockItem to a variant of its current part.
"""
"""View for 'converting' a StockItem to a variant of its current part."""
model = StockItem
form_class = StockForms.ConvertStockItemForm
@ -229,9 +216,7 @@ class StockItemConvert(AjaxUpdateView):
context_object_name = 'item'
def get_form(self):
"""
Filter the available parts.
"""
"""Filter the available parts."""
form = super().get_form()
item = self.get_object()
@ -289,7 +274,7 @@ class StockItemTrackingDelete(AjaxDeleteView):
class StockItemTrackingEdit(AjaxUpdateView):
""" View for editing a StockItemTracking object """
"""View for editing a StockItemTracking object"""
model = StockItemTracking
ajax_form_title = _('Edit Stock Tracking Entry')
@ -297,8 +282,7 @@ class StockItemTrackingEdit(AjaxUpdateView):
class StockItemTrackingCreate(AjaxCreateView):
""" View for creating a new StockItemTracking object.
"""
"""View for creating a new StockItemTracking object."""
model = StockItemTracking
ajax_form_title = _("Add Stock Tracking Entry")

View File

@ -14,9 +14,7 @@ User = get_user_model()
class RuleSetInline(admin.TabularInline):
"""
Class for displaying inline RuleSet data in the Group admin page.
"""
"""Class for displaying inline RuleSet data in the Group admin page."""
model = RuleSet
can_delete = False
@ -76,9 +74,7 @@ class InvenTreeGroupAdminForm(forms.ModelForm):
class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover
"""
Custom admin interface for the Group model
"""
"""Custom admin interface for the Group model"""
form = InvenTreeGroupAdminForm
@ -213,9 +209,7 @@ class InvenTreeUserAdmin(UserAdmin):
class OwnerAdmin(admin.ModelAdmin):
"""
Custom admin interface for the Owner model
"""
"""Custom admin interface for the Owner model"""
pass

View File

@ -14,9 +14,7 @@ from users.serializers import OwnerSerializer, UserSerializer
class OwnerList(generics.ListAPIView):
"""
List API endpoint for Owner model. Cannot create.
"""
"""List API endpoint for Owner model. Cannot create."""
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
@ -54,9 +52,7 @@ class OwnerList(generics.ListAPIView):
class OwnerDetail(generics.RetrieveAPIView):
"""
Detail API endpoint for Owner model. Cannot edit or delete
"""
"""Detail API endpoint for Owner model. Cannot edit or delete"""
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
@ -108,7 +104,7 @@ class RoleDetails(APIView):
class UserDetail(generics.RetrieveAPIView):
""" Detail endpoint for a single user """
"""Detail endpoint for a single user"""
queryset = User.objects.all()
serializer_class = UserSerializer
@ -116,7 +112,7 @@ class UserDetail(generics.RetrieveAPIView):
class UserList(generics.ListAPIView):
""" List endpoint for detail on all users """
"""List endpoint for detail on all users"""
queryset = User.objects.all()
serializer_class = UserSerializer
@ -135,7 +131,7 @@ class UserList(generics.ListAPIView):
class GetAuthToken(APIView):
""" Return authentication token for an authenticated user. """
"""Return authentication token for an authenticated user."""
permission_classes = [
permissions.IsAuthenticated,

View File

@ -221,9 +221,7 @@ class RuleSet(models.Model):
@classmethod
def check_table_permission(cls, user, table, permission):
"""
Check if the provided user has the specified permission against the table
"""
"""Check if the provided user has the specified permission against the table"""
# If the table does *not* require permissions
if table in cls.RULESET_IGNORE:
@ -269,7 +267,7 @@ class RuleSet(models.Model):
)
def __str__(self, debug=False): # pragma: no cover
""" Ruleset string representation """
"""Ruleset string representation"""
if debug:
# Makes debugging easier
return f'{str(self.group).ljust(15)}: {self.name.title().ljust(15)} | ' \
@ -296,15 +294,13 @@ class RuleSet(models.Model):
self.group.save()
def get_models(self):
"""
Return the database tables / models that this ruleset covers.
"""
"""Return the database tables / models that this ruleset covers."""
return self.RULESET_MODELS.get(self.name, [])
def split_model(model):
"""get modelname and app from modelstring"""
"""Get modelname and app from modelstring"""
*app, model = model.split('_')
# handle models that have
@ -317,7 +313,7 @@ def split_model(model):
def split_permission(app, perm):
"""split permission string into permission and model"""
"""Split permission string into permission and model"""
permission_name, *model = perm.split('_')
# handle models that have underscores
if len(model) > 1: # pragma: no cover
@ -329,7 +325,6 @@ def split_permission(app, perm):
def update_group_roles(group, debug=False):
"""
Iterates through all of the RuleSets associated with the group,
and ensures that the correct permissions are either applied or removed from the group.
@ -339,7 +334,6 @@ def update_group_roles(group, debug=False):
b) Whenver the group object is updated
The RuleSet model has complete control over the permissions applied to any group.
"""
if not canAppAccessDatabase(allow_test=True):
@ -594,24 +588,20 @@ class Owner(models.Model):
owner = GenericForeignKey('owner_type', 'owner_id')
def __str__(self):
""" Defines the owner string representation """
"""Defines the owner string representation"""
return f'{self.owner} ({self.owner_type.name})'
def name(self):
"""
Return the 'name' of this owner
"""
"""Return the 'name' of this owner"""
return str(self.owner)
def label(self):
"""
Return the 'type' label of this owner i.e. 'user' or 'group'
"""
"""Return the 'type' label of this owner i.e. 'user' or 'group'"""
return str(self.owner_type.name)
@classmethod
def create(cls, obj):
""" Check if owner exist then create new owner entry """
"""Check if owner exist then create new owner entry"""
# Check for existing owner
existing_owner = cls.get_owner(obj)
@ -627,7 +617,7 @@ class Owner(models.Model):
@classmethod
def get_owner(cls, user_or_group):
""" Get owner instance for a group or user """
"""Get owner instance for a group or user"""
user_model = get_user_model()
owner = None

View File

@ -10,8 +10,7 @@ from .models import Owner
class UserSerializer(InvenTreeModelSerializer):
""" Serializer for a User
"""
"""Serializer for a User"""
class Meta:
model = User
@ -23,9 +22,7 @@ class UserSerializer(InvenTreeModelSerializer):
class OwnerSerializer(InvenTreeModelSerializer):
"""
Serializer for an "Owner" (either a "user" or a "group")
"""
"""Serializer for an "Owner" (either a "user" or a "group")"""
name = serializers.CharField(read_only=True)

View File

@ -1,6 +1,4 @@
"""
Unit tests for the user model database migrations
"""
"""Unit tests for the user model database migrations"""
from django_test_migrations.contrib.unittest_case import MigratorTestCase
@ -8,9 +6,7 @@ from InvenTree import helpers
class TestForwardMigrations(MigratorTestCase):
"""
Test entire schema migration sequence for the users app
"""
"""Test entire schema migration sequence for the users app"""
migrate_from = ('users', helpers.getOldestMigrationFile('users'))
migrate_to = ('users', helpers.getNewestMigrationFile('users'))

View File

@ -10,9 +10,7 @@ from users.models import Owner, RuleSet
class RuleSetModelTest(TestCase):
"""
Some simplistic tests to ensure the RuleSet model is setup correctly.
"""
"""Some simplistic tests to ensure the RuleSet model is setup correctly."""
def test_ruleset_models(self):
@ -48,10 +46,7 @@ class RuleSetModelTest(TestCase):
self.assertEqual(len(empty), 0)
def test_model_names(self):
"""
Test that each model defined in the rulesets is valid,
based on the database schema!
"""
"""Test that each model defined in the rulesets is valid, based on the database schema!"""
available_models = apps.get_models()
@ -108,9 +103,7 @@ class RuleSetModelTest(TestCase):
self.assertEqual(len(extra_models), 0)
def test_permission_assign(self):
"""
Test that the permission assigning works!
"""
"""Test that the permission assigning works!"""
# Create a new group
group = Group.objects.create(name="Test group")
@ -161,9 +154,7 @@ class RuleSetModelTest(TestCase):
class OwnerModelTest(InvenTreeTestCase):
"""
Some simplistic tests to ensure the Owner model is setup correctly.
"""
"""Some simplistic tests to ensure the Owner model is setup correctly."""
def do_request(self, endpoint, filters, status_code=200):
response = self.client.get(endpoint, filters, format='json')
@ -212,9 +203,7 @@ class OwnerModelTest(InvenTreeTestCase):
self.assertEqual(group_as_owner, None)
def test_api(self):
"""
Test user APIs
"""
"""Test user APIs"""
self.client.logout()
# not authed
@ -231,9 +220,7 @@ class OwnerModelTest(InvenTreeTestCase):
# self.do_request(reverse('api-owner-detail', kwargs={'pk': self.user.id}), {})
def test_token(self):
"""
Test token mechanisms
"""
"""Test token mechanisms"""
self.client.logout()
token = Token.objects.filter(user=self.user)