From 9b40fddf7cce55fc36a851bfc224102538a876ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 May 2022 02:04:02 +0200 Subject: [PATCH] reformat comments 1 --- InvenTree/report/models.py | 3 +- InvenTree/report/templatetags/barcode.py | 17 +- InvenTree/report/templatetags/report.py | 23 +- InvenTree/report/tests.py | 20 +- InvenTree/script/translation_stats.py | 4 +- InvenTree/stock/__init__.py | 3 +- InvenTree/stock/admin.py | 8 +- InvenTree/stock/api.py | 132 +++------- InvenTree/stock/forms.py | 13 +- InvenTree/stock/models.py | 298 ++++++----------------- InvenTree/stock/serializers.py | 96 +++----- InvenTree/stock/test_api.py | 128 +++------- InvenTree/stock/test_views.py | 8 +- InvenTree/stock/tests.py | 49 +--- InvenTree/stock/urls.py | 4 +- InvenTree/stock/views.py | 42 +--- InvenTree/users/admin.py | 12 +- InvenTree/users/api.py | 14 +- InvenTree/users/models.py | 30 +-- InvenTree/users/serializers.py | 7 +- InvenTree/users/test_migrations.py | 8 +- InvenTree/users/tests.py | 25 +- 22 files changed, 262 insertions(+), 682 deletions(-) diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index bc852b4faa..7714b42306 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -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 %} tag. diff --git a/InvenTree/report/templatetags/barcode.py b/InvenTree/report/templatetags/barcode.py index 8d8397d50a..1abf0b1eea 100644 --- a/InvenTree/report/templatetags/barcode.py +++ b/InvenTree/report/templatetags/barcode.py @@ -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 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) diff --git a/InvenTree/report/templatetags/report.py b/InvenTree/report/templatetags/report.py index 8a14501933..7d6be7d836 100644 --- a/InvenTree/report/templatetags/report.py +++ b/InvenTree/report/templatetags/report.py @@ -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 href which points to an InvenTree URL. + """Make 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) diff --git a/InvenTree/report/tests.py b/InvenTree/report/tests.py index 38fbb491bf..9d5508b249 100644 --- a/InvenTree/report/tests.py +++ b/InvenTree/report/tests.py @@ -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}) diff --git a/InvenTree/script/translation_stats.py b/InvenTree/script/translation_stats.py index e96708d1f2..36032ed1e0 100644 --- a/InvenTree/script/translation_stats.py +++ b/InvenTree/script/translation_stats.py @@ -1,6 +1,4 @@ -""" -This script calculates translation coverage for various languages -""" +"""This script calculates translation coverage for various languages""" import json import os diff --git a/InvenTree/stock/__init__.py b/InvenTree/stock/__init__.py index 6970329be1..524da0600c 100644 --- a/InvenTree/stock/__init__.py +++ b/InvenTree/stock/__init__.py @@ -1,5 +1,4 @@ -""" -The Stock module is responsible for Stock management. +"""The Stock module is responsible for Stock management. It includes models for: diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index 85d7b7afe0..85561c9786 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -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)) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 4bc21df5a9..56b316eb50 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -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 diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 7e615b5eae..847cd875a5 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -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? """ diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 3faea832d9..be0ab4117d 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -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. diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 920052a0ec..44f0946194 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -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(), diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 87d8bfaada..af02a57d63 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -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() diff --git a/InvenTree/stock/test_views.py b/InvenTree/stock/test_views.py index dba39334de..48f1126395 100644 --- a/InvenTree/stock/test_views.py +++ b/InvenTree/stock/test_views.py @@ -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 diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index fed4b9858e..a89817aa9d 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -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) diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index 3a9ad6c490..6a6b8da44e 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -1,6 +1,4 @@ -""" -URL lookup for Stock app -""" +"""URL lookup for Stock app""" from django.urls import include, re_path diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 7a0f1ab978..5df3f2a027 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -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") diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index 2be478fa0e..0e533bb164 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -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 diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py index d2b25ceb5b..be8d83b58b 100644 --- a/InvenTree/users/api.py +++ b/InvenTree/users/api.py @@ -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, diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index be5fac1641..786ed2c081 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -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 diff --git a/InvenTree/users/serializers.py b/InvenTree/users/serializers.py index fcf1ed5678..70a80f7d7f 100644 --- a/InvenTree/users/serializers.py +++ b/InvenTree/users/serializers.py @@ -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) diff --git a/InvenTree/users/test_migrations.py b/InvenTree/users/test_migrations.py index 7bb17d0070..0adffe070b 100644 --- a/InvenTree/users/test_migrations.py +++ b/InvenTree/users/test_migrations.py @@ -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')) diff --git a/InvenTree/users/tests.py b/InvenTree/users/tests.py index 393b640d2e..0420096cbf 100644 --- a/InvenTree/users/tests.py +++ b/InvenTree/users/tests.py @@ -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)