mirror of
https://github.com/inventree/InvenTree.git
synced 2026-02-06 13:25:53 +00:00
[API] Tags filters (#11021)
* Add optional "tags" field * Refactor "tags" field - Off by default - Only prefetch when requested (expensive) - Ref: https://github.com/inventree/InvenTree/pull/11012 - Ref: https://github.com/inventree/InvenTree/issues/11002 - Closes https://github.com/inventree/InvenTree/issues/10996 * Bump API version * Tweak unit tests * Ensure all fields are available when writing data * Handle case where request has *no* method
This commit is contained in:
@@ -1,11 +1,15 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 433
|
INVENTREE_API_VERSION = 434
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v434 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11021
|
||||||
|
- The "tags" fields (on various API endpoints) is now optional, and disabled by default
|
||||||
|
- To request tags information, add "tags=true" to the API request query parameters
|
||||||
|
|
||||||
v433 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11023
|
v433 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11023
|
||||||
- "substitutes" field on the BomItem API endpoint is now excluded by default
|
- "substitutes" field on the BomItem API endpoint is now excluded by default
|
||||||
- Add "?substitutes=true" query parameter to include substitute parts in BomItem API endpoint(s)
|
- Add "?substitutes=true" query parameter to include substitute parts in BomItem API endpoint(s)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from rest_framework.fields import empty
|
|||||||
from rest_framework.mixins import ListModelMixin
|
from rest_framework.mixins import ListModelMixin
|
||||||
from rest_framework.serializers import DecimalField
|
from rest_framework.serializers import DecimalField
|
||||||
from rest_framework.utils import model_meta
|
from rest_framework.utils import model_meta
|
||||||
from taggit.serializers import TaggitSerializer
|
from taggit.serializers import TaggitSerializer, TagListSerializerField
|
||||||
|
|
||||||
import common.models as common_models
|
import common.models as common_models
|
||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
@@ -211,6 +211,12 @@ class FilterableSerializerMixin:
|
|||||||
if getattr(self, '_exporting_data', False):
|
if getattr(self, '_exporting_data', False):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Skip filtering for a write requests - all fields should be present for data creation
|
||||||
|
if request := self.context.get('request', None):
|
||||||
|
if method := getattr(request, 'method', None):
|
||||||
|
if str(method).lower() in ['post', 'put', 'patch']:
|
||||||
|
return
|
||||||
|
|
||||||
# Throw out fields which are not requested (either by default or explicitly)
|
# 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():
|
||||||
# See `enable_filter` where` is_filterable and is_filterable_vals are set
|
# See `enable_filter` where` is_filterable and is_filterable_vals are set
|
||||||
@@ -253,6 +259,13 @@ class FilterableIntegerField(FilterableSerializerField, serializers.IntegerField
|
|||||||
"""Custom IntegerField which allows filtering."""
|
"""Custom IntegerField which allows filtering."""
|
||||||
|
|
||||||
|
|
||||||
|
class FilterableTagListField(FilterableSerializerField, TagListSerializerField):
|
||||||
|
"""Custom TagListSerializerField which allows filtering."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Empty Meta class."""
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1238,7 +1238,6 @@ class BuildItemSerializer(
|
|||||||
filter_name='stock_detail',
|
filter_name='stock_detail',
|
||||||
prefetch_fields=[
|
prefetch_fields=[
|
||||||
'stock_item',
|
'stock_item',
|
||||||
'stock_item__tags',
|
|
||||||
'stock_item__part',
|
'stock_item__part',
|
||||||
'stock_item__supplier_part',
|
'stock_item__supplier_part',
|
||||||
'stock_item__supplier_part__manufacturer_part',
|
'stock_item__supplier_part__manufacturer_part',
|
||||||
@@ -1257,7 +1256,7 @@ class BuildItemSerializer(
|
|||||||
allow_null=True,
|
allow_null=True,
|
||||||
),
|
),
|
||||||
True,
|
True,
|
||||||
prefetch_fields=['stock_item__location', 'stock_item__location__tags'],
|
prefetch_fields=['stock_item__location'],
|
||||||
)
|
)
|
||||||
|
|
||||||
build_detail = enable_filter(
|
build_detail = enable_filter(
|
||||||
@@ -1389,7 +1388,6 @@ class BuildLineSerializer(
|
|||||||
'allocations__stock_item__supplier_part',
|
'allocations__stock_item__supplier_part',
|
||||||
'allocations__stock_item__supplier_part__manufacturer_part',
|
'allocations__stock_item__supplier_part__manufacturer_part',
|
||||||
'allocations__stock_item__location',
|
'allocations__stock_item__location',
|
||||||
'allocations__stock_item__tags',
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -380,3 +380,21 @@ def enable_parameters_filter():
|
|||||||
'parameters_list__template',
|
'parameters_list__template',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def enable_tags_filter(default: bool = False):
|
||||||
|
"""Add an optional 'tags' field to an API serializer.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
default: If True, enable the filter by default.
|
||||||
|
|
||||||
|
If applied, this field will automatically prefetch the 'tags' relationship.
|
||||||
|
"""
|
||||||
|
from InvenTree.serializers import FilterableTagListField
|
||||||
|
|
||||||
|
return InvenTree.serializers.enable_filter(
|
||||||
|
FilterableTagListField(required=False),
|
||||||
|
default,
|
||||||
|
filter_name='tags',
|
||||||
|
prefetch_fields=['tags', 'tagged_items', 'tagged_items__tag'],
|
||||||
|
)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from error_report.models import Error
|
|||||||
from flags.state import flag_state
|
from flags.state import flag_state
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from taggit.serializers import TagListSerializerField
|
|
||||||
|
|
||||||
|
import common.filters
|
||||||
import common.models as common_models
|
import common.models as common_models
|
||||||
import common.validators
|
import common.validators
|
||||||
import generic.states.custom
|
import generic.states.custom
|
||||||
@@ -612,7 +612,7 @@ class FailedTaskSerializer(InvenTreeModelSerializer):
|
|||||||
result = serializers.CharField()
|
result = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
class AttachmentSerializer(InvenTreeModelSerializer):
|
class AttachmentSerializer(FilterableSerializerMixin, InvenTreeModelSerializer):
|
||||||
"""Serializer class for the Attachment model."""
|
"""Serializer class for the Attachment model."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -645,7 +645,7 @@ class AttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
'model_type'
|
'model_type'
|
||||||
].choices = common.validators.attachment_model_options()
|
].choices = common.validators.attachment_model_options()
|
||||||
|
|
||||||
tags = TagListSerializerField(required=False)
|
tags = common.filters.enable_tags_filter()
|
||||||
|
|
||||||
user_detail = UserSerializer(source='upload_user', read_only=True, many=False)
|
user_detail = UserSerializer(source='upload_user', read_only=True, many=False)
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class ManufacturerPartMixin(SerializerContextMixin):
|
|||||||
"""Return annotated queryset for the ManufacturerPart list endpoint."""
|
"""Return annotated queryset for the ManufacturerPart list endpoint."""
|
||||||
queryset = super().get_queryset(*args, **kwargs)
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
queryset = queryset.prefetch_related('supplier_parts', 'tags')
|
queryset = queryset.prefetch_related('supplier_parts')
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ class SupplierPartOutputOptions(OutputConfiguration):
|
|||||||
class SupplierPartMixin:
|
class SupplierPartMixin:
|
||||||
"""Mixin class for SupplierPart API endpoints."""
|
"""Mixin class for SupplierPart API endpoints."""
|
||||||
|
|
||||||
queryset = SupplierPart.objects.all().prefetch_related('tags')
|
queryset = SupplierPart.objects.all()
|
||||||
serializer_class = SupplierPartSerializer
|
serializer_class = SupplierPartSerializer
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
def get_queryset(self, *args, **kwargs):
|
||||||
@@ -331,9 +331,7 @@ class SupplierPartMixin:
|
|||||||
queryset = super().get_queryset(*args, **kwargs)
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
queryset = SupplierPartSerializer.annotate_queryset(queryset)
|
queryset = SupplierPartSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
queryset = queryset.prefetch_related(
|
queryset = queryset.prefetch_related('part', 'part__pricing_data')
|
||||||
'part', 'part__pricing_data', 'manufacturer_part__tags'
|
|
||||||
)
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|||||||
@@ -591,23 +591,6 @@ class ManufacturerPart(
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartManager(models.Manager):
|
|
||||||
"""Define custom SupplierPart objects manager.
|
|
||||||
|
|
||||||
The main purpose of this manager is to improve database hit as the
|
|
||||||
SupplierPart model involves A LOT of foreign keys lookups
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
"""Prefetch related fields when querying against the SupplierPart model."""
|
|
||||||
# Always prefetch related models
|
|
||||||
return (
|
|
||||||
super()
|
|
||||||
.get_queryset()
|
|
||||||
.prefetch_related('part', 'supplier', 'manufacturer_part__manufacturer')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SupplierPart(
|
class SupplierPart(
|
||||||
InvenTree.models.InvenTreeAttachmentMixin,
|
InvenTree.models.InvenTreeAttachmentMixin,
|
||||||
InvenTree.models.InvenTreeParameterMixin,
|
InvenTree.models.InvenTreeParameterMixin,
|
||||||
@@ -647,8 +630,6 @@ class SupplierPart(
|
|||||||
# This model was moved from the 'Part' app
|
# This model was moved from the 'Part' app
|
||||||
db_table = 'part_supplierpart'
|
db_table = 'part_supplierpart'
|
||||||
|
|
||||||
objects = SupplierPartManager()
|
|
||||||
|
|
||||||
tags = TaggableManager(blank=True)
|
tags = TaggableManager(blank=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from sql_util.utils import SubqueryCount
|
from sql_util.utils import SubqueryCount
|
||||||
from taggit.serializers import TagListSerializerField
|
|
||||||
|
|
||||||
import common.filters
|
import common.filters
|
||||||
import company.filters
|
import company.filters
|
||||||
@@ -260,7 +259,7 @@ class ManufacturerPartSerializer(
|
|||||||
'parameters',
|
'parameters',
|
||||||
]
|
]
|
||||||
|
|
||||||
tags = TagListSerializerField(required=False)
|
tags = common.filters.enable_tags_filter()
|
||||||
|
|
||||||
parameters = common.filters.enable_parameters_filter()
|
parameters = common.filters.enable_parameters_filter()
|
||||||
|
|
||||||
@@ -383,7 +382,7 @@ class SupplierPartSerializer(
|
|||||||
'pack_quantity_native',
|
'pack_quantity_native',
|
||||||
]
|
]
|
||||||
|
|
||||||
tags = TagListSerializerField(required=False)
|
tags = common.filters.enable_tags_filter()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize this serializer with extra detail fields as required."""
|
"""Initialize this serializer with extra detail fields as required."""
|
||||||
@@ -398,10 +397,9 @@ class SupplierPartSerializer(
|
|||||||
return
|
return
|
||||||
|
|
||||||
if brief:
|
if brief:
|
||||||
self.fields.pop('tags')
|
self.fields.pop('available', None)
|
||||||
self.fields.pop('available')
|
self.fields.pop('on_order', None)
|
||||||
self.fields.pop('on_order')
|
self.fields.pop('availability_updated', None)
|
||||||
self.fields.pop('availability_updated')
|
|
||||||
|
|
||||||
# Annotated field showing total in-stock quantity
|
# Annotated field showing total in-stock quantity
|
||||||
in_stock = serializers.FloatField(
|
in_stock = serializers.FloatField(
|
||||||
|
|||||||
@@ -563,7 +563,6 @@ class PurchaseOrderLineItemSerializer(
|
|||||||
'part__part',
|
'part__part',
|
||||||
'part__part__pricing_data',
|
'part__part__pricing_data',
|
||||||
'part__part__default_location',
|
'part__part__default_location',
|
||||||
'part__tags',
|
|
||||||
'part__supplier',
|
'part__supplier',
|
||||||
'part__manufacturer_part',
|
'part__manufacturer_part',
|
||||||
'part__manufacturer_part__manufacturer',
|
'part__manufacturer_part__manufacturer',
|
||||||
|
|||||||
@@ -1060,6 +1060,7 @@ class PartOutputOptions(OutputConfiguration):
|
|||||||
InvenTreeOutputOption('location_detail'),
|
InvenTreeOutputOption('location_detail'),
|
||||||
InvenTreeOutputOption('path_detail'),
|
InvenTreeOutputOption('path_detail'),
|
||||||
InvenTreeOutputOption('price_breaks'),
|
InvenTreeOutputOption('price_breaks'),
|
||||||
|
InvenTreeOutputOption('tags'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -347,29 +347,6 @@ def rename_part_image(instance, filename):
|
|||||||
return os.path.join(base, fname)
|
return os.path.join(base, fname)
|
||||||
|
|
||||||
|
|
||||||
class PartManager(TreeManager):
|
|
||||||
"""Defines a custom object manager for the Part model.
|
|
||||||
|
|
||||||
The main purpose of this manager is to reduce the number of database hits,
|
|
||||||
as the Part model has a large number of ForeignKey fields!
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
"""Perform default prefetch operations when accessing Part model from the database."""
|
|
||||||
return (
|
|
||||||
super()
|
|
||||||
.get_queryset()
|
|
||||||
.prefetch_related(
|
|
||||||
'category',
|
|
||||||
'pricing_data',
|
|
||||||
'category__parent',
|
|
||||||
'stock_items',
|
|
||||||
'builds',
|
|
||||||
'tags',
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
|
class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
|
||||||
"""A PartCategoryParameterTemplate creates a unique relationship between a PartCategory and a ParameterTemplate.
|
"""A PartCategoryParameterTemplate creates a unique relationship between a PartCategory and a ParameterTemplate.
|
||||||
|
|
||||||
@@ -540,7 +517,7 @@ class Part(
|
|||||||
NODE_PARENT_KEY = 'variant_of'
|
NODE_PARENT_KEY = 'variant_of'
|
||||||
IMAGE_RENAME = rename_part_image
|
IMAGE_RENAME = rename_part_image
|
||||||
|
|
||||||
objects = PartManager()
|
objects = TreeManager()
|
||||||
|
|
||||||
tags = TaggableManager(blank=True)
|
tags = TaggableManager(blank=True)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from djmoney.contrib.exchange.models import convert_money
|
|||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from sql_util.utils import SubqueryCount
|
from sql_util.utils import SubqueryCount
|
||||||
from taggit.serializers import TagListSerializerField
|
|
||||||
|
|
||||||
import common.currency
|
import common.currency
|
||||||
import common.filters
|
import common.filters
|
||||||
@@ -633,8 +632,6 @@ class PartSerializer(
|
|||||||
]
|
]
|
||||||
read_only_fields = ['barcode_hash', 'creation_date', 'creation_user']
|
read_only_fields = ['barcode_hash', 'creation_date', 'creation_user']
|
||||||
|
|
||||||
tags = TagListSerializerField(required=False)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Custom initialization method for PartSerializer.
|
"""Custom initialization method for PartSerializer.
|
||||||
|
|
||||||
@@ -910,6 +907,8 @@ class PartSerializer(
|
|||||||
|
|
||||||
parameters = common.filters.enable_parameters_filter()
|
parameters = common.filters.enable_parameters_filter()
|
||||||
|
|
||||||
|
tags = common.filters.enable_tags_filter()
|
||||||
|
|
||||||
price_breaks = enable_filter(
|
price_breaks = enable_filter(
|
||||||
PartSalePriceSerializer(
|
PartSalePriceSerializer(
|
||||||
source='salepricebreaks', many=True, read_only=True, allow_null=True
|
source='salepricebreaks', many=True, read_only=True, allow_null=True
|
||||||
|
|||||||
@@ -1734,8 +1734,6 @@ class PartDetailTests(PartImageTestMixin, PartAPITestBase):
|
|||||||
# Now, try to set the name to the *same* value
|
# Now, try to set the name to the *same* value
|
||||||
# 2021-06-22 this test is to check that the "duplicate part" checks don't do strange things
|
# 2021-06-22 this test is to check that the "duplicate part" checks don't do strange things
|
||||||
response = self.patch(url, {'name': 'a new better name'})
|
response = self.patch(url, {'name': 'a new better name'})
|
||||||
|
|
||||||
# Try to remove a tag
|
|
||||||
response = self.patch(url, {'tags': ['tag1']})
|
response = self.patch(url, {'tags': ['tag1']})
|
||||||
self.assertEqual(response.data['tags'], ['tag1'])
|
self.assertEqual(response.data['tags'], ['tag1'])
|
||||||
|
|
||||||
@@ -2051,8 +2049,7 @@ class PartListTests(PartAPITestBase):
|
|||||||
with CaptureQueriesContext(connection) as ctx:
|
with CaptureQueriesContext(connection) as ctx:
|
||||||
self.get(url, query, expected_code=200)
|
self.get(url, query, expected_code=200)
|
||||||
|
|
||||||
# No more than 25 database queries
|
self.assertLess(len(ctx), 30)
|
||||||
self.assertLess(len(ctx), 25)
|
|
||||||
|
|
||||||
# Test 'category_detail' annotation
|
# Test 'category_detail' annotation
|
||||||
for b in [False, True]:
|
for b in [False, True]:
|
||||||
@@ -2065,8 +2062,7 @@ class PartListTests(PartAPITestBase):
|
|||||||
if b and result['category'] is not None:
|
if b and result['category'] is not None:
|
||||||
self.assertIn('category_detail', result)
|
self.assertIn('category_detail', result)
|
||||||
|
|
||||||
# No more than 25 DB queries
|
self.assertLessEqual(len(ctx), 30)
|
||||||
self.assertLessEqual(len(ctx), 25)
|
|
||||||
|
|
||||||
def test_price_breaks(self):
|
def test_price_breaks(self):
|
||||||
"""Test that price_breaks parameter works correctly and efficiently."""
|
"""Test that price_breaks parameter works correctly and efficiently."""
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class CategoryTest(TestCase):
|
|||||||
def test_parameters(self):
|
def test_parameters(self):
|
||||||
"""Test that the Category parameters are correctly fetched."""
|
"""Test that the Category parameters are correctly fetched."""
|
||||||
# Check number of SQL queries to iterate other parameters
|
# Check number of SQL queries to iterate other parameters
|
||||||
with self.assertNumQueries(9):
|
with self.assertNumQueries(3):
|
||||||
# Prefetch: 3 queries (parts, parameters and parameters_template)
|
# Prefetch: 3 queries (parts, parameters and parameters_template)
|
||||||
fasteners = self.fasteners.prefetch_parts_parameters()
|
fasteners = self.fasteners.prefetch_parts_parameters()
|
||||||
# Iterate through all parts and parameters
|
# Iterate through all parts and parameters
|
||||||
|
|||||||
@@ -100,21 +100,6 @@ class StockLocationType(InvenTree.models.MetadataMixin, models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class StockLocationManager(TreeManager):
|
|
||||||
"""Custom database manager for the StockLocation class.
|
|
||||||
|
|
||||||
StockLocation querysets will automatically select related fields for performance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
"""Prefetch queryset to optimize db hits.
|
|
||||||
|
|
||||||
- Joins the StockLocationType by default for speedier icon access
|
|
||||||
"""
|
|
||||||
# return super().get_queryset().select_related("location_type")
|
|
||||||
return super().get_queryset()
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocationReportContext(report.mixins.BaseReportContext):
|
class StockLocationReportContext(report.mixins.BaseReportContext):
|
||||||
"""Report context for the StockLocation model.
|
"""Report context for the StockLocation model.
|
||||||
|
|
||||||
@@ -151,7 +136,7 @@ class StockLocation(
|
|||||||
|
|
||||||
EXTRA_PATH_FIELDS = ['icon']
|
EXTRA_PATH_FIELDS = ['icon']
|
||||||
|
|
||||||
objects = StockLocationManager()
|
objects = TreeManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass defines extra model properties."""
|
"""Metaclass defines extra model properties."""
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ from drf_spectacular.utils import extend_schema_field
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from sql_util.utils import SubqueryCount, SubquerySum
|
from sql_util.utils import SubqueryCount, SubquerySum
|
||||||
from taggit.serializers import TagListSerializerField
|
|
||||||
|
|
||||||
import build.models
|
import build.models
|
||||||
|
import common.filters
|
||||||
import company.models
|
import company.models
|
||||||
import company.serializers as company_serializers
|
import company.serializers as company_serializers
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
@@ -496,7 +496,6 @@ class StockItemSerializer(
|
|||||||
'belongs_to',
|
'belongs_to',
|
||||||
'sales_order',
|
'sales_order',
|
||||||
'consumed_by',
|
'consumed_by',
|
||||||
'tags',
|
|
||||||
).select_related('part')
|
).select_related('part')
|
||||||
|
|
||||||
# Annotate the queryset with the total allocated to sales orders
|
# Annotate the queryset with the total allocated to sales orders
|
||||||
@@ -579,10 +578,8 @@ class StockItemSerializer(
|
|||||||
False,
|
False,
|
||||||
prefetch_fields=[
|
prefetch_fields=[
|
||||||
'supplier_part__supplier',
|
'supplier_part__supplier',
|
||||||
'supplier_part__manufacturer_part__manufacturer',
|
|
||||||
'supplier_part__manufacturer_part__tags',
|
|
||||||
'supplier_part__purchase_order_line_items',
|
'supplier_part__purchase_order_line_items',
|
||||||
'supplier_part__tags',
|
'supplier_part__manufacturer_part__manufacturer',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -655,7 +652,7 @@ class StockItemSerializer(
|
|||||||
source='sales_order.reference', read_only=True, allow_null=True
|
source='sales_order.reference', read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
tags = TagListSerializerField(required=False)
|
tags = common.filters.enable_tags_filter()
|
||||||
|
|
||||||
|
|
||||||
class SerializeStockItemSerializer(serializers.Serializer):
|
class SerializeStockItemSerializer(serializers.Serializer):
|
||||||
@@ -1196,7 +1193,7 @@ class LocationSerializer(
|
|||||||
|
|
||||||
level = serializers.IntegerField(read_only=True)
|
level = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
tags = TagListSerializerField(required=False)
|
tags = common.filters.enable_tags_filter()
|
||||||
|
|
||||||
path = enable_filter(
|
path = enable_filter(
|
||||||
FilterableListField(
|
FilterableListField(
|
||||||
|
|||||||
Reference in New Issue
Block a user