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
|
import imghdr
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@ -37,16 +35,14 @@ from .models import (BomItem, BomItemSubstitute, Part, PartAttachment,
|
|||||||
|
|
||||||
|
|
||||||
class CategorySerializer(InvenTreeModelSerializer):
|
class CategorySerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for PartCategory """
|
"""Serializer for PartCategory"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -209,12 +195,10 @@ class PartThumbSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
|
|
||||||
class PartThumbSerializerUpdate(InvenTreeModelSerializer):
|
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")
|
||||||
@ -230,7 +214,7 @@ class PartThumbSerializerUpdate(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
||||||
""" JSON serializer for the PartParameterTemplate model """
|
"""JSON serializer for the PartParameterTemplate model"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PartParameterTemplate
|
model = PartParameterTemplate
|
||||||
@ -242,7 +226,7 @@ class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class PartParameterSerializer(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)
|
template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
|
||||||
|
|
||||||
@ -258,7 +242,7 @@ class PartParameterSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class PartBriefSerializer(InvenTreeModelSerializer):
|
class PartBriefSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for Part (brief detail) """
|
"""Serializer for Part (brief detail)"""
|
||||||
|
|
||||||
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
||||||
|
|
||||||
@ -288,7 +272,8 @@ 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)
|
||||||
@ -541,7 +515,7 @@ class PartRelationSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class PartStarSerializer(InvenTreeModelSerializer):
|
class PartStarSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for a PartStar object """
|
"""Serializer for a PartStar object"""
|
||||||
|
|
||||||
partname = serializers.CharField(source='part.full_name', read_only=True)
|
partname = serializers.CharField(source='part.full_name', read_only=True)
|
||||||
username = serializers.CharField(source='user.username', read_only=True)
|
username = serializers.CharField(source='user.username', read_only=True)
|
||||||
@ -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,
|
||||||
@ -799,8 +767,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
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:
|
||||||
@ -830,8 +797,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
return purchase_price_range
|
return purchase_price_range
|
||||||
|
|
||||||
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:
|
||||||
@ -877,7 +843,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class CategoryParameterTemplateSerializer(InvenTreeModelSerializer):
|
class CategoryParameterTemplateSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for PartCategoryParameterTemplate """
|
"""Serializer for PartCategoryParameterTemplate"""
|
||||||
|
|
||||||
parameter_template = PartParameterTemplateSerializer(many=False,
|
parameter_template = PartParameterTemplateSerializer(many=False,
|
||||||
read_only=True)
|
read_only=True)
|
||||||
@ -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
|
||||||
"""
|
"""
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ def multiply(x, y, *args, **kwargs):
|
|||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def add(x, y, *args, **kwargs):
|
def add(x, y, *args, **kwargs):
|
||||||
""" Add two numbers together """
|
"""Add two numbers together"""
|
||||||
return x + y
|
return x + y
|
||||||
|
|
||||||
|
|
||||||
@ -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
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
@ -271,7 +256,7 @@ class PartAPITest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(len(response.data), 3)
|
self.assertEqual(len(response.data), 3)
|
||||||
|
|
||||||
def test_add_categories(self):
|
def test_add_categories(self):
|
||||||
""" Check that we can add categories """
|
"""Check that we can add categories"""
|
||||||
data = {
|
data = {
|
||||||
'name': 'Animals',
|
'name': 'Animals',
|
||||||
'description': 'All animals go here'
|
'description': 'All animals go here'
|
||||||
@ -338,7 +323,8 @@ class PartAPITest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(part['category'], 2)
|
self.assertEqual(part['category'], 2)
|
||||||
|
|
||||||
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)
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
|
@ -112,7 +112,7 @@ class TemplateTagTest(InvenTreeTestCase):
|
|||||||
|
|
||||||
|
|
||||||
class PartTest(TestCase):
|
class PartTest(TestCase):
|
||||||
""" Tests for the Part model """
|
"""Tests for the Part model"""
|
||||||
|
|
||||||
fixtures = [
|
fixtures = [
|
||||||
'category',
|
'category',
|
||||||
@ -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',
|
||||||
|
@ -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')
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -6,7 +6,7 @@ from plugin import registry
|
|||||||
|
|
||||||
|
|
||||||
class SampleApiCallerPluginTests(TestCase):
|
class SampleApiCallerPluginTests(TestCase):
|
||||||
"""Tests for SampleApiCallerPluginTests """
|
"""Tests for SampleApiCallerPluginTests"""
|
||||||
|
|
||||||
def test_return(self):
|
def test_return(self):
|
||||||
"""Check if the external api call works"""
|
"""Check if the external api call works"""
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
@ -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)
|
||||||
"""
|
"""
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user