From 3ee3cea8d88ad6c278b326b8489a0634a02ea6d1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 5 Oct 2025 18:47:25 +0200 Subject: [PATCH] move filtering of serializer fields out of functions into mixin --- .../InvenTree/InvenTree/serializers.py | 50 +++- src/backend/InvenTree/order/serializers.py | 251 ++++++------------ src/backend/InvenTree/part/serializers.py | 105 +++----- src/backend/InvenTree/stock/serializers.py | 126 ++++----- src/backend/InvenTree/users/serializers.py | 1 + 5 files changed, 229 insertions(+), 304 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py index 59cbb81532..c2fd006c78 100644 --- a/src/backend/InvenTree/InvenTree/serializers.py +++ b/src/backend/InvenTree/InvenTree/serializers.py @@ -28,6 +28,52 @@ from common.currency import currency_code_default, currency_code_mappings from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField +# region path filtering +class OptionalFilterabelSerializer(serializers.Serializer): + """Mixin to add context to serializer.""" + + is_filterable = None + is_filterable_default = None + + def __init__(self, *args, **kwargs): + """Initialize the serializer.""" + self.is_filterable = kwargs.pop('is_filterable', None) + self.is_filterable_default = kwargs.pop('is_filterable_default', True) + super().__init__(*args, **kwargs) + + +class PathScopedMixin: + """Mixin to disable a serializer field based on kwargs passed to the view.""" + + def __init__(self, *args, **kwargs): + """Initialization routine for the serializer.""" + flt_fld = { + k: a for k, a in self.fields.items() if getattr(a, 'is_filterable', None) + } + filter_targets = {k: kwargs.pop(k, False) for k in flt_fld} + + super().__init__(*args, **kwargs) + + if InvenTree.ready.isGeneratingSchema(): + return + + # Throw out fields which are not requested + for k, v in filter_targets.items(): + if v is not True: + self.fields.pop(k, None) + + +# Decorator for marking serialzier fields that can be filtered out +def can_filter(func, default=False): + """Decorator for marking serializer fields as filterable.""" + func._kwargs['is_filterable'] = True + func._kwargs['is_filterable_default'] = default + return func + + +# endregion + + class EmptySerializer(serializers.Serializer): """Empty serializer for use in testing.""" @@ -222,7 +268,9 @@ class DependentField(serializers.Field): return None -class InvenTreeModelSerializer(serializers.ModelSerializer): +class InvenTreeModelSerializer( + OptionalFilterabelSerializer, serializers.ModelSerializer +): """Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation.""" # Switch out URLField mapping diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index 511b843b99..3154ded6f7 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -52,6 +52,8 @@ from InvenTree.serializers import ( InvenTreeModelSerializer, InvenTreeMoneySerializer, NotesFieldMixin, + PathScopedMixin, + can_filter, ) from order.status_codes import ( PurchaseOrderStatusGroups, @@ -276,15 +278,6 @@ class AbstractExtraLineSerializer( ): """Abstract Serializer for a ExtraLine object.""" - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer.""" - order_detail = kwargs.pop('order_detail', False) - - super().__init__(*args, **kwargs) - - if order_detail is not True and not isGeneratingSchema(): - self.fields.pop('order_detail', None) - quantity = serializers.FloatField() price = InvenTreeMoneySerializer(allow_null=True) @@ -343,15 +336,6 @@ class PurchaseOrderSerializer( 'order_currency': {'required': False}, } - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer.""" - supplier_detail = kwargs.pop('supplier_detail', False) - - super().__init__(*args, **kwargs) - - if supplier_detail is not True and not isGeneratingSchema(): - self.fields.pop('supplier_detail', None) - def skip_create_fields(self): """Skip these fields when instantiating a new object.""" fields = super().skip_create_fields() @@ -389,8 +373,10 @@ class PurchaseOrderSerializer( source='supplier.name', read_only=True, label=_('Supplier Name') ) - supplier_detail = CompanyBriefSerializer( - source='supplier', many=False, read_only=True, allow_null=True + supplier_detail = can_filter( + CompanyBriefSerializer( + source='supplier', many=False, read_only=True, allow_null=True + ) ) @@ -522,20 +508,17 @@ class PurchaseOrderLineItemSerializer( def __init__(self, *args, **kwargs): """Initialization routine for the serializer.""" part_detail = kwargs.pop('part_detail', False) - order_detail = kwargs.pop('order_detail', False) super().__init__(*args, **kwargs) if isGeneratingSchema(): return + # TODO INVE-T1 support complex filters if part_detail is not True: self.fields.pop('part_detail', None) self.fields.pop('supplier_part_detail', None) - if order_detail is not True: - self.fields.pop('order_detail', None) - def skip_create_fields(self): """Return a list of fields to skip when creating a new object.""" return ['auto_pricing', 'merge_items', *super().skip_create_fields()] @@ -644,8 +627,10 @@ class PurchaseOrderLineItemSerializer( help_text=_('Purchase price currency') ) - order_detail = PurchaseOrderSerializer( - source='order', read_only=True, allow_null=True, many=False + order_detail = can_filter( + PurchaseOrderSerializer( + source='order', read_only=True, allow_null=True, many=False + ) ) build_order_detail = build.serializers.BuildSerializer( @@ -724,8 +709,10 @@ class PurchaseOrderExtraLineSerializer( ): """Serializer for a PurchaseOrderExtraLine object.""" - order_detail = PurchaseOrderSerializer( - source='order', many=False, read_only=True, allow_null=True + order_detail = can_filter( + PurchaseOrderSerializer( + source='order', many=False, read_only=True, allow_null=True + ) ) class Meta(AbstractExtraLineMeta): @@ -1009,15 +996,6 @@ class SalesOrderSerializer( extra_kwargs = {'order_currency': {'required': False}} - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer.""" - customer_detail = kwargs.pop('customer_detail', False) - - super().__init__(*args, **kwargs) - - if customer_detail is not True and not isGeneratingSchema(): - self.fields.pop('customer_detail', None) - def skip_create_fields(self): """Skip these fields when instantiating a new object.""" fields = super().skip_create_fields() @@ -1058,8 +1036,10 @@ class SalesOrderSerializer( return queryset - customer_detail = CompanyBriefSerializer( - source='customer', many=False, read_only=True, allow_null=True + customer_detail = can_filter( + CompanyBriefSerializer( + source='customer', many=False, read_only=True, allow_null=True + ) ) shipments_count = serializers.IntegerField( @@ -1083,6 +1063,7 @@ class SalesOrderIssueSerializer(OrderAdjustSerializer): class SalesOrderLineItemSerializer( DataImportExportSerializerMixin, AbstractLineItemSerializer, + PathScopedMixin, InvenTreeModelSerializer, ): """Serializer for a SalesOrderLineItem object.""" @@ -1116,29 +1097,6 @@ class SalesOrderLineItemSerializer( 'on_order', ] - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer. - - - Add extra related serializer information if required - """ - part_detail = kwargs.pop('part_detail', False) - order_detail = kwargs.pop('order_detail', False) - customer_detail = kwargs.pop('customer_detail', False) - - super().__init__(*args, **kwargs) - - if isGeneratingSchema(): - return - - if part_detail is not True: - self.fields.pop('part_detail', None) - - if order_detail is not True: - self.fields.pop('order_detail', None) - - if customer_detail is not True: - self.fields.pop('customer_detail', None) - @staticmethod def annotate_queryset(queryset): """Add some extra annotations to this queryset. @@ -1236,14 +1194,18 @@ class SalesOrderLineItemSerializer( return queryset - order_detail = SalesOrderSerializer( - source='order', many=False, read_only=True, allow_null=True + order_detail = can_filter( + SalesOrderSerializer( + source='order', many=False, read_only=True, allow_null=True + ) ) - part_detail = PartBriefSerializer( - source='part', many=False, read_only=True, allow_null=True + part_detail = can_filter( + PartBriefSerializer(source='part', many=False, read_only=True, allow_null=True) ) - customer_detail = CompanyBriefSerializer( - source='order.customer', many=False, read_only=True, allow_null=True + customer_detail = can_filter( + CompanyBriefSerializer( + source='order.customer', many=False, read_only=True, allow_null=True + ) ) # Annotated fields @@ -1291,15 +1253,6 @@ class SalesOrderShipmentSerializer(NotesFieldMixin, InvenTreeModelSerializer): 'notes', ] - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer.""" - order_detail = kwargs.pop('order_detail', True) - - super().__init__(*args, **kwargs) - - if not order_detail and not isGeneratingSchema(): - self.fields.pop('order_detail', None) - @staticmethod def annotate_queryset(queryset): """Annotate the queryset with extra information.""" @@ -1314,8 +1267,11 @@ class SalesOrderShipmentSerializer(NotesFieldMixin, InvenTreeModelSerializer): read_only=True, allow_null=True, label=_('Allocated Items') ) - order_detail = SalesOrderSerializer( - source='order', read_only=True, allow_null=True, many=False + order_detail = can_filter( + SalesOrderSerializer( + source='order', read_only=True, allow_null=True, many=False + ), + True, ) @@ -1352,34 +1308,6 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): read_only_fields = ['line', ''] - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer.""" - order_detail = kwargs.pop('order_detail', False) - part_detail = kwargs.pop('part_detail', True) - item_detail = kwargs.pop('item_detail', True) - location_detail = kwargs.pop('location_detail', False) - customer_detail = kwargs.pop('customer_detail', False) - - super().__init__(*args, **kwargs) - - if isGeneratingSchema(): - return - - if not order_detail: - self.fields.pop('order_detail', None) - - if not part_detail: - self.fields.pop('part_detail', None) - - if not item_detail: - self.fields.pop('item_detail', None) - - if not location_detail: - self.fields.pop('location_detail', None) - - if not customer_detail: - self.fields.pop('customer_detail', None) - part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) order = serializers.PrimaryKeyRelatedField( source='line.order', many=False, read_only=True @@ -1391,26 +1319,38 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): ) # Extra detail fields - order_detail = SalesOrderSerializer( - source='line.order', many=False, read_only=True, allow_null=True + order_detail = can_filter( + SalesOrderSerializer( + source='line.order', many=False, read_only=True, allow_null=True + ) ) - part_detail = PartBriefSerializer( - source='item.part', many=False, read_only=True, allow_null=True + part_detail = can_filter( + PartBriefSerializer( + source='item.part', many=False, read_only=True, allow_null=True + ), + True, ) - item_detail = stock.serializers.StockItemSerializer( - source='item', - many=False, - read_only=True, - allow_null=True, - part_detail=False, - location_detail=False, - supplier_part_detail=False, + item_detail = can_filter( + stock.serializers.StockItemSerializer( + source='item', + many=False, + read_only=True, + allow_null=True, + part_detail=False, + location_detail=False, + supplier_part_detail=False, + ), + True, ) - location_detail = stock.serializers.LocationBriefSerializer( - source='item.location', many=False, read_only=True, allow_null=True + location_detail = can_filter( + stock.serializers.LocationBriefSerializer( + source='item.location', many=False, read_only=True, allow_null=True + ) ) - customer_detail = CompanyBriefSerializer( - source='line.order.customer', many=False, read_only=True, allow_null=True + customer_detail = can_filter( + CompanyBriefSerializer( + source='line.order.customer', many=False, read_only=True, allow_null=True + ) ) shipment_detail = SalesOrderShipmentSerializer( @@ -1860,8 +1800,10 @@ class SalesOrderExtraLineSerializer( model = order.models.SalesOrderExtraLine - order_detail = SalesOrderSerializer( - source='order', many=False, read_only=True, allow_null=True + order_detail = can_filter( + SalesOrderSerializer( + source='order', many=False, read_only=True, allow_null=True + ) ) @@ -1891,15 +1833,6 @@ class ReturnOrderSerializer( read_only_fields = ['creation_date'] - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer.""" - customer_detail = kwargs.pop('customer_detail', False) - - super().__init__(*args, **kwargs) - - if customer_detail is not True and not isGeneratingSchema(): - self.fields.pop('customer_detail', None) - def skip_create_fields(self): """Skip these fields when instantiating a new object.""" fields = super().skip_create_fields() @@ -1929,8 +1862,10 @@ class ReturnOrderSerializer( return queryset - customer_detail = CompanyBriefSerializer( - source='customer', many=False, read_only=True, allow_null=True + customer_detail = can_filter( + CompanyBriefSerializer( + source='customer', many=False, read_only=True, allow_null=True + ) ) @@ -2096,40 +2031,26 @@ class ReturnOrderLineItemSerializer( 'link', ] - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer.""" - order_detail = kwargs.pop('order_detail', False) - item_detail = kwargs.pop('item_detail', False) - part_detail = kwargs.pop('part_detail', False) - - super().__init__(*args, **kwargs) - - if isGeneratingSchema(): - return - - if not order_detail: - self.fields.pop('order_detail', None) - - if not item_detail: - self.fields.pop('item_detail', None) - - if not part_detail: - self.fields.pop('part_detail', None) - - order_detail = ReturnOrderSerializer( - source='order', many=False, read_only=True, allow_null=True + order_detail = can_filter( + ReturnOrderSerializer( + source='order', many=False, read_only=True, allow_null=True + ) ) quantity = serializers.FloatField( label=_('Quantity'), help_text=_('Quantity to return') ) - item_detail = stock.serializers.StockItemSerializer( - source='item', many=False, read_only=True, allow_null=True + item_detail = can_filter( + stock.serializers.StockItemSerializer( + source='item', many=False, read_only=True, allow_null=True + ) ) - part_detail = PartBriefSerializer( - source='item.part', many=False, read_only=True, allow_null=True + part_detail = can_filter( + PartBriefSerializer( + source='item.part', many=False, read_only=True, allow_null=True + ) ) price = InvenTreeMoneySerializer(allow_null=True) @@ -2147,6 +2068,8 @@ class ReturnOrderExtraLineSerializer( model = order.models.ReturnOrderExtraLine - order_detail = ReturnOrderSerializer( - source='order', many=False, read_only=True, allow_null=True + order_detail = can_filter( + ReturnOrderSerializer( + source='order', many=False, read_only=True, allow_null=True + ) ) diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py index 1402267fa3..9ceab29c2d 100644 --- a/src/backend/InvenTree/part/serializers.py +++ b/src/backend/InvenTree/part/serializers.py @@ -34,6 +34,7 @@ import users.models from importer.registry import register_importer from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.ready import isGeneratingSchema +from InvenTree.serializers import can_filter from users.serializers import UserSerializer from .models import ( @@ -91,6 +92,7 @@ class CategorySerializer( super().__init__(*args, **kwargs) + # TODO INVE-T1 support complex filters if not path_detail and not isGeneratingSchema(): self.fields.pop('path', None) @@ -355,6 +357,7 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer): super().__init__(*args, **kwargs) + # TODO INVE-T1 support complex filters if not pricing and not isGeneratingSchema(): self.fields.pop('pricing_min', None) self.fields.pop('pricing_max', None) @@ -414,25 +417,6 @@ class PartParameterSerializer( read_only_fields = ['updated', 'updated_by'] - def __init__(self, *args, **kwargs): - """Custom initialization method for the serializer. - - Allows us to optionally include or exclude particular information - """ - template_detail = kwargs.pop('template_detail', True) - part_detail = kwargs.pop('part_detail', False) - - super().__init__(*args, **kwargs) - - if isGeneratingSchema(): - return - - if not part_detail: - self.fields.pop('part_detail', None) - - if not template_detail: - self.fields.pop('template_detail', None) - def save(self): """Save the PartParameter instance.""" instance = super().save() @@ -444,12 +428,15 @@ class PartParameterSerializer( return instance - part_detail = PartBriefSerializer( - source='part', many=False, read_only=True, allow_null=True + part_detail = can_filter( + PartBriefSerializer(source='part', many=False, read_only=True, allow_null=True) ) - template_detail = PartParameterTemplateSerializer( - source='template', many=False, read_only=True, allow_null=True + template_detail = can_filter( + PartParameterTemplateSerializer( + source='template', many=False, read_only=True, allow_null=True + ), + True, ) updated_by_detail = UserSerializer( @@ -737,9 +724,7 @@ class PartSerializer( - Allows us to optionally pass extra fields based on the query. """ self.starred_parts = kwargs.pop('starred_parts', []) - category_detail = kwargs.pop('category_detail', False) location_detail = kwargs.pop('location_detail', False) - parameters = kwargs.pop('parameters', False) create = kwargs.pop('create', False) pricing = kwargs.pop('pricing', True) path_detail = kwargs.pop('path_detail', False) @@ -749,15 +734,11 @@ class PartSerializer( if isGeneratingSchema(): return - if not category_detail: - self.fields.pop('category_detail', None) - + # TODO INVE-T1 support complex filters if not location_detail: self.fields.pop('default_location_detail', None) - if not parameters: - self.fields.pop('parameters', None) - + # TODO INVE-T1 support complex filters if not path_detail: self.fields.pop('category_path', None) @@ -769,6 +750,7 @@ class PartSerializer( continue self.fields.pop(f, None) + # TODO INVE-T1 support complex filters if not pricing: self.fields.pop('pricing_min', None) self.fields.pop('pricing_max', None) @@ -888,8 +870,10 @@ class PartSerializer( return part in self.starred_parts # Extra detail for the category - category_detail = CategorySerializer( - source='category', many=False, read_only=True, allow_null=True + category_detail = can_filter( + CategorySerializer( + source='category', many=False, read_only=True, allow_null=True + ) ) category_path = serializers.ListField( @@ -1018,7 +1002,9 @@ class PartSerializer( source='pricing_data.updated', allow_null=True, read_only=True ) - parameters = PartParameterSerializer(many=True, read_only=True, allow_null=True) + parameters = can_filter( + PartParameterSerializer(many=True, read_only=True, allow_null=True) + ) # Extra fields used only for creation of a new Part instance duplicate = DuplicatePartSerializer( @@ -1649,29 +1635,14 @@ class BomItemSerializer( - part_detail and sub_part_detail serializers are only included if requested. - This saves a bunch of database requests """ - can_build = kwargs.pop('can_build', True) - part_detail = kwargs.pop('part_detail', False) - sub_part_detail = kwargs.pop('sub_part_detail', True) pricing = kwargs.pop('pricing', True) - substitutes = kwargs.pop('substitutes', True) super().__init__(*args, **kwargs) if isGeneratingSchema(): return - if not part_detail: - self.fields.pop('part_detail', None) - - if not sub_part_detail: - self.fields.pop('sub_part_detail', None) - - if not can_build: - self.fields.pop('can_build') - - if not substitutes: - self.fields.pop('substitutes', None) - + # TODO INVE-T1 support complex filters if not pricing: self.fields.pop('pricing_min', None) self.fields.pop('pricing_max', None) @@ -1702,12 +1673,18 @@ class BomItemSerializer( help_text=_('Select the parent assembly'), ) - substitutes = BomItemSubstituteSerializer( - many=True, read_only=True, allow_null=True + substitutes = can_filter( + BomItemSubstituteSerializer(many=True, read_only=True, allow_null=True), True ) - part_detail = PartBriefSerializer( - source='part', label=_('Assembly'), many=False, read_only=True, allow_null=True + part_detail = can_filter( + PartBriefSerializer( + source='part', + label=_('Assembly'), + many=False, + read_only=True, + allow_null=True, + ) ) sub_part = serializers.PrimaryKeyRelatedField( @@ -1716,12 +1693,15 @@ class BomItemSerializer( help_text=_('Select the component part'), ) - sub_part_detail = PartBriefSerializer( - source='sub_part', - label=_('Component'), - many=False, - read_only=True, - allow_null=True, + sub_part_detail = can_filter( + PartBriefSerializer( + source='sub_part', + label=_('Component'), + many=False, + read_only=True, + allow_null=True, + ), + True, ) on_order = serializers.FloatField( @@ -1732,8 +1712,9 @@ class BomItemSerializer( label=_('In Production'), read_only=True, allow_null=True ) - can_build = serializers.FloatField( - label=_('Can Build'), read_only=True, allow_null=True + can_build = can_filter( + serializers.FloatField(label=_('Can Build'), read_only=True, allow_null=True), + True, ) # Cached pricing fields diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py index 222f177ae8..c719bec0e7 100644 --- a/src/backend/InvenTree/stock/serializers.py +++ b/src/backend/InvenTree/stock/serializers.py @@ -37,6 +37,8 @@ from InvenTree.serializers import ( InvenTreeCurrencySerializer, InvenTreeDecimalField, InvenTreeModelSerializer, + PathScopedMixin, + can_filter, ) from users.serializers import UserSerializer @@ -222,23 +224,9 @@ class StockItemTestResultSerializer( read_only_fields = ['pk', 'user', 'date'] - def __init__(self, *args, **kwargs): - """Add detail fields.""" - user_detail = kwargs.pop('user_detail', False) - template_detail = kwargs.pop('template_detail', False) - - super().__init__(*args, **kwargs) - - if isGeneratingSchema(): - return - - if user_detail is not True: - self.fields.pop('user_detail', None) - - if template_detail is not True: - self.fields.pop('template_detail', None) - - user_detail = UserSerializer(source='user', read_only=True, allow_null=True) + user_detail = can_filter( + UserSerializer(source='user', read_only=True, allow_null=True) + ) template = serializers.PrimaryKeyRelatedField( queryset=part_models.PartTestTemplate.objects.all(), @@ -249,8 +237,10 @@ class StockItemTestResultSerializer( label=_('Test template for this result'), ) - template_detail = part_serializers.PartTestTemplateSerializer( - source='template', read_only=True, allow_null=True + template_detail = can_filter( + part_serializers.PartTestTemplateSerializer( + source='template', read_only=True, allow_null=True + ) ) attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField( @@ -415,30 +405,14 @@ class StockItemSerializer( def __init__(self, *args, **kwargs): """Add detail fields.""" - part_detail = kwargs.pop('part_detail', True) - location_detail = kwargs.pop('location_detail', True) - supplier_part_detail = kwargs.pop('supplier_part_detail', True) path_detail = kwargs.pop('path_detail', False) - tests = kwargs.pop('tests', False) - super().__init__(*args, **kwargs) if isGeneratingSchema(): return - if not part_detail: - self.fields.pop('part_detail', None) - - if not location_detail: - self.fields.pop('location_detail', None) - - if not supplier_part_detail: - self.fields.pop('supplier_part_detail', None) - - if not tests: - self.fields.pop('tests', None) - + # TODO INVE-T1 support complex filters if not path_detail: self.fields.pop('location_path', None) @@ -613,32 +587,43 @@ class StockItemSerializer( ) # Optional detail fields, which can be appended via query parameters - supplier_part_detail = company_serializers.SupplierPartSerializer( - label=_('Supplier Part'), - source='supplier_part', - brief=True, - supplier_detail=False, - manufacturer_detail=False, - part_detail=False, - many=False, - read_only=True, - allow_null=True, + supplier_part_detail = can_filter( + company_serializers.SupplierPartSerializer( + label=_('Supplier Part'), + source='supplier_part', + brief=True, + supplier_detail=False, + manufacturer_detail=False, + part_detail=False, + many=False, + read_only=True, + allow_null=True, + ), + True, ) - part_detail = part_serializers.PartBriefSerializer( - label=_('Part'), source='part', many=False, read_only=True, allow_null=True + part_detail = can_filter( + part_serializers.PartBriefSerializer( + label=_('Part'), source='part', many=False, read_only=True, allow_null=True + ), + True, ) - location_detail = LocationBriefSerializer( - label=_('Location'), - source='location', - many=False, - read_only=True, - allow_null=True, + location_detail = can_filter( + LocationBriefSerializer( + label=_('Location'), + source='location', + many=False, + read_only=True, + allow_null=True, + ), + True, ) - tests = StockItemTestResultSerializer( - source='test_results', many=True, read_only=True, allow_null=True + tests = can_filter( + StockItemTestResultSerializer( + source='test_results', many=True, read_only=True, allow_null=True + ) ) quantity = InvenTreeDecimalField() @@ -1189,6 +1174,7 @@ class LocationSerializer( super().__init__(*args, **kwargs) + # TODO INVE-T1 support complex filters if not path_detail and not isGeneratingSchema(): self.fields.pop('path', None) @@ -1241,7 +1227,9 @@ class LocationSerializer( @register_importer() class StockTrackingSerializer( - DataImportExportSerializerMixin, InvenTree.serializers.InvenTreeModelSerializer + DataImportExportSerializerMixin, + PathScopedMixin, + InvenTree.serializers.InvenTreeModelSerializer, ): """Serializer for StockItemTracking model.""" @@ -1264,30 +1252,14 @@ class StockTrackingSerializer( read_only_fields = ['date', 'user', 'label', 'tracking_type'] - def __init__(self, *args, **kwargs): - """Add detail fields.""" - item_detail = kwargs.pop('item_detail', False) - user_detail = kwargs.pop('user_detail', False) - - super().__init__(*args, **kwargs) - - if isGeneratingSchema(): - return - - if item_detail is not True: - self.fields.pop('item_detail', None) - - if user_detail is not True: - self.fields.pop('user_detail', None) - label = serializers.CharField(read_only=True) - item_detail = StockItemSerializer( - source='item', many=False, read_only=True, allow_null=True + item_detail = can_filter( + StockItemSerializer(source='item', many=False, read_only=True, allow_null=True) ) - user_detail = UserSerializer( - source='user', many=False, read_only=True, allow_null=True + user_detail = can_filter( + UserSerializer(source='user', many=False, read_only=True, allow_null=True) ) deltas = serializers.JSONField(read_only=True) diff --git a/src/backend/InvenTree/users/serializers.py b/src/backend/InvenTree/users/serializers.py index d88b24e72e..8d09eb9ad1 100644 --- a/src/backend/InvenTree/users/serializers.py +++ b/src/backend/InvenTree/users/serializers.py @@ -245,6 +245,7 @@ class GroupSerializer(InvenTreeModelSerializer): def __init__(self, *args, **kwargs): """Initialize this serializer with extra fields as required.""" + # TODO INVE-T1 support complex filters role_detail = kwargs.pop('role_detail', False) user_detail = kwargs.pop('user_detail', False) permission_detail = kwargs.pop('permission_detail', False)