2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-24 01:47:39 +00:00

adjust naming and docs, add typing to clean stuff up

This commit is contained in:
Matthias Mair
2025-10-15 00:05:25 +02:00
parent 88fcc27062
commit 6549ad63b1
9 changed files with 260 additions and 215 deletions

View File

@@ -183,5 +183,12 @@ Overriding a global setting with a different value than the current one.
See [Override global settings](../settings/global.md#override-global-settings) for more information. See [Override global settings](../settings/global.md#override-global-settings) for more information.
#### INVE-I2
**Issue with filtering serializer or decorator - Backend**
An issue was detected with the application of a filtering serializer or decorator. This might lead to unexpected behaviour or performance issues. Therefore an issue is raised to make the developer aware of the possible issue. Look into the docstrings of enable_filter, FilterableSerializerField or FilterableSerializerMixin.
This warning should only be raised during development and not in production, if you recently installed a plugin you might want to contact the plugin author.
### INVE-M (InvenTree Miscellaneous) ### INVE-M (InvenTree Miscellaneous)
Miscellaneous — These are information messages that might be used to mark debug information or other messages helpful for the InvenTree team to understand behaviour. Miscellaneous — These are information messages that might be used to mark debug information or other messages helpful for the InvenTree team to understand behaviour.

View File

@@ -14,7 +14,7 @@ from InvenTree.helpers import (
strip_html_tags, strip_html_tags,
) )
from InvenTree.schema import schema_for_view_output_options from InvenTree.schema import schema_for_view_output_options
from InvenTree.serializers import PathScopedMixin from InvenTree.serializers import FilterableSerializerMixin
class CleanMixin: class CleanMixin:
@@ -231,9 +231,9 @@ class OutputOptionsMixin:
serializer = super().get_serializer(*args, **kwargs) serializer = super().get_serializer(*args, **kwargs)
# Check if the serializer actually can be filtered - makes not much sense to use this mixin without that prerequisite # Check if the serializer actually can be filtered - makes not much sense to use this mixin without that prerequisite
if not isinstance(serializer, PathScopedMixin): if not isinstance(serializer, FilterableSerializerMixin):
raise Exception( raise Exception(
'INVE-W999: `OutputOptionsMixin` can only be used with serializers that contain the `PathScopedMixin` mixin' 'INVE-I2: `OutputOptionsMixin` can only be used with serializers that contain the `FilterableSerializerMixin` mixin'
) )
return serializer return serializer

View File

