fix docstrings 10

This commit is contained in:
Matthias 2022-05-28 03:39:01 +02:00
parent 6b4df40117
commit 60f13ad2e8
No known key found for this signature in database
GPG Key ID: AB6D0E6C4CB65093
27 changed files with 131 additions and 360 deletions

View File

@ -1,6 +1,4 @@
""" """JSON serializers for Part app"""
JSON serializers for Part app
"""
import imghdr import imghdr
from decimal import Decimal from decimal import Decimal
@ -44,9 +42,7 @@ class CategorySerializer(InvenTreeModelSerializer):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def get_starred(self, category): 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', []) return category in self.context.get('starred_categories', [])
@ -76,9 +72,7 @@ class CategorySerializer(InvenTreeModelSerializer):
class CategoryTree(InvenTreeModelSerializer): class CategoryTree(InvenTreeModelSerializer):
""" """Serializer for PartCategory tree"""
Serializer for PartCategory tree
"""
class Meta: class Meta:
model = PartCategory model = PartCategory
@ -90,9 +84,7 @@ class CategoryTree(InvenTreeModelSerializer):
class PartAttachmentSerializer(InvenTreeAttachmentSerializer): class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
""" """Serializer for the PartAttachment class"""
Serializer for the PartAttachment class
"""
class Meta: class Meta:
model = PartAttachment model = PartAttachment
@ -113,9 +105,7 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
class PartTestTemplateSerializer(InvenTreeModelSerializer): class PartTestTemplateSerializer(InvenTreeModelSerializer):
""" """Serializer for the PartTestTemplate class"""
Serializer for the PartTestTemplate class
"""
key = serializers.CharField(read_only=True) key = serializers.CharField(read_only=True)
@ -135,9 +125,7 @@ class PartTestTemplateSerializer(InvenTreeModelSerializer):
class PartSalePriceSerializer(InvenTreeModelSerializer): class PartSalePriceSerializer(InvenTreeModelSerializer):
""" """Serializer for sale prices for Part model."""
Serializer for sale prices for Part model.
"""
quantity = InvenTreeDecimalField() quantity = InvenTreeDecimalField()
@ -167,9 +155,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
class PartInternalPriceSerializer(InvenTreeModelSerializer): class PartInternalPriceSerializer(InvenTreeModelSerializer):
""" """Serializer for internal prices for Part model."""
Serializer for internal prices for Part model.
"""
quantity = InvenTreeDecimalField() quantity = InvenTreeDecimalField()
@ -199,8 +185,8 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer):
class PartThumbSerializer(serializers.Serializer): 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. Used to serve and display existing Part images.
""" """
@ -212,9 +198,7 @@ class PartThumbSerializerUpdate(InvenTreeModelSerializer):
"""Serializer for updating Part thumbnail""" """Serializer for updating Part thumbnail"""
def validate_image(self, value): def validate_image(self, value):
""" """Check that file is an image."""
Check that file is an image.
"""
validate = imghdr.what(value) validate = imghdr.what(value)
if not validate: if not validate:
raise serializers.ValidationError("File is not an image") raise serializers.ValidationError("File is not an image")
@ -289,6 +273,7 @@ class PartBriefSerializer(InvenTreeModelSerializer):
class PartSerializer(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. Used when displaying all details of a single component.
""" """
@ -296,11 +281,7 @@ class PartSerializer(InvenTreeModelSerializer):
return reverse_lazy('api-part-list') return reverse_lazy('api-part-list')
def __init__(self, *args, **kwargs): 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', []) self.starred_parts = kwargs.pop('starred_parts', [])
category_detail = kwargs.pop('category_detail', False) category_detail = kwargs.pop('category_detail', False)
@ -317,12 +298,10 @@ class PartSerializer(InvenTreeModelSerializer):
@staticmethod @staticmethod
def annotate_queryset(queryset): def annotate_queryset(queryset):
""" """Add some extra annotations to the queryset.
Add some extra annotations to the queryset,
performing database queries as efficiently as possible,
to reduce database trips.
"""
Performing database queries as efficiently as possible, to reduce database trips.
"""
# Annotate with the total 'in stock' quantity # Annotate with the total 'in stock' quantity
queryset = queryset.annotate( queryset = queryset.annotate(
in_stock=Coalesce( in_stock=Coalesce(
@ -444,10 +423,7 @@ class PartSerializer(InvenTreeModelSerializer):
return queryset return queryset
def get_starred(self, part): 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 return part in self.starred_parts
# Extra detail for the category # Extra detail for the category
@ -522,9 +498,7 @@ class PartSerializer(InvenTreeModelSerializer):
class PartRelationSerializer(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_1_detail = PartSerializer(source='part_1', read_only=True, many=False)
part_2_detail = PartSerializer(source='part_2', read_only=True, many=False) part_2_detail = PartSerializer(source='part_2', read_only=True, many=False)
@ -558,9 +532,7 @@ class PartStarSerializer(InvenTreeModelSerializer):
class BomItemSubstituteSerializer(InvenTreeModelSerializer): class BomItemSubstituteSerializer(InvenTreeModelSerializer):
""" """Serializer for the BomItemSubstitute class"""
Serializer for the BomItemSubstitute class
"""
part_detail = PartBriefSerializer(source='part', read_only=True, many=False) part_detail = PartBriefSerializer(source='part', read_only=True, many=False)
@ -575,9 +547,7 @@ class BomItemSubstituteSerializer(InvenTreeModelSerializer):
class BomItemSerializer(InvenTreeModelSerializer): class BomItemSerializer(InvenTreeModelSerializer):
""" """Serializer for BomItem object"""
Serializer for BomItem object
"""
price_range = serializers.CharField(read_only=True) price_range = serializers.CharField(read_only=True)
@ -663,8 +633,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
@staticmethod @staticmethod
def annotate_queryset(queryset): def annotate_queryset(queryset):
""" """Annotate the BomItem queryset with extra information:
Annotate the BomItem queryset with extra information:
Annotations: Annotations:
available_stock: The amount of stock available for the sub_part Part object 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: Construct an "available stock" quantity:
available_stock = total_stock - build_order_allocations - sales_order_allocations available_stock = total_stock - build_order_allocations - sales_order_allocations
""" """
build_order_filter = Q(build__status__in=BuildStatus.ACTIVE_CODES) build_order_filter = Q(build__status__in=BuildStatus.ACTIVE_CODES)
sales_order_filter = Q( sales_order_filter = Q(
line__order__status__in=SalesOrderStatus.OPEN, line__order__status__in=SalesOrderStatus.OPEN,
@ -800,7 +768,6 @@ class BomItemSerializer(InvenTreeModelSerializer):
def get_purchase_price_range(self, obj): def get_purchase_price_range(self, obj):
"""Return purchase price range""" """Return purchase price range"""
try: try:
purchase_price_min = obj.purchase_price_min purchase_price_min = obj.purchase_price_min
except AttributeError: except AttributeError:
@ -831,7 +798,6 @@ class BomItemSerializer(InvenTreeModelSerializer):
def get_purchase_price_avg(self, obj): def get_purchase_price_avg(self, obj):
"""Return purchase price average""" """Return purchase price average"""
try: try:
purchase_price_avg = obj.purchase_price_avg purchase_price_avg = obj.purchase_price_avg
except AttributeError: except AttributeError:
@ -896,9 +862,7 @@ class CategoryParameterTemplateSerializer(InvenTreeModelSerializer):
class PartCopyBOMSerializer(serializers.Serializer): class PartCopyBOMSerializer(serializers.Serializer):
""" """Serializer for copying a BOM from another part"""
Serializer for copying a BOM from another part
"""
class Meta: class Meta:
fields = [ fields = [
@ -919,10 +883,7 @@ class PartCopyBOMSerializer(serializers.Serializer):
) )
def validate_part(self, part): def validate_part(self, part):
""" """Check that a 'valid' part was selected"""
Check that a 'valid' part was selected
"""
return part return part
remove_existing = serializers.BooleanField( remove_existing = serializers.BooleanField(
@ -950,10 +911,7 @@ class PartCopyBOMSerializer(serializers.Serializer):
) )
def save(self): def save(self):
""" """Actually duplicate the BOM"""
Actually duplicate the BOM
"""
base_part = self.context['part'] base_part = self.context['part']
data = self.validated_data data = self.validated_data
@ -968,9 +926,7 @@ class PartCopyBOMSerializer(serializers.Serializer):
class BomImportUploadSerializer(DataFileUploadSerializer): 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 TARGET_MODEL = BomItem
@ -1089,8 +1045,7 @@ class BomImportExtractSerializer(DataFileExtractSerializer):
class BomImportSubmitSerializer(serializers.Serializer): 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 A "BOM" is a set of BomItem objects which are to be validated together as a set
""" """

View File

@ -1,61 +1,38 @@
""" """User-configurable settings for the Part app"""
User-configurable settings for the Part app
"""
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
def part_assembly_default(): 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') return InvenTreeSetting.get_setting('PART_ASSEMBLY')
def part_template_default(): 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') return InvenTreeSetting.get_setting('PART_TEMPLATE')
def part_virtual_default(): 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') return InvenTreeSetting.get_setting('PART_VIRTUAL')
def part_component_default(): 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') return InvenTreeSetting.get_setting('PART_COMPONENT')
def part_purchaseable_default(): 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') return InvenTreeSetting.get_setting('PART_PURCHASEABLE')
def part_salable_default(): 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') return InvenTreeSetting.get_setting('PART_SALABLE')
def part_trackable_default(): 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') return InvenTreeSetting.get_setting('PART_TRACKABLE')

View File

@ -33,12 +33,10 @@ def notify_low_stock(part: part.models.Part):
def notify_low_stock_if_required(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 If true, notify the users who have subscribed to the part
""" """
# Run "up" the tree, to allow notification for "parent" parts # Run "up" the tree, to allow notification for "parent" parts
parts = part.get_ancestors(include_self=True, ascending=True) parts = part.get_ancestors(include_self=True, ascending=True)

View File

@ -277,6 +277,7 @@ def default_currency(*args, **kwargs):
@register.simple_tag() @register.simple_tag()
def setting_object(key, *args, **kwargs): def setting_object(key, *args, **kwargs):
"""Return a setting object speciifed by the given key """Return a setting object speciifed by the given key
(Or return None if the setting does not exist) (Or return None if the setting does not exist)
if a user-setting was requested return that if a user-setting was requested return that
""" """

View File

@ -74,7 +74,6 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
def test_category_metadata(self): def test_category_metadata(self):
"""Test metadata endpoint for the PartCategory""" """Test metadata endpoint for the PartCategory"""
cat = PartCategory.objects.get(pk=1) cat = PartCategory.objects.get(pk=1)
cat.metadata = { cat.metadata = {
@ -95,8 +94,7 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
class PartOptionsAPITest(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! Ensure that the required field details are provided!
""" """
@ -110,10 +108,7 @@ class PartOptionsAPITest(InvenTreeAPITestCase):
super().setUp() super().setUp()
def test_part(self): def test_part(self):
""" """Test the Part API OPTIONS"""
Test the Part API OPTIONS
"""
actions = self.getActions(reverse('api-part-list'))['POST'] actions = self.getActions(reverse('api-part-list'))['POST']
# Check that a bunch o' fields are contained # Check that a bunch o' fields are contained
@ -147,10 +142,7 @@ class PartOptionsAPITest(InvenTreeAPITestCase):
self.assertEqual(category['help_text'], 'Part category') self.assertEqual(category['help_text'], 'Part category')
def test_category(self): 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 = self.getActions(reverse('api-part-category-list'))
# actions should *not* contain 'POST' as we do not have the correct role # 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')) self.assertEqual(loc['api_url'], reverse('api-location-list'))
def test_bom_item(self): 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'] actions = self.getActions(reverse('api-bom-list'))['POST']
inherited = actions['inherited'] inherited = actions['inherited']
@ -195,8 +184,8 @@ class PartOptionsAPITest(InvenTreeAPITestCase):
class PartAPITest(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 Part API
- Tests for PartCategory API - Tests for PartCategory API
""" """
@ -222,11 +211,7 @@ class PartAPITest(InvenTreeAPITestCase):
super().setUp() super().setUp()
def test_get_categories(self): 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') url = reverse('api-part-category-list')
# Request *all* part categories # Request *all* part categories
@ -339,6 +324,7 @@ class PartAPITest(InvenTreeAPITestCase):
def test_include_children(self): 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) If provided, parts are provided for ANY child category (recursive)
""" """
url = reverse('api-part-list') url = reverse('api-part-list')
@ -419,10 +405,7 @@ class PartAPITest(InvenTreeAPITestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_get_thumbs(self): def test_get_thumbs(self):
""" """Return list of part thumbnails"""
Return list of part thumbnails
"""
url = reverse('api-part-thumbs') url = reverse('api-part-thumbs')
response = self.client.get(url) response = self.client.get(url)
@ -430,10 +413,7 @@ class PartAPITest(InvenTreeAPITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_paginate(self): def test_paginate(self):
""" """Test pagination of the Part list API"""
Test pagination of the Part list API
"""
for n in [1, 5, 10]: for n in [1, 5, 10]:
response = self.get(reverse('api-part-list'), {'limit': n}) response = self.get(reverse('api-part-list'), {'limit': n})
@ -445,13 +425,11 @@ class PartAPITest(InvenTreeAPITestCase):
self.assertEqual(len(data['results']), n) self.assertEqual(len(data['results']), n)
def test_default_values(self): def test_default_values(self):
""" """Tests for 'default' values:
Tests for 'default' values:
Ensure that unspecified fields revert to "default" values Ensure that unspecified fields revert to "default" values
(as specified in the model field definition) (as specified in the model field definition)
""" """
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.client.post(url, { response = self.client.post(url, {
@ -498,10 +476,7 @@ class PartAPITest(InvenTreeAPITestCase):
self.assertFalse(response.data['purchaseable']) self.assertFalse(response.data['purchaseable'])
def test_initial_stock(self): def test_initial_stock(self):
""" """Tests for initial stock quantity creation"""
Tests for initial stock quantity creation
"""
url = reverse('api-part-list') url = reverse('api-part-list')
# Track how many parts exist at the start of this test # 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) self.assertEqual(new_part.total_stock, 12345)
def test_initial_supplier_data(self): 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') url = reverse('api-part-list')
n = Part.objects.count() n = Part.objects.count()
@ -620,10 +592,7 @@ class PartAPITest(InvenTreeAPITestCase):
self.assertEqual(new_part.manufacturer_parts.count(), 1) self.assertEqual(new_part.manufacturer_parts.count(), 1)
def test_strange_chars(self): 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') url = reverse('api-part-list')
name = "Kaltgerätestecker" name = "Kaltgerätestecker"
@ -641,15 +610,13 @@ class PartAPITest(InvenTreeAPITestCase):
self.assertEqual(response.data['description'], description) self.assertEqual(response.data['description'], description)
def test_template_filters(self): 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 - variant_of : Return children of specified part
- ancestor : Return descendants of specified part - ancestor : Return descendants of specified part
Uses the 'chair template' part (pk=10000) Uses the 'chair template' part (pk=10000)
""" """
# Rebuild the MPTT structure before running these tests # Rebuild the MPTT structure before running these tests
Part.objects.rebuild() Part.objects.rebuild()
@ -736,7 +703,6 @@ class PartAPITest(InvenTreeAPITestCase):
Unit tests for the 'variant_stock' annotation, Unit tests for the 'variant_stock' annotation,
which provides a stock count for *variant* parts which provides a stock count for *variant* parts
""" """
# Ensure the MPTT structure is in a known state before running tests # Ensure the MPTT structure is in a known state before running tests
Part.objects.rebuild() Part.objects.rebuild()
@ -821,7 +787,6 @@ class PartAPITest(InvenTreeAPITestCase):
def test_part_download(self): def test_part_download(self):
"""Test download of part data via the API""" """Test download of part data via the API"""
url = reverse('api-part-list') url = reverse('api-part-list')
required_cols = [ required_cols = [
@ -873,9 +838,7 @@ class PartAPITest(InvenTreeAPITestCase):
class PartDetailTests(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 = [ fixtures = [
'category', 'category',
@ -970,10 +933,7 @@ class PartDetailTests(InvenTreeAPITestCase):
self.assertEqual(Part.objects.count(), n) self.assertEqual(Part.objects.count(), n)
def test_duplicates(self): 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 # Create a part
response = self.client.post(reverse('api-part-list'), { response = self.client.post(reverse('api-part-list'), {
'name': 'part', 'name': 'part',
@ -1049,10 +1009,7 @@ class PartDetailTests(InvenTreeAPITestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_image_upload(self): 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') self.assignRole('part.add')
# Create a new part # Create a new part
@ -1120,10 +1077,7 @@ class PartDetailTests(InvenTreeAPITestCase):
self.assertIsNotNone(p.image) self.assertIsNotNone(p.image)
def test_details(self): def test_details(self):
""" """Test that the required details are available"""
Test that the required details are available
"""
p = Part.objects.get(pk=1) p = Part.objects.get(pk=1)
url = reverse('api-part-detail', kwargs={'pk': 1}) url = reverse('api-part-detail', kwargs={'pk': 1})
@ -1152,10 +1106,7 @@ class PartDetailTests(InvenTreeAPITestCase):
self.assertEqual(data['unallocated_stock'], 9000) self.assertEqual(data['unallocated_stock'], 9000)
def test_part_metadata(self): 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}) url = reverse('api-part-metadata', kwargs={'pk': 1})
part = Part.objects.get(pk=1) part = Part.objects.get(pk=1)
@ -1206,9 +1157,7 @@ class PartDetailTests(InvenTreeAPITestCase):
class PartAPIAggregationTest(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 = [ fixtures = [
'category', 'category',
@ -1267,10 +1216,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
self.assertTrue(False) # pragma: no cover self.assertTrue(False) # pragma: no cover
def test_stock_quantity(self): def test_stock_quantity(self):
""" """Simple test for the stock quantity"""
Simple test for the stock quantity
"""
data = self.get_part_data() data = self.get_part_data()
self.assertEqual(data['in_stock'], 600) self.assertEqual(data['in_stock'], 600)
@ -1290,11 +1236,10 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
self.assertEqual(data['stock_item_count'], 105) self.assertEqual(data['stock_item_count'], 105)
def test_allocation_annotations(self): 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 Ref: https://github.com/inventree/InvenTree/pull/2797
""" """
# We are looking at Part ID 100 ("Bob") # We are looking at Part ID 100 ("Bob")
url = reverse('api-part-detail', kwargs={'pk': 100}) url = reverse('api-part-detail', kwargs={'pk': 100})
@ -1438,9 +1383,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
class BomItemTest(InvenTreeAPITestCase): class BomItemTest(InvenTreeAPITestCase):
""" """Unit tests for the BomItem API"""
Unit tests for the BomItem API
"""
fixtures = [ fixtures = [
'category', 'category',
@ -1461,10 +1404,7 @@ class BomItemTest(InvenTreeAPITestCase):
super().setUp() super().setUp()
def test_bom_list(self): 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? # How many BOM items currently exist in the database?
n = BomItem.objects.count() n = BomItem.objects.count()
@ -1529,10 +1469,7 @@ class BomItemTest(InvenTreeAPITestCase):
self.assertTrue(key in el) self.assertTrue(key in el)
def test_get_bom_detail(self): 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}) url = reverse('api-bom-item-detail', kwargs={'pk': 3})
response = self.get(url, expected_code=200) response = self.get(url, expected_code=200)
@ -1570,10 +1507,7 @@ class BomItemTest(InvenTreeAPITestCase):
self.assertEqual(response.data['note'], 'Added a note') self.assertEqual(response.data['note'], 'Added a note')
def test_add_bom_item(self): 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') url = reverse('api-bom-list')
data = { data = {
@ -1590,10 +1524,7 @@ class BomItemTest(InvenTreeAPITestCase):
self.client.post(url, data, expected_code=400) self.client.post(url, data, expected_code=400)
def test_variants(self): def test_variants(self):
""" """Tests for BomItem use with variants"""
Tests for BomItem use with variants
"""
stock_url = reverse('api-stock-list') stock_url = reverse('api-stock-list')
# BOM item we are interested in # BOM item we are interested in
@ -1675,10 +1606,7 @@ class BomItemTest(InvenTreeAPITestCase):
self.assertEqual(len(response.data), 2) self.assertEqual(len(response.data), 2)
def test_substitutes(self): def test_substitutes(self):
""" """Tests for BomItem substitutes"""
Tests for BomItem substitutes
"""
url = reverse('api-bom-substitute-list') url = reverse('api-bom-substitute-list')
stock_url = reverse('api-stock-list') stock_url = reverse('api-stock-list')
@ -1760,10 +1688,7 @@ class BomItemTest(InvenTreeAPITestCase):
self.assertEqual(data['available_stock'], 9000) self.assertEqual(data['available_stock'], 9000)
def test_bom_item_uses(self): def test_bom_item_uses(self):
""" """Tests for the 'uses' field"""
Tests for the 'uses' field
"""
url = reverse('api-bom-list') url = reverse('api-bom-list')
# Test that the direct 'sub_part' association works # Test that the direct 'sub_part' association works
@ -1813,10 +1738,7 @@ class BomItemTest(InvenTreeAPITestCase):
self.assertEqual(len(response.data), i) self.assertEqual(len(response.data), i)
def test_bom_variant_stock(self): def test_bom_variant_stock(self):
""" """Test for 'available_variant_stock' annotation"""
Test for 'available_variant_stock' annotation
"""
Part.objects.rebuild() Part.objects.rebuild()
# BOM item we are interested in # BOM item we are interested in
@ -1852,10 +1774,7 @@ class BomItemTest(InvenTreeAPITestCase):
class PartParameterTest(InvenTreeAPITestCase): class PartParameterTest(InvenTreeAPITestCase):
""" """Tests for the ParParameter API"""
Tests for the ParParameter API
"""
superuser = True superuser = True
fixtures = [ fixtures = [
@ -1870,10 +1789,7 @@ class PartParameterTest(InvenTreeAPITestCase):
super().setUp() super().setUp()
def test_list_params(self): def test_list_params(self):
""" """Test for listing part parameters"""
Test for listing part parameters
"""
url = reverse('api-part-parameter-list') url = reverse('api-part-parameter-list')
response = self.client.get(url, format='json') response = self.client.get(url, format='json')
@ -1903,10 +1819,7 @@ class PartParameterTest(InvenTreeAPITestCase):
self.assertEqual(len(response.data), 3) self.assertEqual(len(response.data), 3)
def test_create_param(self): 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') url = reverse('api-part-parameter-list')
response = self.client.post( response = self.client.post(
@ -1925,10 +1838,7 @@ class PartParameterTest(InvenTreeAPITestCase):
self.assertEqual(len(response.data), 6) self.assertEqual(len(response.data), 6)
def test_param_detail(self): 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}) url = reverse('api-part-parameter-detail', kwargs={'pk': 5})
response = self.client.get(url) response = self.client.get(url)

View File

@ -1,6 +1,4 @@
""" """Unit testing for BOM export functionality"""
Unit testing for BOM export functionality
"""
import csv import csv
@ -26,10 +24,7 @@ class BomExportTest(InvenTreeTestCase):
self.url = reverse('bom-download', kwargs={'pk': 100}) self.url = reverse('bom-download', kwargs={'pk': 100})
def test_bom_template(self): 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') url = reverse('bom-upload-template')
# Download an XLS template # Download an XLS template
@ -78,10 +73,7 @@ class BomExportTest(InvenTreeTestCase):
self.assertTrue(header in headers) self.assertTrue(header in headers)
def test_export_csv(self): def test_export_csv(self):
""" """Test BOM download in CSV format"""
Test BOM download in CSV format
"""
params = { params = {
'format': 'csv', 'format': 'csv',
'cascade': True, 'cascade': True,
@ -142,10 +134,7 @@ class BomExportTest(InvenTreeTestCase):
self.assertTrue(header in expected) self.assertTrue(header in expected)
def test_export_xls(self): def test_export_xls(self):
""" """Test BOM download in XLS format"""
Test BOM download in XLS format
"""
params = { params = {
'format': 'xls', 'format': 'xls',
'cascade': True, 'cascade': True,
@ -163,10 +152,7 @@ class BomExportTest(InvenTreeTestCase):
self.assertEqual(content, 'attachment; filename="BOB | Bob | A2_BOM.xls"') self.assertEqual(content, 'attachment; filename="BOB | Bob | A2_BOM.xls"')
def test_export_xlsx(self): def test_export_xlsx(self):
""" """Test BOM download in XLSX format"""
Test BOM download in XLSX format
"""
params = { params = {
'format': 'xlsx', 'format': 'xlsx',
'cascade': True, 'cascade': True,
@ -181,10 +167,7 @@ class BomExportTest(InvenTreeTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_export_json(self): def test_export_json(self):
""" """Test BOM download in JSON format"""
Test BOM download in JSON format
"""
params = { params = {
'format': 'json', 'format': 'json',
'cascade': True, 'cascade': True,

View File

@ -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.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse from django.urls import reverse
@ -12,9 +10,7 @@ from part.models import Part
class BomUploadTest(InvenTreeAPITestCase): class BomUploadTest(InvenTreeAPITestCase):
""" """Test BOM file upload API endpoint"""
Test BOM file upload API endpoint
"""
roles = [ roles = [
'part.add', 'part.add',
@ -63,10 +59,7 @@ class BomUploadTest(InvenTreeAPITestCase):
return response return response
def test_missing_file(self): def test_missing_file(self):
""" """POST without a file"""
POST without a file
"""
response = self.post( response = self.post(
reverse('api-bom-import-upload'), reverse('api-bom-import-upload'),
data={}, data={},
@ -76,10 +69,7 @@ class BomUploadTest(InvenTreeAPITestCase):
self.assertIn('No file was submitted', str(response.data['data_file'])) self.assertIn('No file was submitted', str(response.data['data_file']))
def test_unsupported_file(self): def test_unsupported_file(self):
""" """POST with an unsupported file type"""
POST with an unsupported file type
"""
response = self.post_bom( response = self.post_bom(
'sample.txt', 'sample.txt',
b'hello world', b'hello world',
@ -89,10 +79,7 @@ class BomUploadTest(InvenTreeAPITestCase):
self.assertIn('Unsupported file type', str(response.data['data_file'])) self.assertIn('Unsupported file type', str(response.data['data_file']))
def test_broken_file(self): def test_broken_file(self):
""" """Test upload with broken (corrupted) files"""
Test upload with broken (corrupted) files
"""
response = self.post_bom( response = self.post_bom(
'sample.csv', 'sample.csv',
b'', b'',
@ -111,10 +98,7 @@ class BomUploadTest(InvenTreeAPITestCase):
self.assertIn('Unsupported format, or corrupt file', str(response.data['data_file'])) self.assertIn('Unsupported format, or corrupt file', str(response.data['data_file']))
def test_missing_rows(self): 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 = tablib.Dataset()
dataset.headers = [ dataset.headers = [
@ -142,10 +126,7 @@ class BomUploadTest(InvenTreeAPITestCase):
self.assertIn('No data rows found in file', str(response.data)) self.assertIn('No data rows found in file', str(response.data))
def test_missing_columns(self): 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') url = reverse('api-bom-import-extract')
rows = [ rows = [
@ -195,10 +176,7 @@ class BomUploadTest(InvenTreeAPITestCase):
) )
def test_invalid_data(self): def test_invalid_data(self):
""" """Upload data which contains errors"""
Upload data which contains errors
"""
dataset = tablib.Dataset() dataset = tablib.Dataset()
# Only these headers are strictly necessary # 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') self.assertEqual(rows[5]['data']['errors']['part'], 'Part is not designated as a component')
def test_part_guess(self): 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() dataset = tablib.Dataset()
# Should be able to 'guess' the part from the name # 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) self.assertEqual(rows[idx]['data']['part'], components[idx].pk)
def test_levels(self): 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') url = reverse('api-bom-import-extract')
dataset = tablib.Dataset() dataset = tablib.Dataset()

View File

@ -326,10 +326,7 @@ class PartSettingsTest(InvenTreeTestCase):
""" """
def make_part(self): def make_part(self):
""" """Helper function to create a simple part"""
Helper function to create a simple part
"""
part = Part.objects.create( part = Part.objects.create(
name='Test Part', name='Test Part',
description='I am but a humble test part', description='I am but a humble test part',

View File

@ -58,6 +58,7 @@ class PartDetailTest(PartViewTestCase):
def test_part_detail_from_ipn(self): def test_part_detail_from_ipn(self):
"""Test that we can retrieve a part detail page from part IPN: """Test that we can retrieve a part detail page from part IPN:
- if no part with matching IPN -> return part index - if no part with matching IPN -> return part index
- if unique IPN match -> return part detail page - if unique IPN match -> return part detail page
- if multiple IPN matches -> return part index - if multiple IPN matches -> return part index
@ -107,7 +108,6 @@ class PartDetailTest(PartViewTestCase):
def test_bom_download(self): def test_bom_download(self):
"""Test downloading a BOM for a valid part""" """Test downloading a BOM for a valid part"""
response = self.client.get(reverse('bom-download', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.get(reverse('bom-download', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn('streaming_content', dir(response)) self.assertIn('streaming_content', dir(response))
@ -141,7 +141,6 @@ class CategoryTest(PartViewTestCase):
def test_set_category(self): def test_set_category(self):
"""Test that the "SetCategory" view works""" """Test that the "SetCategory" view works"""
url = reverse('part-set-category') url = reverse('part-set-category')
response = self.client.get(url, {'parts[]': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.get(url, {'parts[]': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

View File

@ -78,7 +78,6 @@ class PartSetCategory(AjaxUpdateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Respond to a GET request to this view""" """Respond to a GET request to this view"""
self.request = request self.request = request
if 'parts[]' in request.GET: if 'parts[]' in request.GET:
@ -90,7 +89,6 @@ class PartSetCategory(AjaxUpdateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""Respond to a POST request to this view""" """Respond to a POST request to this view"""
self.parts = [] self.parts = []
for item in request.POST: for item in request.POST:
@ -1009,6 +1007,7 @@ class CategoryParameterTemplateCreate(AjaxCreateView):
def get_form(self): def get_form(self):
"""Create a form to upload a new CategoryParameterTemplate """Create a form to upload a new CategoryParameterTemplate
- Hide the 'category' field (parent part) - Hide the 'category' field (parent part)
- Display parameter templates which are not yet related - Display parameter templates which are not yet related
""" """
@ -1102,10 +1101,10 @@ class CategoryParameterTemplateEdit(AjaxUpdateView):
def get_form(self): def get_form(self):
"""Create a form to upload a new CategoryParameterTemplate """Create a form to upload a new CategoryParameterTemplate
- Hide the 'category' field (parent part) - Hide the 'category' field (parent part)
- Display parameter templates which are not yet related - Display parameter templates which are not yet related
""" """
form = super().get_form() form = super().get_form()
form.fields['category'].widget = HiddenInput() form.fields['category'].widget = HiddenInput()

View File

@ -30,7 +30,6 @@ class ActionMixin:
def get_result(self, user=None, data=None): def get_result(self, user=None, data=None):
"""Result of the action?""" """Result of the action?"""
# Re-implement this for cutsom actions # Re-implement this for cutsom actions
return False return False

View File

@ -64,8 +64,7 @@ class BarcodeMixin:
return None # pragma: no cover return None # pragma: no cover
def getStockItemByHash(self): 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: try:
item = StockItem.objects.get(uid=self.hash()) item = StockItem.objects.get(uid=self.hash())
return item return item

View File

@ -481,6 +481,7 @@ class PanelMixin:
def get_panel_context(self, view, request, context): def get_panel_context(self, view, request, context):
"""Build the context data to be used for template rendering. """Build the context data to be used for template rendering.
Custom class can override this to provide any custom context data. Custom class can override this to provide any custom context data.
(See the example in "custom_panel_sample.py") (See the example in "custom_panel_sample.py")

View File

@ -301,9 +301,7 @@ class PanelMixinTests(InvenTreeTestCase):
self.assertNotIn('Custom Part Panel', str(response.content)) self.assertNotIn('Custom Part Panel', str(response.content))
def test_enabled(self): 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') plugin = registry.get_plugin('samplepanel')
self.assertEqual(len(registry.with_mixin('panel', active=True)), 0) self.assertEqual(len(registry.with_mixin('panel', active=True)), 0)

View File

@ -8,8 +8,7 @@ logger = logging.getLogger('inventree')
class LocateMixin: class LocateMixin:
"""Mixin class which provides support for 'locating' inventory items, """Mixin class which provides support for 'locating' inventory items, for example identifying the location of a particular StockLocation.
for example identifying the location of a particular StockLocation.
Plugins could implement audible or visual cues to direct attention to the location, 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. 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 Note: A custom implemenation could always change this behaviour
""" """
logger.info(f"LocateMixin: Attempting to locate StockItem pk={item_pk}") logger.info(f"LocateMixin: Attempting to locate StockItem pk={item_pk}")
from stock.models import StockItem from stock.models import StockItem

View File

@ -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. 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, 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. references model objects actually exist in the database.
""" """
# -*- coding: utf-8 -*-
import json import json
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
@ -26,16 +23,13 @@ class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):
NAME = "InvenTreeBarcode" NAME = "InvenTreeBarcode"
def validate(self): 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', 'tool': 'InvenTree',
'version': <anything> 'version': <anything>
} }
""" """
# The data must either be dict or be able to dictified # The data must either be dict or be able to dictified
if type(self.data) is dict: if type(self.data) is dict:
pass pass

View File

@ -12,8 +12,7 @@ from plugin import InvenTreePlugin, registry
class MetadataMixin(models.Model): class MetadataMixin(models.Model):
"""Model mixin class which adds a JSON metadata field to a model, """Model mixin class which adds a JSON metadata field to a model, for use by any (and all) plugins.
for use by any (and all) plugins.
The intent of this mixin is to provide a metadata field on a model instance, 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. for plugins to read / modify as required, to store any extra information.
@ -168,8 +167,7 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
@classmethod @classmethod
def get_setting_definition(cls, key, **kwargs): def get_setting_definition(cls, key, **kwargs):
"""In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS', """In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS', which is a dict object that fully defines all the setting parameters.
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 Here, unlike the BaseInvenTreeSetting, we do not know the definitions of all settings
'ahead of time' (as they are defined externally in the plugins). '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 not provided, we'll look at the plugin registry to see what settings are available,
(if the plugin is specified!) (if the plugin is specified!)
""" """
if 'settings' not in kwargs: if 'settings' not in kwargs:
plugin = kwargs.pop('plugin', None) plugin = kwargs.pop('plugin', None)

View File

@ -64,7 +64,8 @@ class MetaBase:
def plugin_slug(self): def plugin_slug(self):
"""Slug of plugin """Slug of plugin
If not set plugin name slugified""" If not set plugin name slugified
"""
slug = self.get_meta_value('SLUG', 'PLUGIN_SLUG', None) slug = self.get_meta_value('SLUG', 'PLUGIN_SLUG', None)
if not slug: if not slug:
slug = self.plugin_name() slug = self.plugin_name()

View File

@ -17,7 +17,6 @@ class EventPluginSample(EventMixin, InvenTreePlugin):
def process_event(self, event, *args, **kwargs): def process_event(self, event, *args, **kwargs):
"""Custom event processing""" """Custom event processing"""
print(f"Processing triggered event: '{event}'") print(f"Processing triggered event: '{event}'")
print("args:", str(args)) print("args:", str(args))
print("kwargs:", str(kwargs)) print("kwargs:", str(kwargs))

View File

@ -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 from plugin import InvenTreePlugin
class BrokenFileIntegrationPlugin(InvenTreePlugin): class BrokenFileIntegrationPlugin(InvenTreePlugin):
""" """An very broken plugin"""
An very broken plugin
"""
aaa = bb # noqa: F821 aaa = bb # noqa: F821

View File

@ -48,7 +48,6 @@ class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin):
- Only for a specific instance (e.g. part) - Only for a specific instance (e.g. part)
- Based on the user viewing the page! - Based on the user viewing the page!
""" """
panels = [ panels = [
{ {
# Simple panel without any actual content # Simple panel without any actual content

View File

@ -13,8 +13,7 @@ logger = logging.getLogger('inventree')
class InvenTreePluginViewMixin: class InvenTreePluginViewMixin:
"""Custom view mixin which adds context data to the view, """Custom view mixin which adds context data to the view, based on loaded plugins.
based on loaded plugins.
This allows rendered pages to be augmented by loaded plugins. This allows rendered pages to be augmented by loaded plugins.
""" """

View File

@ -1730,6 +1730,7 @@ class StockItem(MetadataMixin, MPTTModel):
def testResultMap(self, **kwargs): 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, Where multiple test results exist for a given name,
the *most recent* test is used. the *most recent* test is used.

View File

@ -154,7 +154,9 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover
filter_horizontal = ['permissions'] filter_horizontal = ['permissions']
def save_model(self, request, obj, form, change): 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 - 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. - skip saving of the group instance model as inlines needs to be saved before.
""" """

View File

@ -31,7 +31,6 @@ class OwnerList(generics.ListAPIView):
It is not necessarily "efficient" to do it this way, It is not necessarily "efficient" to do it this way,
but until we determine a better way, this is what we have... but until we determine a better way, this is what we have...
""" """
search_term = str(self.request.query_params.get('search', '')).lower() search_term = str(self.request.query_params.get('search', '')).lower()
queryset = super().filter_queryset(queryset) queryset = super().filter_queryset(queryset)
@ -58,8 +57,7 @@ class OwnerDetail(generics.RetrieveAPIView):
class RoleDetails(APIView): class RoleDetails(APIView):
"""API endpoint which lists the available role permissions """API endpoint which lists the available role permissions for the current user
for the current user
(Requires authentication) (Requires authentication)
""" """

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import logging import logging
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -20,8 +18,7 @@ logger = logging.getLogger("inventree")
class RuleSet(models.Model): class RuleSet(models.Model):
"""A RuleSet is somewhat like a superset of the django permission class, """A RuleSet is somewhat like a superset of the django permission class, in that in encapsulates a bunch of permissions.
in that in encapsulates a bunch of permissions.
There are *many* apps models used within InvenTree, There are *many* apps models used within InvenTree,
so it makes sense to group them into "roles". 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): def update_group_roles(group, debug=False):
"""Iterates through all of the RuleSets associated with 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.
and ensures that the correct permissions are either applied or removed from the group.
This function is called under the following conditions: 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): def add_model(name, action, allowed):
"""Add a new model to the pile: """Add a new model to the pile:
args: Args:
name - The name of the model e.g. part_part name - The name of the model e.g. part_part
action - The permission action e.g. view action - The permission action e.g. view
allowed - Whether or not the action is allowed allowed - Whether or not the action is allowed
@ -477,9 +473,8 @@ def update_group_roles(group, debug=False):
@receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets') @receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets')
def create_missing_rule_sets(sender, instance, **kwargs): def create_missing_rule_sets(sender, instance, **kwargs):
"""Called *after* a Group object is saved. """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 As the linked RuleSet instances are saved *before* the Group, then we can now use these RuleSet values to update the group permissions.
group permissions.
""" """
update_group_roles(instance) update_group_roles(instance)
@ -516,6 +511,7 @@ def check_user_role(user, role, permission):
class Owner(models.Model): class Owner(models.Model):
"""The Owner class is a proxy for a Group or User instance. """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 can be associated to any InvenTree model (part, stock, build, etc.)
owner_type: Model type (Group or User) owner_type: Model type (Group or User)