mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
fix docstrings 10
This commit is contained in:
parent
6b4df40117
commit
60f13ad2e8
@ -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
|
||||
"""
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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',
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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)
|
||||
"""
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user