@@ -4,7 +4,7 @@ import os
from collections import OrderedDict from collections import OrderedDict
from copy import deepcopy from copy import deepcopy
from decimal import Decimal from decimal import Decimal
from typing import Optional from typing import Any, Optional
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
@@ -31,8 +31,11 @@ from InvenTree.helpers import str2bool
# region path filtering # region path filtering
class OptFilter: class FilterableSerializerField:
"""Filter for serializer or field.""" """Mixin to mark serializer as filterable.
This needs to be used in conjunction with `enable_filter` on the serializer field!
"""
is_filterable = None is_filterable = None
is_filterable_vals = {} is_filterable_vals = {}
@@ -45,15 +48,47 @@ class OptFilter:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class PathScopedMixin: def enable_filter(
"""Mixin to disable a serializer field based on kwargs passed to the view.""" func: Any, default_include: bool = False, filter_name: Optional[str] = None
):
"""Decorator for marking a serializer field as filterable.
This can be customized by passing in arguments. This only works in conjunction with serializer fields or serializers that contain the `FilterableSerializerField` mixin.
Args:
func: The serializer field to mark as filterable. Will automatically be passed when used as a decorator.
default_include (bool): If True, the field will be included by default unless explicitly excluded. If False, the field will be excluded by default unless explicitly included.
filter_name (str, optional): The name of the filter parameter to use in the URL. If None, the function name of the (decorated) function will be used.
"""
# Ensure this function can be actually filteres
if not issubclass(func.__class__, FilterableSerializerField):
raise TypeError(
'INVE-I2: `enable_filter` can only be applied to serializer fields / serializers that contain the `FilterableSerializerField` mixin!'
)
# Mark the function as filterable
func._kwargs['is_filterable'] = True
func._kwargs['is_filterable_vals'] = {
'default': default_include,
'filter_name': filter_name if filter_name else func.field_name,
}
return func
class FilterableSerializerMixin:
"""Mixin that enables filtering of marked fields on a serializer.
Use the `enable_filter` decorator to mark serializer fields as filterable.
This introduces overhead during initialization, so only use this mixin when necessary.
If you need to mark a serializer as filterable but it does not contain any filterable fields, set `no_filters = True` to avoid getting an exception that protects against over-application of this mixin.
"""
_was_filtered = False _was_filtered = False
no_filters = False no_filters = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialization routine for the serializer.""" """Initialization routine for the serializer. This gathers and applies filters through kwargs."""
# add list_serializer_class to meta if not present # add list_serializer_class to meta if not present - reduces duplication
if not isinstance(self, FilterableListSerializer) and ( if not isinstance(self, FilterableListSerializer) and (
not hasattr(self.Meta, 'list_serializer_class') not hasattr(self.Meta, 'list_serializer_class')
): ):
@@ -61,98 +96,90 @@ class PathScopedMixin:
self.gather_filters(kwargs) self.gather_filters(kwargs)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.do_filtering(*args, **kwargs) self.do_filtering()
def gather_filters(self, kwargs): def gather_filters(self, kwargs) -> None:
"""Gather filterable fields.""" """Gather filterable fields through introspection."""
# Fast exit if this has already been done or would not have any effect
if getattr(self, '_was_filtered', False) or not hasattr(self, 'fields'): if getattr(self, '_was_filtered', False) or not hasattr(self, 'fields'):
return kwargs return
self._was_filtered = True self._was_filtered = True
# Actually gather the filterable fields # Actually gather the filterable fields
fields = self.fields.items() # Also see `enable_filter` where` is_filterable and is_filterable_vals are set
self.filter_targets = { self.filter_targets: dict[str, dict] = {
k: {'serializer': a, **getattr(a, 'is_filterable_vals', {})} str(k): {'serializer': a, **getattr(a, 'is_filterable_vals', {})}
for k, a in fields for k, a in self.fields.items()
if getattr(a, 'is_filterable', None) if getattr(a, 'is_filterable', None)
} }
# Remove filter args from kwargs to avoid issues with super().__init__ # Remove filter args from kwargs to avoid issues with super().__init__
poped_kwargs = {} # store popped kwargs as a arg might be reused for multiple fields poped_kwargs = {} # store popped kwargs as a arg might be reused for multiple fields
tgs_vals = {} tgs_vals: dict[str, bool] = {}
for k, v in self.filter_targets.items(): for k, v in self.filter_targets.items():
pop_ref = v['name'] or k pop_ref = v['filter_name'] or k
val = kwargs.pop(pop_ref, poped_kwargs.get(pop_ref)) val = kwargs.pop(pop_ref, poped_kwargs.get(pop_ref))
if val: if val: # Save popped value for reuse
poped_kwargs[pop_ref] = val poped_kwargs[pop_ref] = val
tgs_vals[k] = str2bool(val) if isinstance(val, str) else val tgs_vals[k] = (
str2bool(val) if isinstance(val, (str, int, float)) else bool(val)
) # Support for various filtering style for backwards compatibility
self.filter_target_values = tgs_vals self.filter_target_values = tgs_vals
# Ensure this mixin is not proadly applied as it is expensive on scale (total CI time increased by 21% when running all coverage tests)
if len(self.filter_targets) == 0 and not self.no_filters: if len(self.filter_targets) == 0 and not self.no_filters:
raise Exception( raise Exception(
'INVE-W999: No filter targets found in fields, remove `PathScopedMixin`' 'INVE-I2: No filter targets found in fields, remove `PathScopedMixin`'
) )
return kwargs def do_filtering(self) -> None:
def do_filtering(self, *args, **kwargs):
"""Do the actual filtering.""" """Do the actual filtering."""
# This serializer might not contain filters or we do not want to pop fields while generating the schema
if ( if (
not hasattr(self, 'filter_target_values') not hasattr(self, 'filter_target_values')
or InvenTree.ready.isGeneratingSchema() or InvenTree.ready.isGeneratingSchema()
): ):
return return
# Throw out fields which are not requested # Throw out fields which are not requested (either by default or explicitly)
for k, v in self.filter_target_values.items(): for k, v in self.filter_target_values.items():
value = v if v is not None else self.filter_targets[k]['default'] # See `enable_filter` where` is_filterable and is_filterable_vals are set
value = v if v is not None else bool(self.filter_targets[k]['default'])
if value is not True: if value is not True:
self.fields.pop(k, None) self.fields.pop(k, None)
# Decorator for marking serialzier fields that can be filtered out # special serializers which allow filtering
def can_filter(func, default=False, name: Optional[str] = None): class FilterableListSerializer(
"""Decorator for marking serializer fields as filterable.""" FilterableSerializerField, FilterableSerializerMixin, serializers.ListSerializer
# Ensure this function can be actually filteres ):
if not issubclass(func.__class__, OptFilter):
raise TypeError(
'INVE-W999: `can_filter` can only be applied to serializers that contain `OptFilter` mixin!'
)
# Mark the function as filterable
func._kwargs['is_filterable'] = True
func._kwargs['is_filterable_vals'] = {
'default': default,
'name': name if name else func.field_name,
}
return func
class FilterableListSerializer(OptFilter, PathScopedMixin, serializers.ListSerializer):
"""Custom ListSerializer which allows filtering of fields.""" """Custom ListSerializer which allows filtering of fields."""
class CfListField(OptFilter, serializers.ListField): # special serializer fields which allow filtering
class FilterableListField(FilterableSerializerField, serializers.ListField):
"""Custom ListField which allows filtering.""" """Custom ListField which allows filtering."""
class CfSerializerMethodField(OptFilter, serializers.SerializerMethodField): class FilterableSerializerMethodField(
FilterableSerializerField, serializers.SerializerMethodField
):
"""Custom SerializerMethodField which allows filtering.""" """Custom SerializerMethodField which allows filtering."""
class CfDateTimeField(OptFilter, serializers.DateTimeField): class FilterableDateTimeField(FilterableSerializerField, serializers.DateTimeField):
"""Custom DateTimeField which allows filtering.""" """Custom DateTimeField which allows filtering."""
class CfFloatField(OptFilter, serializers.FloatField): class FilterableFloatField(FilterableSerializerField, serializers.FloatField):
"""Custom FloatField which allows filtering.""" """Custom FloatField which allows filtering."""
class CfCharField(OptFilter, serializers.CharField): class FilterableCharField(FilterableSerializerField, serializers.CharField):
"""Custom CharField which allows filtering.""" """Custom CharField which allows filtering."""
class CfIntegerField(OptFilter, serializers.IntegerField): class FilterableIntegerField(FilterableSerializerField, serializers.IntegerField):
"""Custom IntegerField which allows filtering.""" """Custom IntegerField which allows filtering."""
@@ -163,10 +190,11 @@ class EmptySerializer(serializers.Serializer):
"""Empty serializer for use in testing.""" """Empty serializer for use in testing."""
class InvenTreeMoneySerializer(OptFilter, MoneyField): class InvenTreeMoneySerializer(FilterableSerializerField, MoneyField):
"""Custom serializer for 'MoneyField', which ensures that passed values are numerically valid. """Custom serializer for 'MoneyField', which ensures that passed values are numerically valid.
Ref: https://github.com/django-money/django-money/blob/master/djmoney/contrib/django_rest_framework/fields.py Ref: https://github.com/django-money/django-money/blob/master/djmoney/contrib/django_rest_framework/fields.py
This field allows filtering.
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -519,8 +547,11 @@ class BareInvenTreeModelSerializer(serializers.ModelSerializer):
return data return data
class InvenTreeModelSerializer(OptFilter, BareInvenTreeModelSerializer): class InvenTreeModelSerializer(FilterableSerializerField, BareInvenTreeModelSerializer):
"""Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation.""" """Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation.
This field allows filtering.
"""
class InvenTreeTaggitSerializer(TaggitSerializer): class InvenTreeTaggitSerializer(TaggitSerializer):

View File

@@ -32,13 +32,13 @@ from common.settings import get_global_setting
from generic.states.fields import InvenTreeCustomStatusSerializerMixin from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import ( from InvenTree.serializers import (
CfCharField, FilterableCharField,
CfIntegerField, FilterableIntegerField,
FilterableSerializerMixin,
InvenTreeDecimalField, InvenTreeDecimalField,
InvenTreeModelSerializer, InvenTreeModelSerializer,
NotesFieldMixin, NotesFieldMixin,
PathScopedMixin, enable_filter,
can_filter,
) )
from stock.generators import generate_batch_code from stock.generators import generate_batch_code
from stock.models import StockItem, StockLocation from stock.models import StockItem, StockLocation
@@ -55,7 +55,7 @@ from .status_codes import BuildStatus
class BuildSerializer( class BuildSerializer(
PathScopedMixin, FilterableSerializerMixin,
NotesFieldMixin, NotesFieldMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTreeCustomStatusSerializerMixin, InvenTreeCustomStatusSerializerMixin,
@@ -118,7 +118,7 @@ class BuildSerializer(
status_text = serializers.CharField(source='get_status_display', read_only=True) status_text = serializers.CharField(source='get_status_display', read_only=True)
part_detail = can_filter( part_detail = enable_filter(
part_serializers.PartBriefSerializer(source='part', many=False, read_only=True), part_serializers.PartBriefSerializer(source='part', many=False, read_only=True),
True, True,
) )
@@ -131,46 +131,48 @@ class BuildSerializer(
overdue = serializers.BooleanField(read_only=True, default=False) overdue = serializers.BooleanField(read_only=True, default=False)
issued_by_detail = can_filter( issued_by_detail = enable_filter(
UserSerializer(source='issued_by', read_only=True), True, name='user_detail' UserSerializer(source='issued_by', read_only=True),
True,
filter_name='user_detail',
) )
responsible_detail = can_filter( responsible_detail = enable_filter(
OwnerSerializer(source='responsible', read_only=True, allow_null=True), OwnerSerializer(source='responsible', read_only=True, allow_null=True),
True, True,
name='user_detail', filter_name='user_detail',
) )
barcode_hash = serializers.CharField(read_only=True) barcode_hash = serializers.CharField(read_only=True)
project_code_label = can_filter( project_code_label = enable_filter(
CfCharField( FilterableCharField(
source='project_code.code', source='project_code.code',
read_only=True, read_only=True,
label=_('Project Code Label'), label=_('Project Code Label'),
allow_null=True, allow_null=True,
), ),
True, True,
name='project_code_detail', filter_name='project_code_detail',
) )
project_code_detail = can_filter( project_code_detail = enable_filter(
ProjectCodeSerializer( ProjectCodeSerializer(
source='project_code', many=False, read_only=True, allow_null=True source='project_code', many=False, read_only=True, allow_null=True
), ),
True, True,
name='project_code_detail', filter_name='project_code_detail',
) )
project_code = can_filter( project_code = enable_filter(
CfIntegerField( FilterableIntegerField(
allow_null=True, allow_null=True,
required=False, required=False,
label=_('Project Code'), label=_('Project Code'),
help_text=_('Project code for this build order'), help_text=_('Project code for this build order'),
), ),
True, True,
name='project_code_detail', filter_name='project_code_detail',
) )
@staticmethod @staticmethod
@@ -1157,7 +1159,7 @@ class BuildAutoAllocationSerializer(serializers.Serializer):
class BuildItemSerializer( class BuildItemSerializer(
PathScopedMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer FilterableSerializerMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer
): ):
"""Serializes a BuildItem object, which is an allocation of a stock item against a build order.""" """Serializes a BuildItem object, which is an allocation of a stock item against a build order."""
@@ -1228,7 +1230,7 @@ class BuildItemSerializer(
) )
# Extra (optional) detail fields # Extra (optional) detail fields
part_detail = can_filter( part_detail = enable_filter(
part_serializers.PartBriefSerializer( part_serializers.PartBriefSerializer(
label=_('Part'), label=_('Part'),
source='stock_item.part', source='stock_item.part',
@@ -1240,7 +1242,7 @@ class BuildItemSerializer(
True, True,
) )
stock_item_detail = can_filter( stock_item_detail = enable_filter(
StockItemSerializer( StockItemSerializer(
source='stock_item', source='stock_item',
read_only=True, read_only=True,
@@ -1252,14 +1254,14 @@ class BuildItemSerializer(
path_detail=False, path_detail=False,
), ),
True, True,
name='stock_detail', filter_name='stock_detail',
) )
location = serializers.PrimaryKeyRelatedField( location = serializers.PrimaryKeyRelatedField(
label=_('Location'), source='stock_item.location', many=False, read_only=True label=_('Location'), source='stock_item.location', many=False, read_only=True
) )
location_detail = can_filter( location_detail = enable_filter(
LocationBriefSerializer( LocationBriefSerializer(
label=_('Location'), label=_('Location'),
source='stock_item.location', source='stock_item.location',
@@ -1269,7 +1271,7 @@ class BuildItemSerializer(
True, True,
) )
build_detail = can_filter( build_detail = enable_filter(
BuildSerializer( BuildSerializer(
label=_('Build'), label=_('Build'),
source='build_line.build', source='build_line.build',
@@ -1293,7 +1295,7 @@ class BuildItemSerializer(
class BuildLineSerializer( class BuildLineSerializer(
PathScopedMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer FilterableSerializerMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer
): ):
"""Serializer for a BuildItem object.""" """Serializer for a BuildItem object."""
@@ -1369,7 +1371,7 @@ class BuildLineSerializer(
read_only=True, read_only=True,
) )
allocations = can_filter( allocations = enable_filter(
BuildItemSerializer(many=True, read_only=True, build_detail=False), True BuildItemSerializer(many=True, read_only=True, build_detail=False), True
) )
@@ -1402,7 +1404,7 @@ class BuildLineSerializer(
bom_item = serializers.PrimaryKeyRelatedField(label=_('BOM Item'), read_only=True) bom_item = serializers.PrimaryKeyRelatedField(label=_('BOM Item'), read_only=True)
# Foreign key fields # Foreign key fields
bom_item_detail = can_filter( bom_item_detail = enable_filter(
part_serializers.BomItemSerializer( part_serializers.BomItemSerializer(
label=_('BOM Item'), label=_('BOM Item'),
source='bom_item', source='bom_item',
@@ -1417,7 +1419,7 @@ class BuildLineSerializer(
True, True,
) )
assembly_detail = can_filter( assembly_detail = enable_filter(
part_serializers.PartBriefSerializer( part_serializers.PartBriefSerializer(
label=_('Assembly'), label=_('Assembly'),
source='bom_item.part', source='bom_item.part',
@@ -1429,7 +1431,7 @@ class BuildLineSerializer(
True, True,
) )
part_detail = can_filter( part_detail = enable_filter(
part_serializers.PartBriefSerializer( part_serializers.PartBriefSerializer(
label=_('Part'), label=_('Part'),
source='bom_item.sub_part', source='bom_item.sub_part',
@@ -1440,7 +1442,7 @@ class BuildLineSerializer(
True, True,
) )
build_detail = can_filter( build_detail = enable_filter(
BuildSerializer( BuildSerializer(
label=_('Build'), label=_('Build'),
source='build', source='build',

View File

@@ -18,7 +18,8 @@ from importer.registry import register_importer
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.ready import isGeneratingSchema from InvenTree.ready import isGeneratingSchema
from InvenTree.serializers import ( from InvenTree.serializers import (
CfCharField, FilterableCharField,
FilterableSerializerMixin,
InvenTreeCurrencySerializer, InvenTreeCurrencySerializer,
InvenTreeDecimalField, InvenTreeDecimalField,
InvenTreeImageSerializerField, InvenTreeImageSerializerField,
@@ -26,9 +27,8 @@ from InvenTree.serializers import (
InvenTreeMoneySerializer, InvenTreeMoneySerializer,
InvenTreeTagModelSerializer, InvenTreeTagModelSerializer,
NotesFieldMixin, NotesFieldMixin,
PathScopedMixin,
RemoteImageMixin, RemoteImageMixin,
can_filter, enable_filter,
) )
from .models import ( from .models import (
@@ -251,7 +251,7 @@ class ContactSerializer(DataImportExportSerializerMixin, InvenTreeModelSerialize
@register_importer() @register_importer()
class ManufacturerPartSerializer( class ManufacturerPartSerializer(
PathScopedMixin, FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTreeTagModelSerializer, InvenTreeTagModelSerializer,
NotesFieldMixin, NotesFieldMixin,
@@ -279,22 +279,22 @@ class ManufacturerPartSerializer(
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
part_detail = can_filter( part_detail = enable_filter(
part_serializers.PartBriefSerializer( part_serializers.PartBriefSerializer(
source='part', many=False, read_only=True, allow_null=True source='part', many=False, read_only=True, allow_null=True
), ),
True, True,
) )
manufacturer_detail = can_filter( manufacturer_detail = enable_filter(
CompanyBriefSerializer( CompanyBriefSerializer(
source='manufacturer', many=False, read_only=True, allow_null=True source='manufacturer', many=False, read_only=True, allow_null=True
), ),
True, True,
) )
pretty_name = can_filter( pretty_name = enable_filter(
CfCharField(read_only=True, allow_null=True), name='pretty' FilterableCharField(read_only=True, allow_null=True), filter_name='pretty'
) )
manufacturer = serializers.PrimaryKeyRelatedField( manufacturer = serializers.PrimaryKeyRelatedField(
@@ -304,7 +304,7 @@ class ManufacturerPartSerializer(
@register_importer() @register_importer()
class ManufacturerPartParameterSerializer( class ManufacturerPartParameterSerializer(
PathScopedMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer FilterableSerializerMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer
): ):
"""Serializer for the ManufacturerPartParameter model.""" """Serializer for the ManufacturerPartParameter model."""
@@ -322,7 +322,7 @@ class ManufacturerPartParameterSerializer(
'units', 'units',
] ]
manufacturer_part_detail = can_filter( manufacturer_part_detail = enable_filter(
ManufacturerPartSerializer( ManufacturerPartSerializer(
source='manufacturer_part', many=False, read_only=True, allow_null=True source='manufacturer_part', many=False, read_only=True, allow_null=True
) )
@@ -331,7 +331,7 @@ class ManufacturerPartParameterSerializer(
@register_importer() @register_importer()
class SupplierPartSerializer( class SupplierPartSerializer(
PathScopedMixin, FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTreeTagModelSerializer, InvenTreeTagModelSerializer,
NotesFieldMixin, NotesFieldMixin,
@@ -532,7 +532,7 @@ class SupplierPartSerializer(
@register_importer() @register_importer()
class SupplierPriceBreakSerializer( class SupplierPriceBreakSerializer(
PathScopedMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer FilterableSerializerMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer
): ):
"""Serializer for SupplierPriceBreak object.""" """Serializer for SupplierPriceBreak object."""
@@ -569,14 +569,14 @@ class SupplierPriceBreakSerializer(
source='part.supplier', many=False, read_only=True source='part.supplier', many=False, read_only=True
) )
supplier_detail = can_filter( supplier_detail = enable_filter(
CompanyBriefSerializer( CompanyBriefSerializer(
source='part.supplier', many=False, read_only=True, allow_null=True source='part.supplier', many=False, read_only=True, allow_null=True
) )
) )
# Detail serializer for SupplierPart # Detail serializer for SupplierPart
part_detail = can_filter( part_detail = enable_filter(
SupplierPartSerializer( SupplierPartSerializer(
source='part', brief=True, many=False, read_only=True, allow_null=True source='part', brief=True, many=False, read_only=True, allow_null=True
) )

View File

@@ -45,13 +45,13 @@ from InvenTree.helpers import (
) )
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import ( from InvenTree.serializers import (
FilterableSerializerMixin,
InvenTreeCurrencySerializer, InvenTreeCurrencySerializer,
InvenTreeDecimalField, InvenTreeDecimalField,
InvenTreeModelSerializer, InvenTreeModelSerializer,
InvenTreeMoneySerializer, InvenTreeMoneySerializer,
NotesFieldMixin, NotesFieldMixin,
PathScopedMixin, enable_filter,
can_filter,
) )
from order.status_codes import ( from order.status_codes import (
PurchaseOrderStatusGroups, PurchaseOrderStatusGroups,
@@ -303,7 +303,7 @@ class AbstractExtraLineMeta:
@register_importer() @register_importer()
class PurchaseOrderSerializer( class PurchaseOrderSerializer(
PathScopedMixin, FilterableSerializerMixin,
NotesFieldMixin, NotesFieldMixin,
TotalPriceMixin, TotalPriceMixin,
InvenTreeCustomStatusSerializerMixin, InvenTreeCustomStatusSerializerMixin,
@@ -369,7 +369,7 @@ class PurchaseOrderSerializer(
source='supplier.name', read_only=True, label=_('Supplier Name') source='supplier.name', read_only=True, label=_('Supplier Name')
) )
supplier_detail = can_filter( supplier_detail = enable_filter(
CompanyBriefSerializer( CompanyBriefSerializer(
source='supplier', many=False, read_only=True, allow_null=True source='supplier', many=False, read_only=True, allow_null=True
) )
@@ -460,7 +460,7 @@ class PurchaseOrderIssueSerializer(OrderAdjustSerializer):
@register_importer() @register_importer()
class PurchaseOrderLineItemSerializer( class PurchaseOrderLineItemSerializer(
PathScopedMixin, FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
AbstractLineItemSerializer, AbstractLineItemSerializer,
InvenTreeModelSerializer, InvenTreeModelSerializer,
@@ -583,18 +583,18 @@ class PurchaseOrderLineItemSerializer(
total_price = serializers.FloatField(read_only=True) total_price = serializers.FloatField(read_only=True)
part_detail = can_filter( part_detail = enable_filter(
PartBriefSerializer( PartBriefSerializer(
source='get_base_part', many=False, read_only=True, allow_null=True source='get_base_part', many=False, read_only=True, allow_null=True
), ),
name='part_detail', filter_name='part_detail',
) )
supplier_part_detail = can_filter( supplier_part_detail = enable_filter(
SupplierPartSerializer( SupplierPartSerializer(
source='part', brief=True, many=False, read_only=True, allow_null=True source='part', brief=True, many=False, read_only=True, allow_null=True
), ),
name='part_detail', filter_name='part_detail',
) )
purchase_price = InvenTreeMoneySerializer(allow_null=True) purchase_price = InvenTreeMoneySerializer(allow_null=True)
@@ -615,7 +615,7 @@ class PurchaseOrderLineItemSerializer(
help_text=_('Purchase price currency') help_text=_('Purchase price currency')
) )
order_detail = can_filter( order_detail = enable_filter(
PurchaseOrderSerializer( PurchaseOrderSerializer(
source='order', read_only=True, allow_null=True, many=False source='order', read_only=True, allow_null=True, many=False
) )
@@ -693,11 +693,11 @@ class PurchaseOrderLineItemSerializer(
@register_importer() @register_importer()
class PurchaseOrderExtraLineSerializer( class PurchaseOrderExtraLineSerializer(
PathScopedMixin, AbstractExtraLineSerializer, InvenTreeModelSerializer FilterableSerializerMixin, AbstractExtraLineSerializer, InvenTreeModelSerializer
): ):
"""Serializer for a PurchaseOrderExtraLine object.""" """Serializer for a PurchaseOrderExtraLine object."""
order_detail = can_filter( order_detail = enable_filter(
PurchaseOrderSerializer( PurchaseOrderSerializer(
source='order', many=False, read_only=True, allow_null=True source='order', many=False, read_only=True, allow_null=True
) )
@@ -956,7 +956,7 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer):
@register_importer() @register_importer()
class SalesOrderSerializer( class SalesOrderSerializer(
PathScopedMixin, FilterableSerializerMixin,
NotesFieldMixin, NotesFieldMixin,
TotalPriceMixin, TotalPriceMixin,
InvenTreeCustomStatusSerializerMixin, InvenTreeCustomStatusSerializerMixin,
@@ -1022,7 +1022,7 @@ class SalesOrderSerializer(
return queryset return queryset
customer_detail = can_filter( customer_detail = enable_filter(
CompanyBriefSerializer( CompanyBriefSerializer(
source='customer', many=False, read_only=True, allow_null=True source='customer', many=False, read_only=True, allow_null=True
) )
@@ -1047,7 +1047,7 @@ class SalesOrderIssueSerializer(OrderAdjustSerializer):
@register_importer() @register_importer()
class SalesOrderLineItemSerializer( class SalesOrderLineItemSerializer(
PathScopedMixin, FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
AbstractLineItemSerializer, AbstractLineItemSerializer,
InvenTreeModelSerializer, InvenTreeModelSerializer,
@@ -1179,15 +1179,15 @@ class SalesOrderLineItemSerializer(
return queryset return queryset
order_detail = can_filter( order_detail = enable_filter(
SalesOrderSerializer( SalesOrderSerializer(
source='order', many=False, read_only=True, allow_null=True source='order', many=False, read_only=True, allow_null=True
) )
) )
part_detail = can_filter( part_detail = enable_filter(
PartBriefSerializer(source='part', many=False, read_only=True, allow_null=True) PartBriefSerializer(source='part', many=False, read_only=True, allow_null=True)
) )
customer_detail = can_filter( customer_detail = enable_filter(
CompanyBriefSerializer( CompanyBriefSerializer(
source='order.customer', many=False, read_only=True, allow_null=True source='order.customer', many=False, read_only=True, allow_null=True
) )
@@ -1215,7 +1215,7 @@ class SalesOrderLineItemSerializer(
@register_importer() @register_importer()
class SalesOrderShipmentSerializer( class SalesOrderShipmentSerializer(
PathScopedMixin, NotesFieldMixin, InvenTreeModelSerializer FilterableSerializerMixin, NotesFieldMixin, InvenTreeModelSerializer
): ):
"""Serializer for the SalesOrderShipment class.""" """Serializer for the SalesOrderShipment class."""
@@ -1253,7 +1253,7 @@ class SalesOrderShipmentSerializer(
read_only=True, allow_null=True, label=_('Allocated Items') read_only=True, allow_null=True, label=_('Allocated Items')
) )
order_detail = can_filter( order_detail = enable_filter(
SalesOrderSerializer( SalesOrderSerializer(
source='order', read_only=True, allow_null=True, many=False source='order', read_only=True, allow_null=True, many=False
), ),
@@ -1261,7 +1261,9 @@ class SalesOrderShipmentSerializer(
) )
class SalesOrderAllocationSerializer(PathScopedMixin, InvenTreeModelSerializer): class SalesOrderAllocationSerializer(
FilterableSerializerMixin, InvenTreeModelSerializer
):
"""Serializer for the SalesOrderAllocation model. """Serializer for the SalesOrderAllocation model.
This includes some fields from the related model objects. This includes some fields from the related model objects.
@@ -1303,18 +1305,18 @@ class SalesOrderAllocationSerializer(PathScopedMixin, InvenTreeModelSerializer):
) )
# Extra detail fields # Extra detail fields
order_detail = can_filter( order_detail = enable_filter(
SalesOrderSerializer( SalesOrderSerializer(
source='line.order', many=False, read_only=True, allow_null=True source='line.order', many=False, read_only=True, allow_null=True
) )
) )
part_detail = can_filter( part_detail = enable_filter(
PartBriefSerializer( PartBriefSerializer(
source='item.part', many=False, read_only=True, allow_null=True source='item.part', many=False, read_only=True, allow_null=True
), ),
True, True,
) )
item_detail = can_filter( item_detail = enable_filter(
stock.serializers.StockItemSerializer( stock.serializers.StockItemSerializer(
source='item', source='item',
many=False, many=False,
@@ -1326,12 +1328,12 @@ class SalesOrderAllocationSerializer(PathScopedMixin, InvenTreeModelSerializer):
), ),
True, True,
) )
location_detail = can_filter( location_detail = enable_filter(
stock.serializers.LocationBriefSerializer( stock.serializers.LocationBriefSerializer(
source='item.location', many=False, read_only=True, allow_null=True source='item.location', many=False, read_only=True, allow_null=True
) )
) )
customer_detail = can_filter( customer_detail = enable_filter(
CompanyBriefSerializer( CompanyBriefSerializer(
source='line.order.customer', many=False, read_only=True, allow_null=True source='line.order.customer', many=False, read_only=True, allow_null=True
) )
@@ -1775,7 +1777,7 @@ class SalesOrderShipmentAllocationSerializer(serializers.Serializer):
@register_importer() @register_importer()
class SalesOrderExtraLineSerializer( class SalesOrderExtraLineSerializer(
PathScopedMixin, AbstractExtraLineSerializer, InvenTreeModelSerializer FilterableSerializerMixin, AbstractExtraLineSerializer, InvenTreeModelSerializer
): ):
"""Serializer for a SalesOrderExtraLine object.""" """Serializer for a SalesOrderExtraLine object."""
@@ -1784,7 +1786,7 @@ class SalesOrderExtraLineSerializer(
model = order.models.SalesOrderExtraLine model = order.models.SalesOrderExtraLine
order_detail = can_filter( order_detail = enable_filter(
SalesOrderSerializer( SalesOrderSerializer(
source='order', many=False, read_only=True, allow_null=True source='order', many=False, read_only=True, allow_null=True
) )
@@ -1793,7 +1795,7 @@ class SalesOrderExtraLineSerializer(
@register_importer() @register_importer()
class ReturnOrderSerializer( class ReturnOrderSerializer(
PathScopedMixin, FilterableSerializerMixin,
NotesFieldMixin, NotesFieldMixin,
InvenTreeCustomStatusSerializerMixin, InvenTreeCustomStatusSerializerMixin,
AbstractOrderSerializer, AbstractOrderSerializer,
@@ -1845,7 +1847,7 @@ class ReturnOrderSerializer(
return queryset return queryset
customer_detail = can_filter( customer_detail = enable_filter(
CompanyBriefSerializer( CompanyBriefSerializer(
source='customer', many=False, read_only=True, allow_null=True source='customer', many=False, read_only=True, allow_null=True
) )
@@ -1984,7 +1986,7 @@ class ReturnOrderReceiveSerializer(serializers.Serializer):
@register_importer() @register_importer()
class ReturnOrderLineItemSerializer( class ReturnOrderLineItemSerializer(
PathScopedMixin, FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
AbstractLineItemSerializer, AbstractLineItemSerializer,
InvenTreeModelSerializer, InvenTreeModelSerializer,
@@ -2014,7 +2016,7 @@ class ReturnOrderLineItemSerializer(
'link', 'link',
] ]
order_detail = can_filter( order_detail = enable_filter(
ReturnOrderSerializer( ReturnOrderSerializer(
source='order', many=False, read_only=True, allow_null=True source='order', many=False, read_only=True, allow_null=True
) )
@@ -2024,13 +2026,13 @@ class ReturnOrderLineItemSerializer(
label=_('Quantity'), help_text=_('Quantity to return') label=_('Quantity'), help_text=_('Quantity to return')
) )
item_detail = can_filter( item_detail = enable_filter(
stock.serializers.StockItemSerializer( stock.serializers.StockItemSerializer(
source='item', many=False, read_only=True, allow_null=True source='item', many=False, read_only=True, allow_null=True
) )
) )
part_detail = can_filter( part_detail = enable_filter(
PartBriefSerializer( PartBriefSerializer(
source='item.part', many=False, read_only=True, allow_null=True source='item.part', many=False, read_only=True, allow_null=True
) )
@@ -2042,7 +2044,7 @@ class ReturnOrderLineItemSerializer(
@register_importer() @register_importer()
class ReturnOrderExtraLineSerializer( class ReturnOrderExtraLineSerializer(
PathScopedMixin, AbstractExtraLineSerializer, InvenTreeModelSerializer FilterableSerializerMixin, AbstractExtraLineSerializer, InvenTreeModelSerializer
): ):
"""Serializer for a ReturnOrderExtraLine object.""" """Serializer for a ReturnOrderExtraLine object."""
@@ -2051,7 +2053,7 @@ class ReturnOrderExtraLineSerializer(
model = order.models.ReturnOrderExtraLine model = order.models.ReturnOrderExtraLine
order_detail = can_filter( order_detail = enable_filter(
ReturnOrderSerializer( ReturnOrderSerializer(
source='order', many=False, read_only=True, allow_null=True source='order', many=False, read_only=True, allow_null=True
) )

View File

@@ -33,11 +33,11 @@ from importer.registry import register_importer
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.ready import isGeneratingSchema from InvenTree.ready import isGeneratingSchema
from InvenTree.serializers import ( from InvenTree.serializers import (
CfDateTimeField, FilterableDateTimeField,
CfFloatField, FilterableFloatField,
CfListField, FilterableListField,
FilterableListSerializer, FilterableListSerializer,
can_filter, enable_filter,
) )
from users.serializers import UserSerializer from users.serializers import UserSerializer
@@ -63,7 +63,7 @@ logger = structlog.get_logger('inventree')
@register_importer() @register_importer()
class CategorySerializer( class CategorySerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTree.serializers.InvenTreeModelSerializer, InvenTree.serializers.InvenTreeModelSerializer,
): ):
@@ -131,14 +131,14 @@ class CategorySerializer(
"""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', [])
path = can_filter( path = enable_filter(
CfListField( FilterableListField(
child=serializers.DictField(), child=serializers.DictField(),
source='get_path', source='get_path',
read_only=True, read_only=True,
allow_null=True, allow_null=True,
), ),
name='path_detail', filter_name='path_detail',
) )
icon = serializers.CharField( icon = serializers.CharField(
@@ -314,7 +314,7 @@ class PartParameterTemplateSerializer(
class PartBriefSerializer( class PartBriefSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
InvenTree.serializers.InvenTreeModelSerializer, InvenTree.serializers.InvenTreeModelSerializer,
): ):
"""Serializer for Part (brief detail).""" """Serializer for Part (brief detail)."""
@@ -374,25 +374,25 @@ class PartBriefSerializer(
) )
# Pricing fields # Pricing fields
pricing_min = can_filter( pricing_min = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer( InvenTree.serializers.InvenTreeMoneySerializer(
source='pricing_data.overall_min', allow_null=True, read_only=True source='pricing_data.overall_min', allow_null=True, read_only=True
), ),
True, True,
name='pricing', filter_name='pricing',
) )
pricing_max = can_filter( pricing_max = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer( InvenTree.serializers.InvenTreeMoneySerializer(
source='pricing_data.overall_max', allow_null=True, read_only=True source='pricing_data.overall_max', allow_null=True, read_only=True
), ),
True, True,
name='pricing', filter_name='pricing',
) )
@register_importer() @register_importer()
class PartParameterSerializer( class PartParameterSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTree.serializers.InvenTreeModelSerializer, InvenTree.serializers.InvenTreeModelSerializer,
): ):
@@ -428,11 +428,11 @@ class PartParameterSerializer(
return instance return instance
part_detail = can_filter( part_detail = enable_filter(
PartBriefSerializer(source='part', many=False, read_only=True, allow_null=True) PartBriefSerializer(source='part', many=False, read_only=True, allow_null=True)
) )
template_detail = can_filter( template_detail = enable_filter(
PartParameterTemplateSerializer( PartParameterTemplateSerializer(
source='template', many=False, read_only=True, allow_null=True source='template', many=False, read_only=True, allow_null=True
), ),
@@ -627,7 +627,7 @@ class DefaultLocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
@register_importer() @register_importer()
class PartSerializer( class PartSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTree.serializers.NotesFieldMixin, InvenTree.serializers.NotesFieldMixin,
InvenTree.serializers.RemoteImageMixin, InvenTree.serializers.RemoteImageMixin,
@@ -878,27 +878,27 @@ class PartSerializer(
return part in self.starred_parts return part in self.starred_parts
# Extra detail for the category # Extra detail for the category
category_detail = can_filter( category_detail = enable_filter(
CategorySerializer( CategorySerializer(
source='category', many=False, read_only=True, allow_null=True source='category', many=False, read_only=True, allow_null=True
) )
) )
category_path = can_filter( category_path = enable_filter(
CfListField( FilterableListField(
child=serializers.DictField(), child=serializers.DictField(),
source='category.get_path', source='category.get_path',
read_only=True, read_only=True,
allow_null=True, allow_null=True,
), ),
name='path_detail', filter_name='path_detail',
) )
default_location_detail = can_filter( default_location_detail = enable_filter(
DefaultLocationSerializer( DefaultLocationSerializer(
source='default_location', many=False, read_only=True, allow_null=True source='default_location', many=False, read_only=True, allow_null=True
), ),
name='location_detail', filter_name='location_detail',
) )
category_name = serializers.CharField( category_name = serializers.CharField(
@@ -1006,27 +1006,29 @@ class PartSerializer(
) )
# Pricing fields # Pricing fields
pricing_min = can_filter( pricing_min = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer( InvenTree.serializers.InvenTreeMoneySerializer(
source='pricing_data.overall_min', allow_null=True, read_only=True source='pricing_data.overall_min', allow_null=True, read_only=True
), ),
True, True,
name='pricing', filter_name='pricing',
) )
pricing_max = can_filter( pricing_max = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer( InvenTree.serializers.InvenTreeMoneySerializer(
source='pricing_data.overall_max', allow_null=True, read_only=True source='pricing_data.overall_max', allow_null=True, read_only=True
), ),
True, True,
name='pricing', filter_name='pricing',
) )
pricing_updated = can_filter( pricing_updated = enable_filter(
CfDateTimeField(source='pricing_data.updated', allow_null=True, read_only=True), FilterableDateTimeField(
source='pricing_data.updated', allow_null=True, read_only=True
),
True, True,
name='pricing', filter_name='pricing',
) )
parameters = can_filter( parameters = enable_filter(
PartParameterSerializer(many=True, read_only=True, allow_null=True) PartParameterSerializer(many=True, read_only=True, allow_null=True)
) )
@@ -1615,7 +1617,7 @@ class BomItemSubstituteSerializer(InvenTree.serializers.InvenTreeModelSerializer
@register_importer() @register_importer()
class BomItemSerializer( class BomItemSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTree.serializers.InvenTreeModelSerializer, InvenTree.serializers.InvenTreeModelSerializer,
): ):
@@ -1694,11 +1696,11 @@ class BomItemSerializer(
help_text=_('Select the parent assembly'), help_text=_('Select the parent assembly'),
) )
substitutes = can_filter( substitutes = enable_filter(
BomItemSubstituteSerializer(many=True, read_only=True, allow_null=True), True BomItemSubstituteSerializer(many=True, read_only=True, allow_null=True), True
) )
part_detail = can_filter( part_detail = enable_filter(
PartBriefSerializer( PartBriefSerializer(
source='part', source='part',
label=_('Assembly'), label=_('Assembly'),
@@ -1714,7 +1716,7 @@ class BomItemSerializer(
help_text=_('Select the component part'), help_text=_('Select the component part'),
) )
sub_part_detail = can_filter( sub_part_detail = enable_filter(
PartBriefSerializer( PartBriefSerializer(
source='sub_part', source='sub_part',
label=_('Component'), label=_('Component'),
@@ -1733,41 +1735,42 @@ class BomItemSerializer(
label=_('In Production'), read_only=True, allow_null=True label=_('In Production'), read_only=True, allow_null=True
) )
can_build = can_filter( can_build = enable_filter(
CfFloatField(label=_('Can Build'), read_only=True, allow_null=True), True FilterableFloatField(label=_('Can Build'), read_only=True, allow_null=True),
True,
) )
# Cached pricing fields # Cached pricing fields
pricing_min = can_filter( pricing_min = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer( InvenTree.serializers.InvenTreeMoneySerializer(
source='sub_part.pricing_data.overall_min', allow_null=True, read_only=True source='sub_part.pricing_data.overall_min', allow_null=True, read_only=True
), ),
True, True,
name='pricing', filter_name='pricing',
) )
pricing_max = can_filter( pricing_max = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer( InvenTree.serializers.InvenTreeMoneySerializer(
source='sub_part.pricing_data.overall_max', allow_null=True, read_only=True source='sub_part.pricing_data.overall_max', allow_null=True, read_only=True
), ),
True, True,
name='pricing', filter_name='pricing',
) )
pricing_min_total = can_filter( pricing_min_total = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True), InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True),
True, True,
name='pricing', filter_name='pricing',
) )
pricing_max_total = can_filter( pricing_max_total = enable_filter(
InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True), InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True),
True, True,
name='pricing', filter_name='pricing',
) )
pricing_updated = can_filter( pricing_updated = enable_filter(
CfDateTimeField( FilterableDateTimeField(
source='sub_part.pricing_data.updated', allow_null=True, read_only=True source='sub_part.pricing_data.updated', allow_null=True, read_only=True
), ),
True, True,
name='pricing', filter_name='pricing',
) )
# Annotated fields for available stock # Annotated fields for available stock

View File

@@ -33,10 +33,10 @@ from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from importer.registry import register_importer from importer.registry import register_importer
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import ( from InvenTree.serializers import (
CfListField, FilterableListField,
InvenTreeCurrencySerializer, InvenTreeCurrencySerializer,
InvenTreeDecimalField, InvenTreeDecimalField,
can_filter, enable_filter,
) )
from users.serializers import UserSerializer from users.serializers import UserSerializer
@@ -194,7 +194,7 @@ class LocationBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
@register_importer() @register_importer()
class StockItemTestResultSerializer( class StockItemTestResultSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTree.serializers.InvenTreeModelSerializer, InvenTree.serializers.InvenTreeModelSerializer,
): ):
@@ -222,7 +222,7 @@ class StockItemTestResultSerializer(
] ]
read_only_fields = ['pk', 'user', 'date'] read_only_fields = ['pk', 'user', 'date']
user_detail = can_filter( user_detail = enable_filter(
UserSerializer(source='user', read_only=True, allow_null=True) UserSerializer(source='user', read_only=True, allow_null=True)
) )
@@ -235,7 +235,7 @@ class StockItemTestResultSerializer(
label=_('Test template for this result'), label=_('Test template for this result'),
) )
template_detail = can_filter( template_detail = enable_filter(
part_serializers.PartTestTemplateSerializer( part_serializers.PartTestTemplateSerializer(
source='template', read_only=True, allow_null=True source='template', read_only=True, allow_null=True
) )
@@ -297,7 +297,7 @@ class StockItemTestResultSerializer(
@register_importer() @register_importer()
class StockItemSerializer( class StockItemSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTreeCustomStatusSerializerMixin, InvenTreeCustomStatusSerializerMixin,
InvenTree.serializers.InvenTreeTagModelSerializer, InvenTree.serializers.InvenTreeTagModelSerializer,
@@ -415,14 +415,14 @@ class StockItemSerializer(
help_text=_('Parent stock item'), help_text=_('Parent stock item'),
) )
location_path = can_filter( location_path = enable_filter(
CfListField( FilterableListField(
child=serializers.DictField(), child=serializers.DictField(),
source='location.get_path', source='location.get_path',
read_only=True, read_only=True,
allow_null=True, allow_null=True,
), ),
name='path_detail', filter_name='path_detail',
) )
in_stock = serializers.BooleanField(read_only=True, label=_('In Stock')) in_stock = serializers.BooleanField(read_only=True, label=_('In Stock'))
@@ -574,7 +574,7 @@ class StockItemSerializer(
) )
# Optional detail fields, which can be appended via query parameters # Optional detail fields, which can be appended via query parameters
supplier_part_detail = can_filter( supplier_part_detail = enable_filter(
company_serializers.SupplierPartSerializer( company_serializers.SupplierPartSerializer(
label=_('Supplier Part'), label=_('Supplier Part'),
source='supplier_part', source='supplier_part',
@@ -589,14 +589,14 @@ class StockItemSerializer(
True, True,
) )
part_detail = can_filter( part_detail = enable_filter(
part_serializers.PartBriefSerializer( part_serializers.PartBriefSerializer(
label=_('Part'), source='part', many=False, read_only=True, allow_null=True label=_('Part'), source='part', many=False, read_only=True, allow_null=True
), ),
True, True,
) )
location_detail = can_filter( location_detail = enable_filter(
LocationBriefSerializer( LocationBriefSerializer(
label=_('Location'), label=_('Location'),
source='location', source='location',
@@ -607,7 +607,7 @@ class StockItemSerializer(
True, True,
) )
tests = can_filter( tests = enable_filter(
StockItemTestResultSerializer( StockItemTestResultSerializer(
source='test_results', many=True, read_only=True, allow_null=True source='test_results', many=True, read_only=True, allow_null=True
) )
@@ -1122,7 +1122,7 @@ class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
@register_importer() @register_importer()
class LocationSerializer( class LocationSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTree.serializers.InvenTreeTagModelSerializer, InvenTree.serializers.InvenTreeTagModelSerializer,
): ):
@@ -1187,14 +1187,14 @@ class LocationSerializer(
tags = TagListSerializerField(required=False) tags = TagListSerializerField(required=False)
path = can_filter( path = enable_filter(
CfListField( FilterableListField(
child=serializers.DictField(), child=serializers.DictField(),
source='get_path', source='get_path',
read_only=True, read_only=True,
allow_null=True, allow_null=True,
), ),
name='path_detail', filter_name='path_detail',
) )
# explicitly set this field, so it gets included for AutoSchema # explicitly set this field, so it gets included for AutoSchema
@@ -1208,7 +1208,7 @@ class LocationSerializer(
@register_importer() @register_importer()
class StockTrackingSerializer( class StockTrackingSerializer(
InvenTree.serializers.PathScopedMixin, InvenTree.serializers.FilterableSerializerMixin,
DataImportExportSerializerMixin, DataImportExportSerializerMixin,
InvenTree.serializers.InvenTreeModelSerializer, InvenTree.serializers.InvenTreeModelSerializer,
): ):
@@ -1234,11 +1234,11 @@ class StockTrackingSerializer(
label = serializers.CharField(read_only=True) label = serializers.CharField(read_only=True)
item_detail = can_filter( item_detail = enable_filter(
StockItemSerializer(source='item', many=False, read_only=True, allow_null=True) StockItemSerializer(source='item', many=False, read_only=True, allow_null=True)
) )
user_detail = can_filter( user_detail = enable_filter(
UserSerializer(source='user', many=False, read_only=True, allow_null=True) UserSerializer(source='user', many=False, read_only=True, allow_null=True)
) )

View File

@@ -8,11 +8,11 @@ from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from InvenTree.serializers import ( from InvenTree.serializers import (
CfSerializerMethodField,
FilterableListSerializer, FilterableListSerializer,
FilterableSerializerMethodField,
FilterableSerializerMixin,
InvenTreeModelSerializer, InvenTreeModelSerializer,
PathScopedMixin, enable_filter,
can_filter,
) )
from .models import ApiToken, Owner, RuleSet, UserProfile from .models import ApiToken, Owner, RuleSet, UserProfile
@@ -239,7 +239,7 @@ class ApiTokenSerializer(InvenTreeModelSerializer):
user_detail = UserSerializer(source='user', read_only=True) user_detail = UserSerializer(source='user', read_only=True)
class GroupSerializer(PathScopedMixin, InvenTreeModelSerializer): class GroupSerializer(FilterableSerializerMixin, InvenTreeModelSerializer):
"""Serializer for a 'Group'.""" """Serializer for a 'Group'."""
class Meta: class Meta:
@@ -248,25 +248,25 @@ class GroupSerializer(PathScopedMixin, InvenTreeModelSerializer):
model = Group model = Group
fields = ['pk', 'name', 'permissions', 'roles', 'users'] fields = ['pk', 'name', 'permissions', 'roles', 'users']
permissions = can_filter( permissions = enable_filter(
CfSerializerMethodField(allow_null=True, read_only=True), FilterableSerializerMethodField(allow_null=True, read_only=True),
name='permission_detail', filter_name='permission_detail',
) )
def get_permissions(self, group: Group) -> dict: def get_permissions(self, group: Group) -> dict:
"""Return a list of permissions associated with the group.""" """Return a list of permissions associated with the group."""
return generate_permission_dict(group.permissions.all()) return generate_permission_dict(group.permissions.all())
roles = can_filter( roles = enable_filter(
RuleSetSerializer( RuleSetSerializer(
source='rule_sets', many=True, read_only=True, allow_null=True source='rule_sets', many=True, read_only=True, allow_null=True
), ),
name='role_detail', filter_name='role_detail',
) )
users = can_filter( users = enable_filter(
UserSerializer(source='user_set', many=True, read_only=True, allow_null=True), UserSerializer(source='user_set', many=True, read_only=True, allow_null=True),
name='user_detail', filter_name='user_detail',
) )