diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 4675c529da..3a9bd4bb7a 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -1,6 +1,4 @@ -""" -JSON serializers for Part app -""" +"""JSON serializers for Part app""" import imghdr from decimal import Decimal @@ -37,16 +35,14 @@ from .models import (BomItem, BomItemSubstitute, Part, PartAttachment, class CategorySerializer(InvenTreeModelSerializer): - """ Serializer for PartCategory """ + """Serializer for PartCategory""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def get_starred(self, category): - """ - Return True if the category is directly "starred" by the current user - """ + """Return True if the category is directly "starred" by the current user""" return category in self.context.get('starred_categories', []) @@ -76,9 +72,7 @@ class CategorySerializer(InvenTreeModelSerializer): class CategoryTree(InvenTreeModelSerializer): - """ - Serializer for PartCategory tree - """ + """Serializer for PartCategory tree""" class Meta: model = PartCategory @@ -90,9 +84,7 @@ class CategoryTree(InvenTreeModelSerializer): class PartAttachmentSerializer(InvenTreeAttachmentSerializer): - """ - Serializer for the PartAttachment class - """ + """Serializer for the PartAttachment class""" class Meta: model = PartAttachment @@ -113,9 +105,7 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer): class PartTestTemplateSerializer(InvenTreeModelSerializer): - """ - Serializer for the PartTestTemplate class - """ + """Serializer for the PartTestTemplate class""" key = serializers.CharField(read_only=True) @@ -135,9 +125,7 @@ class PartTestTemplateSerializer(InvenTreeModelSerializer): class PartSalePriceSerializer(InvenTreeModelSerializer): - """ - Serializer for sale prices for Part model. - """ + """Serializer for sale prices for Part model.""" quantity = InvenTreeDecimalField() @@ -167,9 +155,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): class PartInternalPriceSerializer(InvenTreeModelSerializer): - """ - Serializer for internal prices for Part model. - """ + """Serializer for internal prices for Part model.""" quantity = InvenTreeDecimalField() @@ -199,8 +185,8 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer): class PartThumbSerializer(serializers.Serializer): - """ - Serializer for the 'image' field of the Part model. + """Serializer for the 'image' field of the Part model. + Used to serve and display existing Part images. """ @@ -209,12 +195,10 @@ class PartThumbSerializer(serializers.Serializer): class PartThumbSerializerUpdate(InvenTreeModelSerializer): - """ Serializer for updating Part thumbnail """ + """Serializer for updating Part thumbnail""" def validate_image(self, value): - """ - Check that file is an image. - """ + """Check that file is an image.""" validate = imghdr.what(value) if not validate: raise serializers.ValidationError("File is not an image") @@ -230,7 +214,7 @@ class PartThumbSerializerUpdate(InvenTreeModelSerializer): class PartParameterTemplateSerializer(InvenTreeModelSerializer): - """ JSON serializer for the PartParameterTemplate model """ + """JSON serializer for the PartParameterTemplate model""" class Meta: model = PartParameterTemplate @@ -242,7 +226,7 @@ class PartParameterTemplateSerializer(InvenTreeModelSerializer): class PartParameterSerializer(InvenTreeModelSerializer): - """ JSON serializers for the PartParameter model """ + """JSON serializers for the PartParameter model""" template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True) @@ -258,7 +242,7 @@ class PartParameterSerializer(InvenTreeModelSerializer): class PartBriefSerializer(InvenTreeModelSerializer): - """ Serializer for Part (brief detail) """ + """Serializer for Part (brief detail)""" thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True) @@ -288,7 +272,8 @@ class PartBriefSerializer(InvenTreeModelSerializer): class PartSerializer(InvenTreeModelSerializer): - """ Serializer for complete detail information of a part. + """Serializer for complete detail information of a part. + Used when displaying all details of a single component. """ @@ -296,11 +281,7 @@ class PartSerializer(InvenTreeModelSerializer): return reverse_lazy('api-part-list') def __init__(self, *args, **kwargs): - """ - Custom initialization method for PartSerializer, - so that we can optionally pass extra fields based on the query. - """ - + """Custom initialization method for PartSerializer, so that we can optionally pass extra fields based on the query.""" self.starred_parts = kwargs.pop('starred_parts', []) category_detail = kwargs.pop('category_detail', False) @@ -317,12 +298,10 @@ class PartSerializer(InvenTreeModelSerializer): @staticmethod def annotate_queryset(queryset): - """ - Add some extra annotations to the queryset, - performing database queries as efficiently as possible, - to reduce database trips. - """ + """Add some extra annotations to the queryset. + Performing database queries as efficiently as possible, to reduce database trips. + """ # Annotate with the total 'in stock' quantity queryset = queryset.annotate( in_stock=Coalesce( @@ -444,10 +423,7 @@ class PartSerializer(InvenTreeModelSerializer): return queryset def get_starred(self, part): - """ - Return "true" if the part is starred by the current user. - """ - + """Return "true" if the part is starred by the current user.""" return part in self.starred_parts # Extra detail for the category @@ -522,9 +498,7 @@ class PartSerializer(InvenTreeModelSerializer): class PartRelationSerializer(InvenTreeModelSerializer): - """ - Serializer for a PartRelated model - """ + """Serializer for a PartRelated model""" part_1_detail = PartSerializer(source='part_1', read_only=True, many=False) part_2_detail = PartSerializer(source='part_2', read_only=True, many=False) @@ -541,7 +515,7 @@ class PartRelationSerializer(InvenTreeModelSerializer): class PartStarSerializer(InvenTreeModelSerializer): - """ Serializer for a PartStar object """ + """Serializer for a PartStar object""" partname = serializers.CharField(source='part.full_name', read_only=True) username = serializers.CharField(source='user.username', read_only=True) @@ -558,9 +532,7 @@ class PartStarSerializer(InvenTreeModelSerializer): class BomItemSubstituteSerializer(InvenTreeModelSerializer): - """ - Serializer for the BomItemSubstitute class - """ + """Serializer for the BomItemSubstitute class""" part_detail = PartBriefSerializer(source='part', read_only=True, many=False) @@ -575,9 +547,7 @@ class BomItemSubstituteSerializer(InvenTreeModelSerializer): class BomItemSerializer(InvenTreeModelSerializer): - """ - Serializer for BomItem object - """ + """Serializer for BomItem object""" price_range = serializers.CharField(read_only=True) @@ -663,8 +633,7 @@ class BomItemSerializer(InvenTreeModelSerializer): @staticmethod def annotate_queryset(queryset): - """ - Annotate the BomItem queryset with extra information: + """Annotate the BomItem queryset with extra information: Annotations: available_stock: The amount of stock available for the sub_part Part object @@ -674,7 +643,6 @@ class BomItemSerializer(InvenTreeModelSerializer): Construct an "available stock" quantity: available_stock = total_stock - build_order_allocations - sales_order_allocations """ - build_order_filter = Q(build__status__in=BuildStatus.ACTIVE_CODES) sales_order_filter = Q( line__order__status__in=SalesOrderStatus.OPEN, @@ -799,8 +767,7 @@ class BomItemSerializer(InvenTreeModelSerializer): return queryset def get_purchase_price_range(self, obj): - """ Return purchase price range """ - + """Return purchase price range""" try: purchase_price_min = obj.purchase_price_min except AttributeError: @@ -830,8 +797,7 @@ class BomItemSerializer(InvenTreeModelSerializer): return purchase_price_range def get_purchase_price_avg(self, obj): - """ Return purchase price average """ - + """Return purchase price average""" try: purchase_price_avg = obj.purchase_price_avg except AttributeError: @@ -877,7 +843,7 @@ class BomItemSerializer(InvenTreeModelSerializer): class CategoryParameterTemplateSerializer(InvenTreeModelSerializer): - """ Serializer for PartCategoryParameterTemplate """ + """Serializer for PartCategoryParameterTemplate""" parameter_template = PartParameterTemplateSerializer(many=False, read_only=True) @@ -896,9 +862,7 @@ class CategoryParameterTemplateSerializer(InvenTreeModelSerializer): class PartCopyBOMSerializer(serializers.Serializer): - """ - Serializer for copying a BOM from another part - """ + """Serializer for copying a BOM from another part""" class Meta: fields = [ @@ -919,10 +883,7 @@ class PartCopyBOMSerializer(serializers.Serializer): ) def validate_part(self, part): - """ - Check that a 'valid' part was selected - """ - + """Check that a 'valid' part was selected""" return part remove_existing = serializers.BooleanField( @@ -950,10 +911,7 @@ class PartCopyBOMSerializer(serializers.Serializer): ) def save(self): - """ - Actually duplicate the BOM - """ - + """Actually duplicate the BOM""" base_part = self.context['part'] data = self.validated_data @@ -968,9 +926,7 @@ class PartCopyBOMSerializer(serializers.Serializer): class BomImportUploadSerializer(DataFileUploadSerializer): - """ - Serializer for uploading a file and extracting data from it. - """ + """Serializer for uploading a file and extracting data from it.""" TARGET_MODEL = BomItem @@ -1089,8 +1045,7 @@ class BomImportExtractSerializer(DataFileExtractSerializer): class BomImportSubmitSerializer(serializers.Serializer): - """ - Serializer for uploading a BOM against a specified part. + """Serializer for uploading a BOM against a specified part. A "BOM" is a set of BomItem objects which are to be validated together as a set """ diff --git a/InvenTree/part/settings.py b/InvenTree/part/settings.py index 41ecbb0d00..af904e56fd 100644 --- a/InvenTree/part/settings.py +++ b/InvenTree/part/settings.py @@ -1,61 +1,38 @@ -""" -User-configurable settings for the Part app -""" +"""User-configurable settings for the Part app""" from common.models import InvenTreeSetting def part_assembly_default(): - """ - Returns the default value for the 'assembly' field of a Part object - """ - + """Returns the default value for the 'assembly' field of a Part object""" return InvenTreeSetting.get_setting('PART_ASSEMBLY') def part_template_default(): - """ - Returns the default value for the 'is_template' field of a Part object - """ - + """Returns the default value for the 'is_template' field of a Part object""" return InvenTreeSetting.get_setting('PART_TEMPLATE') def part_virtual_default(): - """ - Returns the default value for the 'is_virtual' field of Part object - """ - + """Returns the default value for the 'is_virtual' field of Part object""" return InvenTreeSetting.get_setting('PART_VIRTUAL') def part_component_default(): - """ - Returns the default value for the 'component' field of a Part object - """ - + """Returns the default value for the 'component' field of a Part object""" return InvenTreeSetting.get_setting('PART_COMPONENT') def part_purchaseable_default(): - """ - Returns the default value for the 'purchasable' field for a Part object - """ - + """Returns the default value for the 'purchasable' field for a Part object""" return InvenTreeSetting.get_setting('PART_PURCHASEABLE') def part_salable_default(): - """ - Returns the default value for the 'salable' field for a Part object - """ - + """Returns the default value for the 'salable' field for a Part object""" return InvenTreeSetting.get_setting('PART_SALABLE') def part_trackable_default(): - """ - Returns the default value for the 'trackable' field for a Part object - """ - + """Returns the default value for the 'trackable' field for a Part object""" return InvenTreeSetting.get_setting('PART_TRACKABLE') diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index 18188bed77..7793b03fb7 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -33,12 +33,10 @@ def notify_low_stock(part: part.models.Part): def notify_low_stock_if_required(part: part.models.Part): - """ - Check if the stock quantity has fallen below the minimum threshold of part. + """Check if the stock quantity has fallen below the minimum threshold of part. If true, notify the users who have subscribed to the part """ - # Run "up" the tree, to allow notification for "parent" parts parts = part.get_ancestors(include_self=True, ascending=True) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 491f29911b..bebfc36939 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -120,7 +120,7 @@ def multiply(x, y, *args, **kwargs): @register.simple_tag() def add(x, y, *args, **kwargs): - """ Add two numbers together """ + """Add two numbers together""" return x + y @@ -277,6 +277,7 @@ def default_currency(*args, **kwargs): @register.simple_tag() def setting_object(key, *args, **kwargs): """Return a setting object speciifed by the given key + (Or return None if the setting does not exist) if a user-setting was requested return that """ diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 3819a01706..561c98695d 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -74,7 +74,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase): def test_category_metadata(self): """Test metadata endpoint for the PartCategory""" - cat = PartCategory.objects.get(pk=1) cat.metadata = { @@ -95,8 +94,7 @@ class PartCategoryAPITest(InvenTreeAPITestCase): class PartOptionsAPITest(InvenTreeAPITestCase): - """ - Tests for the various OPTIONS endpoints in the /part/ API + """Tests for the various OPTIONS endpoints in the /part/ API Ensure that the required field details are provided! """ @@ -110,10 +108,7 @@ class PartOptionsAPITest(InvenTreeAPITestCase): super().setUp() def test_part(self): - """ - Test the Part API OPTIONS - """ - + """Test the Part API OPTIONS""" actions = self.getActions(reverse('api-part-list'))['POST'] # Check that a bunch o' fields are contained @@ -147,10 +142,7 @@ class PartOptionsAPITest(InvenTreeAPITestCase): self.assertEqual(category['help_text'], 'Part category') def test_category(self): - """ - Test the PartCategory API OPTIONS endpoint - """ - + """Test the PartCategory API OPTIONS endpoint""" actions = self.getActions(reverse('api-part-category-list')) # actions should *not* contain 'POST' as we do not have the correct role @@ -169,10 +161,7 @@ class PartOptionsAPITest(InvenTreeAPITestCase): self.assertEqual(loc['api_url'], reverse('api-location-list')) def test_bom_item(self): - """ - Test the BomItem API OPTIONS endpoint - """ - + """Test the BomItem API OPTIONS endpoint""" actions = self.getActions(reverse('api-bom-list'))['POST'] inherited = actions['inherited'] @@ -195,8 +184,8 @@ class PartOptionsAPITest(InvenTreeAPITestCase): class PartAPITest(InvenTreeAPITestCase): - """ - Series of tests for the Part DRF API + """Series of tests for the Part DRF API + - Tests for Part API - Tests for PartCategory API """ @@ -222,11 +211,7 @@ class PartAPITest(InvenTreeAPITestCase): super().setUp() def test_get_categories(self): - """ - Test that we can retrieve list of part categories, - with various filtering options. - """ - + """Test that we can retrieve list of part categories, with various filtering options.""" url = reverse('api-part-category-list') # Request *all* part categories @@ -271,7 +256,7 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(len(response.data), 3) def test_add_categories(self): - """ Check that we can add categories """ + """Check that we can add categories""" data = { 'name': 'Animals', 'description': 'All animals go here' @@ -338,7 +323,8 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(part['category'], 2) def test_include_children(self): - """ Test the special 'include_child_categories' flag + """Test the special 'include_child_categories' flag + If provided, parts are provided for ANY child category (recursive) """ url = reverse('api-part-list') @@ -419,10 +405,7 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_get_thumbs(self): - """ - Return list of part thumbnails - """ - + """Return list of part thumbnails""" url = reverse('api-part-thumbs') response = self.client.get(url) @@ -430,10 +413,7 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) def test_paginate(self): - """ - Test pagination of the Part list API - """ - + """Test pagination of the Part list API""" for n in [1, 5, 10]: response = self.get(reverse('api-part-list'), {'limit': n}) @@ -445,13 +425,11 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(len(data['results']), n) def test_default_values(self): - """ - Tests for 'default' values: + """Tests for 'default' values: Ensure that unspecified fields revert to "default" values (as specified in the model field definition) """ - url = reverse('api-part-list') response = self.client.post(url, { @@ -498,10 +476,7 @@ class PartAPITest(InvenTreeAPITestCase): self.assertFalse(response.data['purchaseable']) def test_initial_stock(self): - """ - Tests for initial stock quantity creation - """ - + """Tests for initial stock quantity creation""" url = reverse('api-part-list') # Track how many parts exist at the start of this test @@ -555,10 +530,7 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(new_part.total_stock, 12345) def test_initial_supplier_data(self): - """ - Tests for initial creation of supplier / manufacturer data - """ - + """Tests for initial creation of supplier / manufacturer data""" url = reverse('api-part-list') n = Part.objects.count() @@ -620,10 +592,7 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(new_part.manufacturer_parts.count(), 1) def test_strange_chars(self): - """ - Test that non-standard ASCII chars are accepted - """ - + """Test that non-standard ASCII chars are accepted""" url = reverse('api-part-list') name = "Kaltgerätestecker" @@ -641,15 +610,13 @@ class PartAPITest(InvenTreeAPITestCase): self.assertEqual(response.data['description'], description) def test_template_filters(self): - """ - Unit tests for API filters related to template parts: + """Unit tests for API filters related to template parts: - variant_of : Return children of specified part - ancestor : Return descendants of specified part Uses the 'chair template' part (pk=10000) """ - # Rebuild the MPTT structure before running these tests Part.objects.rebuild() @@ -736,7 +703,6 @@ class PartAPITest(InvenTreeAPITestCase): Unit tests for the 'variant_stock' annotation, which provides a stock count for *variant* parts """ - # Ensure the MPTT structure is in a known state before running tests Part.objects.rebuild() @@ -821,7 +787,6 @@ class PartAPITest(InvenTreeAPITestCase): def test_part_download(self): """Test download of part data via the API""" - url = reverse('api-part-list') required_cols = [ @@ -873,9 +838,7 @@ class PartAPITest(InvenTreeAPITestCase): class PartDetailTests(InvenTreeAPITestCase): - """ - Test that we can create / edit / delete Part objects via the API - """ + """Test that we can create / edit / delete Part objects via the API""" fixtures = [ 'category', @@ -970,10 +933,7 @@ class PartDetailTests(InvenTreeAPITestCase): self.assertEqual(Part.objects.count(), n) def test_duplicates(self): - """ - Check that trying to create 'duplicate' parts results in errors - """ - + """Check that trying to create 'duplicate' parts results in errors""" # Create a part response = self.client.post(reverse('api-part-list'), { 'name': 'part', @@ -1049,10 +1009,7 @@ class PartDetailTests(InvenTreeAPITestCase): self.assertEqual(response.status_code, 200) def test_image_upload(self): - """ - Test that we can upload an image to the part API - """ - + """Test that we can upload an image to the part API""" self.assignRole('part.add') # Create a new part @@ -1120,10 +1077,7 @@ class PartDetailTests(InvenTreeAPITestCase): self.assertIsNotNone(p.image) def test_details(self): - """ - Test that the required details are available - """ - + """Test that the required details are available""" p = Part.objects.get(pk=1) url = reverse('api-part-detail', kwargs={'pk': 1}) @@ -1152,10 +1106,7 @@ class PartDetailTests(InvenTreeAPITestCase): self.assertEqual(data['unallocated_stock'], 9000) def test_part_metadata(self): - """ - Tests for the part metadata endpoint - """ - + """Tests for the part metadata endpoint""" url = reverse('api-part-metadata', kwargs={'pk': 1}) part = Part.objects.get(pk=1) @@ -1206,9 +1157,7 @@ class PartDetailTests(InvenTreeAPITestCase): class PartAPIAggregationTest(InvenTreeAPITestCase): - """ - Tests to ensure that the various aggregation annotations are working correctly... - """ + """Tests to ensure that the various aggregation annotations are working correctly...""" fixtures = [ 'category', @@ -1267,10 +1216,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase): self.assertTrue(False) # pragma: no cover def test_stock_quantity(self): - """ - Simple test for the stock quantity - """ - + """Simple test for the stock quantity""" data = self.get_part_data() self.assertEqual(data['in_stock'], 600) @@ -1290,11 +1236,10 @@ class PartAPIAggregationTest(InvenTreeAPITestCase): self.assertEqual(data['stock_item_count'], 105) def test_allocation_annotations(self): - """ - Tests for query annotations which add allocation information. + """Tests for query annotations which add allocation information. + Ref: https://github.com/inventree/InvenTree/pull/2797 """ - # We are looking at Part ID 100 ("Bob") url = reverse('api-part-detail', kwargs={'pk': 100}) @@ -1438,9 +1383,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase): class BomItemTest(InvenTreeAPITestCase): - """ - Unit tests for the BomItem API - """ + """Unit tests for the BomItem API""" fixtures = [ 'category', @@ -1461,10 +1404,7 @@ class BomItemTest(InvenTreeAPITestCase): super().setUp() def test_bom_list(self): - """ - Tests for the BomItem list endpoint - """ - + """Tests for the BomItem list endpoint""" # How many BOM items currently exist in the database? n = BomItem.objects.count() @@ -1529,10 +1469,7 @@ class BomItemTest(InvenTreeAPITestCase): self.assertTrue(key in el) def test_get_bom_detail(self): - """ - Get the detail view for a single BomItem object - """ - + """Get the detail view for a single BomItem object""" url = reverse('api-bom-item-detail', kwargs={'pk': 3}) response = self.get(url, expected_code=200) @@ -1570,10 +1507,7 @@ class BomItemTest(InvenTreeAPITestCase): self.assertEqual(response.data['note'], 'Added a note') def test_add_bom_item(self): - """ - Test that we can create a new BomItem via the API - """ - + """Test that we can create a new BomItem via the API""" url = reverse('api-bom-list') data = { @@ -1590,10 +1524,7 @@ class BomItemTest(InvenTreeAPITestCase): self.client.post(url, data, expected_code=400) def test_variants(self): - """ - Tests for BomItem use with variants - """ - + """Tests for BomItem use with variants""" stock_url = reverse('api-stock-list') # BOM item we are interested in @@ -1675,10 +1606,7 @@ class BomItemTest(InvenTreeAPITestCase): self.assertEqual(len(response.data), 2) def test_substitutes(self): - """ - Tests for BomItem substitutes - """ - + """Tests for BomItem substitutes""" url = reverse('api-bom-substitute-list') stock_url = reverse('api-stock-list') @@ -1760,10 +1688,7 @@ class BomItemTest(InvenTreeAPITestCase): self.assertEqual(data['available_stock'], 9000) def test_bom_item_uses(self): - """ - Tests for the 'uses' field - """ - + """Tests for the 'uses' field""" url = reverse('api-bom-list') # Test that the direct 'sub_part' association works @@ -1813,10 +1738,7 @@ class BomItemTest(InvenTreeAPITestCase): self.assertEqual(len(response.data), i) def test_bom_variant_stock(self): - """ - Test for 'available_variant_stock' annotation - """ - + """Test for 'available_variant_stock' annotation""" Part.objects.rebuild() # BOM item we are interested in @@ -1852,10 +1774,7 @@ class BomItemTest(InvenTreeAPITestCase): class PartParameterTest(InvenTreeAPITestCase): - """ - Tests for the ParParameter API - """ - + """Tests for the ParParameter API""" superuser = True fixtures = [ @@ -1870,10 +1789,7 @@ class PartParameterTest(InvenTreeAPITestCase): super().setUp() def test_list_params(self): - """ - Test for listing part parameters - """ - + """Test for listing part parameters""" url = reverse('api-part-parameter-list') response = self.client.get(url, format='json') @@ -1903,10 +1819,7 @@ class PartParameterTest(InvenTreeAPITestCase): self.assertEqual(len(response.data), 3) def test_create_param(self): - """ - Test that we can create a param via the API - """ - + """Test that we can create a param via the API""" url = reverse('api-part-parameter-list') response = self.client.post( @@ -1925,10 +1838,7 @@ class PartParameterTest(InvenTreeAPITestCase): self.assertEqual(len(response.data), 6) def test_param_detail(self): - """ - Tests for the PartParameter detail endpoint - """ - + """Tests for the PartParameter detail endpoint""" url = reverse('api-part-parameter-detail', kwargs={'pk': 5}) response = self.client.get(url) diff --git a/InvenTree/part/test_bom_export.py b/InvenTree/part/test_bom_export.py index d78ced4b17..20af439819 100644 --- a/InvenTree/part/test_bom_export.py +++ b/InvenTree/part/test_bom_export.py @@ -1,6 +1,4 @@ -""" -Unit testing for BOM export functionality -""" +"""Unit testing for BOM export functionality""" import csv @@ -26,10 +24,7 @@ class BomExportTest(InvenTreeTestCase): self.url = reverse('bom-download', kwargs={'pk': 100}) def test_bom_template(self): - """ - Test that the BOM template can be downloaded from the server - """ - + """Test that the BOM template can be downloaded from the server""" url = reverse('bom-upload-template') # Download an XLS template @@ -78,10 +73,7 @@ class BomExportTest(InvenTreeTestCase): self.assertTrue(header in headers) def test_export_csv(self): - """ - Test BOM download in CSV format - """ - + """Test BOM download in CSV format""" params = { 'format': 'csv', 'cascade': True, @@ -142,10 +134,7 @@ class BomExportTest(InvenTreeTestCase): self.assertTrue(header in expected) def test_export_xls(self): - """ - Test BOM download in XLS format - """ - + """Test BOM download in XLS format""" params = { 'format': 'xls', 'cascade': True, @@ -163,10 +152,7 @@ class BomExportTest(InvenTreeTestCase): self.assertEqual(content, 'attachment; filename="BOB | Bob | A2_BOM.xls"') def test_export_xlsx(self): - """ - Test BOM download in XLSX format - """ - + """Test BOM download in XLSX format""" params = { 'format': 'xlsx', 'cascade': True, @@ -181,10 +167,7 @@ class BomExportTest(InvenTreeTestCase): self.assertEqual(response.status_code, 200) def test_export_json(self): - """ - Test BOM download in JSON format - """ - + """Test BOM download in JSON format""" params = { 'format': 'json', 'cascade': True, diff --git a/InvenTree/part/test_bom_import.py b/InvenTree/part/test_bom_import.py index a9c853ddc4..2729d6b1c7 100644 --- a/InvenTree/part/test_bom_import.py +++ b/InvenTree/part/test_bom_import.py @@ -1,6 +1,4 @@ -""" -Unit testing for BOM upload / import functionality -""" +"""Unit testing for BOM upload / import functionality""" from django.core.files.uploadedfile import SimpleUploadedFile from django.urls import reverse @@ -12,9 +10,7 @@ from part.models import Part class BomUploadTest(InvenTreeAPITestCase): - """ - Test BOM file upload API endpoint - """ + """Test BOM file upload API endpoint""" roles = [ 'part.add', @@ -63,10 +59,7 @@ class BomUploadTest(InvenTreeAPITestCase): return response def test_missing_file(self): - """ - POST without a file - """ - + """POST without a file""" response = self.post( reverse('api-bom-import-upload'), data={}, @@ -76,10 +69,7 @@ class BomUploadTest(InvenTreeAPITestCase): self.assertIn('No file was submitted', str(response.data['data_file'])) def test_unsupported_file(self): - """ - POST with an unsupported file type - """ - + """POST with an unsupported file type""" response = self.post_bom( 'sample.txt', b'hello world', @@ -89,10 +79,7 @@ class BomUploadTest(InvenTreeAPITestCase): self.assertIn('Unsupported file type', str(response.data['data_file'])) def test_broken_file(self): - """ - Test upload with broken (corrupted) files - """ - + """Test upload with broken (corrupted) files""" response = self.post_bom( 'sample.csv', b'', @@ -111,10 +98,7 @@ class BomUploadTest(InvenTreeAPITestCase): self.assertIn('Unsupported format, or corrupt file', str(response.data['data_file'])) def test_missing_rows(self): - """ - Test upload of an invalid file (without data rows) - """ - + """Test upload of an invalid file (without data rows)""" dataset = tablib.Dataset() dataset.headers = [ @@ -142,10 +126,7 @@ class BomUploadTest(InvenTreeAPITestCase): self.assertIn('No data rows found in file', str(response.data)) def test_missing_columns(self): - """ - Upload extracted data, but with missing columns - """ - + """Upload extracted data, but with missing columns""" url = reverse('api-bom-import-extract') rows = [ @@ -195,10 +176,7 @@ class BomUploadTest(InvenTreeAPITestCase): ) def test_invalid_data(self): - """ - Upload data which contains errors - """ - + """Upload data which contains errors""" dataset = tablib.Dataset() # Only these headers are strictly necessary @@ -241,10 +219,7 @@ class BomUploadTest(InvenTreeAPITestCase): self.assertEqual(rows[5]['data']['errors']['part'], 'Part is not designated as a component') def test_part_guess(self): - """ - Test part 'guessing' when PK values are not supplied - """ - + """Test part 'guessing' when PK values are not supplied""" dataset = tablib.Dataset() # Should be able to 'guess' the part from the name @@ -304,10 +279,7 @@ class BomUploadTest(InvenTreeAPITestCase): self.assertEqual(rows[idx]['data']['part'], components[idx].pk) def test_levels(self): - """ - Test that multi-level BOMs are correctly handled during upload - """ - + """Test that multi-level BOMs are correctly handled during upload""" url = reverse('api-bom-import-extract') dataset = tablib.Dataset() diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index c4383e5d29..b74a249f00 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -112,7 +112,7 @@ class TemplateTagTest(InvenTreeTestCase): class PartTest(TestCase): - """ Tests for the Part model """ + """Tests for the Part model""" fixtures = [ 'category', @@ -326,10 +326,7 @@ class PartSettingsTest(InvenTreeTestCase): """ def make_part(self): - """ - Helper function to create a simple part - """ - + """Helper function to create a simple part""" part = Part.objects.create( name='Test Part', description='I am but a humble test part', diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index f10b30a05b..b7885f6b5f 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -58,6 +58,7 @@ class PartDetailTest(PartViewTestCase): def test_part_detail_from_ipn(self): """Test that we can retrieve a part detail page from part IPN: + - if no part with matching IPN -> return part index - if unique IPN match -> return part detail page - if multiple IPN matches -> return part index @@ -107,7 +108,6 @@ class PartDetailTest(PartViewTestCase): def test_bom_download(self): """Test downloading a BOM for a valid part""" - response = self.client.get(reverse('bom-download', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) self.assertIn('streaming_content', dir(response)) @@ -141,7 +141,6 @@ class CategoryTest(PartViewTestCase): def test_set_category(self): """Test that the "SetCategory" view works""" - url = reverse('part-set-category') response = self.client.get(url, {'parts[]': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index e607647cc8..972fb484df 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -78,7 +78,6 @@ class PartSetCategory(AjaxUpdateView): def get(self, request, *args, **kwargs): """Respond to a GET request to this view""" - self.request = request if 'parts[]' in request.GET: @@ -90,7 +89,6 @@ class PartSetCategory(AjaxUpdateView): def post(self, request, *args, **kwargs): """Respond to a POST request to this view""" - self.parts = [] for item in request.POST: @@ -1009,6 +1007,7 @@ class CategoryParameterTemplateCreate(AjaxCreateView): def get_form(self): """Create a form to upload a new CategoryParameterTemplate + - Hide the 'category' field (parent part) - Display parameter templates which are not yet related """ @@ -1102,10 +1101,10 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): def get_form(self): """Create a form to upload a new CategoryParameterTemplate + - Hide the 'category' field (parent part) - Display parameter templates which are not yet related """ - form = super().get_form() form.fields['category'].widget = HiddenInput() diff --git a/InvenTree/plugin/base/action/mixins.py b/InvenTree/plugin/base/action/mixins.py index bc7427b3d2..2d76dd1035 100644 --- a/InvenTree/plugin/base/action/mixins.py +++ b/InvenTree/plugin/base/action/mixins.py @@ -30,7 +30,6 @@ class ActionMixin: def get_result(self, user=None, data=None): """Result of the action?""" - # Re-implement this for cutsom actions return False diff --git a/InvenTree/plugin/base/barcodes/mixins.py b/InvenTree/plugin/base/barcodes/mixins.py index ce87f8163f..d11f0ace59 100644 --- a/InvenTree/plugin/base/barcodes/mixins.py +++ b/InvenTree/plugin/base/barcodes/mixins.py @@ -64,8 +64,7 @@ class BarcodeMixin: return None # pragma: no cover def getStockItemByHash(self): - """Attempt to retrieve a StockItem associated with this barcode, based on the barcode hash. - """ + """Attempt to retrieve a StockItem associated with this barcode, based on the barcode hash.""" try: item = StockItem.objects.get(uid=self.hash()) return item diff --git a/InvenTree/plugin/base/integration/mixins.py b/InvenTree/plugin/base/integration/mixins.py index 7cf24a1ea3..88a450da69 100644 --- a/InvenTree/plugin/base/integration/mixins.py +++ b/InvenTree/plugin/base/integration/mixins.py @@ -481,6 +481,7 @@ class PanelMixin: def get_panel_context(self, view, request, context): """Build the context data to be used for template rendering. + Custom class can override this to provide any custom context data. (See the example in "custom_panel_sample.py") diff --git a/InvenTree/plugin/base/integration/test_mixins.py b/InvenTree/plugin/base/integration/test_mixins.py index b3eb9f4418..722a71ad8f 100644 --- a/InvenTree/plugin/base/integration/test_mixins.py +++ b/InvenTree/plugin/base/integration/test_mixins.py @@ -301,9 +301,7 @@ class PanelMixinTests(InvenTreeTestCase): self.assertNotIn('Custom Part Panel', str(response.content)) def test_enabled(self): - """ - Test that the panels *do* load if the plugin is enabled - """ + """Test that the panels *do* load if the plugin is enabled""" plugin = registry.get_plugin('samplepanel') self.assertEqual(len(registry.with_mixin('panel', active=True)), 0) diff --git a/InvenTree/plugin/base/locate/mixins.py b/InvenTree/plugin/base/locate/mixins.py index 64de70c5e8..7ffec7c82c 100644 --- a/InvenTree/plugin/base/locate/mixins.py +++ b/InvenTree/plugin/base/locate/mixins.py @@ -8,8 +8,7 @@ logger = logging.getLogger('inventree') class LocateMixin: - """Mixin class which provides support for 'locating' inventory items, - for example identifying the location of a particular StockLocation. + """Mixin class which provides support for 'locating' inventory items, for example identifying the location of a particular StockLocation. Plugins could implement audible or visual cues to direct attention to the location, with (for e.g.) LED strips or buzzers, or some other method. @@ -44,7 +43,6 @@ class LocateMixin: Note: A custom implemenation could always change this behaviour """ - logger.info(f"LocateMixin: Attempting to locate StockItem pk={item_pk}") from stock.models import StockItem diff --git a/InvenTree/plugin/builtin/barcodes/inventree_barcode.py b/InvenTree/plugin/builtin/barcodes/inventree_barcode.py index bbc2d29c68..a5c8cdb607 100644 --- a/InvenTree/plugin/builtin/barcodes/inventree_barcode.py +++ b/InvenTree/plugin/builtin/barcodes/inventree_barcode.py @@ -1,5 +1,4 @@ -""" -The InvenTreeBarcodePlugin validates barcodes generated by InvenTree itself. +"""The InvenTreeBarcodePlugin validates barcodes generated by InvenTree itself. It can be used as a template for developing third-party barcode plugins. The data format is very simple, and maps directly to database objects, @@ -9,8 +8,6 @@ Parsing an InvenTree barcode simply involves validating that the references model objects actually exist in the database. """ -# -*- coding: utf-8 -*- - import json from rest_framework.exceptions import ValidationError @@ -26,16 +23,13 @@ class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin): NAME = "InvenTreeBarcode" def validate(self): - """ - An "InvenTree" barcode must be a jsonnable-dict with the following tags: + """An "InvenTree" barcode must be a jsonnable-dict with the following tags: { 'tool': 'InvenTree', 'version': } - """ - # The data must either be dict or be able to dictified if type(self.data) is dict: pass diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index a5222a3d3e..46aed598c6 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -12,8 +12,7 @@ from plugin import InvenTreePlugin, registry class MetadataMixin(models.Model): - """Model mixin class which adds a JSON metadata field to a model, - for use by any (and all) plugins. + """Model mixin class which adds a JSON metadata field to a model, for use by any (and all) plugins. The intent of this mixin is to provide a metadata field on a model instance, for plugins to read / modify as required, to store any extra information. @@ -168,8 +167,7 @@ class PluginSetting(common.models.BaseInvenTreeSetting): @classmethod def get_setting_definition(cls, key, **kwargs): - """In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS', - which is a dict object that fully defines all the setting parameters. + """In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS', which is a dict object that fully defines all the setting parameters. Here, unlike the BaseInvenTreeSetting, we do not know the definitions of all settings 'ahead of time' (as they are defined externally in the plugins). @@ -179,7 +177,6 @@ class PluginSetting(common.models.BaseInvenTreeSetting): If not provided, we'll look at the plugin registry to see what settings are available, (if the plugin is specified!) """ - if 'settings' not in kwargs: plugin = kwargs.pop('plugin', None) diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py index a22ad156a3..38bb397c82 100644 --- a/InvenTree/plugin/plugin.py +++ b/InvenTree/plugin/plugin.py @@ -64,7 +64,8 @@ class MetaBase: def plugin_slug(self): """Slug of plugin - If not set plugin name slugified""" + If not set plugin name slugified + """ slug = self.get_meta_value('SLUG', 'PLUGIN_SLUG', None) if not slug: slug = self.plugin_name() diff --git a/InvenTree/plugin/samples/event/event_sample.py b/InvenTree/plugin/samples/event/event_sample.py index 6dc7e258f7..f62552803e 100644 --- a/InvenTree/plugin/samples/event/event_sample.py +++ b/InvenTree/plugin/samples/event/event_sample.py @@ -17,7 +17,6 @@ class EventPluginSample(EventMixin, InvenTreePlugin): def process_event(self, event, *args, **kwargs): """Custom event processing""" - print(f"Processing triggered event: '{event}'") print("args:", str(args)) print("kwargs:", str(kwargs)) diff --git a/InvenTree/plugin/samples/integration/broken_file.py b/InvenTree/plugin/samples/integration/broken_file.py index 52c6005771..25e51cac6d 100644 --- a/InvenTree/plugin/samples/integration/broken_file.py +++ b/InvenTree/plugin/samples/integration/broken_file.py @@ -1,11 +1,10 @@ -"""sample of a broken python file that will be ignored on import""" +"""Sample of a broken python file that will be ignored on import""" + from plugin import InvenTreePlugin class BrokenFileIntegrationPlugin(InvenTreePlugin): - """ - An very broken plugin - """ + """An very broken plugin""" aaa = bb # noqa: F821 diff --git a/InvenTree/plugin/samples/integration/custom_panel_sample.py b/InvenTree/plugin/samples/integration/custom_panel_sample.py index bea8413b04..7f4be55298 100644 --- a/InvenTree/plugin/samples/integration/custom_panel_sample.py +++ b/InvenTree/plugin/samples/integration/custom_panel_sample.py @@ -48,7 +48,6 @@ class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin): - Only for a specific instance (e.g. part) - Based on the user viewing the page! """ - panels = [ { # Simple panel without any actual content diff --git a/InvenTree/plugin/samples/integration/test_api_caller.py b/InvenTree/plugin/samples/integration/test_api_caller.py index 8f478b88f8..e0d07931e8 100644 --- a/InvenTree/plugin/samples/integration/test_api_caller.py +++ b/InvenTree/plugin/samples/integration/test_api_caller.py @@ -6,7 +6,7 @@ from plugin import registry class SampleApiCallerPluginTests(TestCase): - """Tests for SampleApiCallerPluginTests """ + """Tests for SampleApiCallerPluginTests""" def test_return(self): """Check if the external api call works""" diff --git a/InvenTree/plugin/views.py b/InvenTree/plugin/views.py index 01cf806b8e..50e06fc4f6 100644 --- a/InvenTree/plugin/views.py +++ b/InvenTree/plugin/views.py @@ -13,8 +13,7 @@ logger = logging.getLogger('inventree') class InvenTreePluginViewMixin: - """Custom view mixin which adds context data to the view, - based on loaded plugins. + """Custom view mixin which adds context data to the view, based on loaded plugins. This allows rendered pages to be augmented by loaded plugins. """ diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index fee23e7136..f0581e9651 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1730,6 +1730,7 @@ class StockItem(MetadataMixin, MPTTModel): def testResultMap(self, **kwargs): """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. diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index 902da70fd9..4aab89399f 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -154,7 +154,9 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover filter_horizontal = ['permissions'] def save_model(self, request, obj, form, change): - """This method serves two purposes: + """Save overwrite. + + This method serves two purposes: - show warning message whenever the group users belong to multiple groups - skip saving of the group instance model as inlines needs to be saved before. """ diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py index 64edc6dc64..a12a3acc12 100644 --- a/InvenTree/users/api.py +++ b/InvenTree/users/api.py @@ -31,7 +31,6 @@ class OwnerList(generics.ListAPIView): It is not necessarily "efficient" to do it this way, but until we determine a better way, this is what we have... """ - search_term = str(self.request.query_params.get('search', '')).lower() queryset = super().filter_queryset(queryset) @@ -58,8 +57,7 @@ class OwnerDetail(generics.RetrieveAPIView): class RoleDetails(APIView): - """API endpoint which lists the available role permissions - for the current user + """API endpoint which lists the available role permissions for the current user (Requires authentication) """ diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 2525b3f391..9a2975917b 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import logging from django.contrib.auth import get_user_model @@ -20,8 +18,7 @@ logger = logging.getLogger("inventree") class RuleSet(models.Model): - """A RuleSet is somewhat like a superset of the django permission class, - in that in encapsulates a bunch of permissions. + """A RuleSet is somewhat like a superset of the django permission class, in that in encapsulates a bunch of permissions. There are *many* apps models used within InvenTree, so it makes sense to group them into "roles". @@ -317,8 +314,7 @@ 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. + """Iterates through all of the RuleSets associated with the group, and ensures that the correct permissions are either applied or removed from the group. This function is called under the following conditions: @@ -354,7 +350,7 @@ def update_group_roles(group, debug=False): def add_model(name, action, allowed): """Add a new model to the pile: - args: + Args: name - The name of the model e.g. part_part action - The permission action e.g. view allowed - Whether or not the action is allowed @@ -477,9 +473,8 @@ def update_group_roles(group, debug=False): @receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets') def create_missing_rule_sets(sender, instance, **kwargs): """Called *after* a Group object is saved. - As the linked RuleSet instances are saved *before* the Group, - then we can now use these RuleSet values to update the - group permissions. + + As the linked RuleSet instances are saved *before* the Group, then we can now use these RuleSet values to update the group permissions. """ update_group_roles(instance) @@ -516,6 +511,7 @@ def check_user_role(user, role, permission): class Owner(models.Model): """The Owner class is a proxy for a Group or User instance. + Owner can be associated to any InvenTree model (part, stock, build, etc.) owner_type: Model type (Group or User)