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
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
"""

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
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')

View File

@ -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)

View File

@ -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
"""

View File

@ -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)

View File

@ -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,

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.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()

View File

@ -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',

View File

@ -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')

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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

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.
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': <anything>
}
"""
# The data must either be dict or be able to dictified
if type(self.data) is dict:
pass

View File

@ -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)

View File

@ -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()

View File

@ -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))

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

View File

@ -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

View File

@ -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"""

View File

@ -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.
"""

View File

@ -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.

View File

@ -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.
"""

View File

@ -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)
"""

View File

@ -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)