mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
Check for schema generation state when removing fields (#9236)
* Ensure notes are not removed when generating schema * Skip remaining conditional field removals when generating schema, remove removable fields from required lists * Update API version, add schema gen state check for api-doc endpoint * Add test for generate schema state * Add test for schema postprocessing function * Filter nullable + read_only fields out of schema required lists --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
parent
cdb445583b
commit
d7aa5e45b9
@ -1,13 +1,17 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 320
|
INVENTREE_API_VERSION = 321
|
||||||
|
|
||||||
"""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 = """
|
||||||
|
|
||||||
|
v321 - 2025-03-06 : https://github.com/inventree/InvenTree/pull/9236
|
||||||
|
- Adds conditionally-returned fields to the schema to match API behavior
|
||||||
|
- Removes required flag for nullable read-only fields to match API behavior
|
||||||
|
|
||||||
v320 - 2025-03-05 : https://github.com/inventree/InvenTree/pull/9243
|
v320 - 2025-03-05 : https://github.com/inventree/InvenTree/pull/9243
|
||||||
- Link fields are now up to 2000 chars long
|
- Link fields are now up to 2000 chars long
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Functions to check if certain parts of InvenTree are ready."""
|
"""Functions to check if certain parts of InvenTree are ready."""
|
||||||
|
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -44,6 +45,14 @@ def isRunningBackup():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def isGeneratingSchema():
|
||||||
|
"""Return true if schema generation is being executed."""
|
||||||
|
if 'schema' in sys.argv:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return any('drf_spectacular' in frame.filename for frame in inspect.stack())
|
||||||
|
|
||||||
|
|
||||||
def isInWorkerThread():
|
def isInWorkerThread():
|
||||||
"""Returns True if the current thread is a background worker thread."""
|
"""Returns True if the current thread is a background worker thread."""
|
||||||
return 'qcluster' in sys.argv
|
return 'qcluster' in sys.argv
|
||||||
|
23
src/backend/InvenTree/InvenTree/schema.py
Normal file
23
src/backend/InvenTree/InvenTree/schema.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""Schema processing functions for cleaning up generated schema."""
|
||||||
|
|
||||||
|
|
||||||
|
def postprocess_required_nullable(result, generator, request, public):
|
||||||
|
"""Un-require nullable fields.
|
||||||
|
|
||||||
|
Read-only values are all marked as required by spectacular, but InvenTree doesn't always include them in the response. This removes them from the required list to allow responses lacking read-only nullable fields to validate against the schema.
|
||||||
|
"""
|
||||||
|
# Process schema section
|
||||||
|
schemas = result.get('components', {}).get('schemas', {})
|
||||||
|
for schema in schemas.values():
|
||||||
|
required_fields = schema.get('required', [])
|
||||||
|
properties = schema.get('properties', {})
|
||||||
|
|
||||||
|
# copy list to allow removing from it while iterating
|
||||||
|
for field in list(required_fields):
|
||||||
|
field_dict = properties.get(field, {})
|
||||||
|
if field_dict.get('readOnly') and field_dict.get('nullable'):
|
||||||
|
required_fields.remove(field)
|
||||||
|
if 'required' in schema and len(required_fields) == 0:
|
||||||
|
schema.pop('required')
|
||||||
|
|
||||||
|
return result
|
@ -22,6 +22,7 @@ from rest_framework.utils import model_meta
|
|||||||
from taggit.serializers import TaggitSerializer
|
from taggit.serializers import TaggitSerializer
|
||||||
|
|
||||||
import common.models as common_models
|
import common.models as common_models
|
||||||
|
import InvenTree.ready
|
||||||
from common.currency import currency_code_default, currency_code_mappings
|
from common.currency import currency_code_default, currency_code_mappings
|
||||||
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
|
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
|
||||||
|
|
||||||
@ -468,7 +469,10 @@ class NotesFieldMixin:
|
|||||||
|
|
||||||
if hasattr(self, 'context'):
|
if hasattr(self, 'context'):
|
||||||
if view := self.context.get('view', None):
|
if view := self.context.get('view', None):
|
||||||
if issubclass(view.__class__, ListModelMixin):
|
if (
|
||||||
|
issubclass(view.__class__, ListModelMixin)
|
||||||
|
and not InvenTree.ready.isGeneratingSchema()
|
||||||
|
):
|
||||||
self.fields.pop('notes', None)
|
self.fields.pop('notes', None)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1423,6 +1423,10 @@ SPECTACULAR_SETTINGS = {
|
|||||||
'VERSION': str(inventreeApiVersion()),
|
'VERSION': str(inventreeApiVersion()),
|
||||||
'SERVE_INCLUDE_SCHEMA': False,
|
'SERVE_INCLUDE_SCHEMA': False,
|
||||||
'SCHEMA_PATH_PREFIX': '/api/',
|
'SCHEMA_PATH_PREFIX': '/api/',
|
||||||
|
'POSTPROCESSING_HOOKS': [
|
||||||
|
'drf_spectacular.hooks.postprocess_schema_enums',
|
||||||
|
'InvenTree.schema.postprocess_required_nullable',
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
if SITE_URL and not TESTING:
|
if SITE_URL and not TESTING:
|
||||||
|
@ -37,7 +37,7 @@ from InvenTree.unit_test import InvenTreeTestCase, in_env_context
|
|||||||
from part.models import Part, PartCategory
|
from part.models import Part, PartCategory
|
||||||
from stock.models import StockItem, StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
|
|
||||||
from . import config, helpers, ready, status, version
|
from . import config, helpers, ready, schema, status, version
|
||||||
from .tasks import offload_task
|
from .tasks import offload_task
|
||||||
from .validators import validate_overage
|
from .validators import validate_overage
|
||||||
|
|
||||||
@ -1116,6 +1116,10 @@ class TestStatus(TestCase):
|
|||||||
"""Test isImportingData check."""
|
"""Test isImportingData check."""
|
||||||
self.assertEqual(ready.isImportingData(), False)
|
self.assertEqual(ready.isImportingData(), False)
|
||||||
|
|
||||||
|
def test_GeneratingSchema(self):
|
||||||
|
"""Test isGeneratingSchema check."""
|
||||||
|
self.assertEqual(ready.isGeneratingSchema(), False)
|
||||||
|
|
||||||
|
|
||||||
class TestSettings(InvenTreeTestCase):
|
class TestSettings(InvenTreeTestCase):
|
||||||
"""Unit tests for settings."""
|
"""Unit tests for settings."""
|
||||||
@ -1631,3 +1635,62 @@ class ClassProviderMixinTest(TestCase):
|
|||||||
def test_get_is_builtin(self):
|
def test_get_is_builtin(self):
|
||||||
"""Test the get_is_builtin function."""
|
"""Test the get_is_builtin function."""
|
||||||
self.assertTrue(self.TestClass.get_is_builtin())
|
self.assertTrue(self.TestClass.get_is_builtin())
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaPostprocessingTest(TestCase):
|
||||||
|
"""Tests for schema postprocessing functions."""
|
||||||
|
|
||||||
|
def create_result_structure(self):
|
||||||
|
"""Create a schema dict structure representative of the spectacular-generated on."""
|
||||||
|
return {
|
||||||
|
'openapi': {},
|
||||||
|
'info': {},
|
||||||
|
'paths': {},
|
||||||
|
'components': {
|
||||||
|
'examples': {},
|
||||||
|
'parameters': {},
|
||||||
|
'requestBodies': {},
|
||||||
|
'responses': {},
|
||||||
|
'schemas': {},
|
||||||
|
'securitySchemes': {},
|
||||||
|
},
|
||||||
|
'servers': {},
|
||||||
|
'externalDocs': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_postprocess_required_nullable(self):
|
||||||
|
"""Verify that only selected elements are removed from required list."""
|
||||||
|
result_in = self.create_result_structure()
|
||||||
|
schemas_in = result_in.get('components').get('schemas')
|
||||||
|
|
||||||
|
schemas_in['SalesOrder'] = {
|
||||||
|
'properties': {
|
||||||
|
'pk': {'type': 'integer', 'readOnly': True, 'title': 'ID'},
|
||||||
|
'customer_detail': {
|
||||||
|
'allOf': [{'$ref': '#/components/schemas/CompanyBrief'}],
|
||||||
|
'readOnly': True,
|
||||||
|
'nullable': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'required': ['customer_detail', 'pk'],
|
||||||
|
}
|
||||||
|
|
||||||
|
schemas_in['SalesOrderShipment'] = {
|
||||||
|
'properties': {
|
||||||
|
'order_detail': {
|
||||||
|
'allOf': [{'$ref': '#/components/schemas/SalesOrder'}],
|
||||||
|
'readOnly': True,
|
||||||
|
'nullable': True,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['order_detail'],
|
||||||
|
}
|
||||||
|
|
||||||
|
result_out = schema.postprocess_required_nullable(result_in, {}, {}, {})
|
||||||
|
schemas_out = result_out.get('components').get('schemas')
|
||||||
|
|
||||||
|
# only intended elements removed (read-only, required, and object type)
|
||||||
|
self.assertIn('pk', schemas_out.get('SalesOrder')['required'])
|
||||||
|
self.assertNotIn('customer_detail', schemas_out.get('SalesOrder')['required'])
|
||||||
|
# required key removed when empty
|
||||||
|
self.assertNotIn('required', schemas_out.get('SalesOrderShipment'))
|
||||||
|
@ -32,6 +32,7 @@ from common.serializers import ProjectCodeSerializer
|
|||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
|
from InvenTree.ready import isGeneratingSchema
|
||||||
from InvenTree.serializers import (
|
from InvenTree.serializers import (
|
||||||
InvenTreeDecimalField,
|
InvenTreeDecimalField,
|
||||||
InvenTreeModelSerializer,
|
InvenTreeModelSerializer,
|
||||||
@ -126,7 +127,9 @@ class BuildSerializer(
|
|||||||
|
|
||||||
issued_by_detail = UserSerializer(source='issued_by', read_only=True)
|
issued_by_detail = UserSerializer(source='issued_by', read_only=True)
|
||||||
|
|
||||||
responsible_detail = OwnerSerializer(source='responsible', read_only=True)
|
responsible_detail = OwnerSerializer(
|
||||||
|
source='responsible', read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
barcode_hash = serializers.CharField(read_only=True)
|
barcode_hash = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
@ -177,6 +180,9 @@ class BuildSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not create:
|
if not create:
|
||||||
self.fields.pop('create_child_builds', None)
|
self.fields.pop('create_child_builds', None)
|
||||||
|
|
||||||
@ -1204,6 +1210,9 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not part_detail:
|
if not part_detail:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
@ -1246,11 +1255,12 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
|||||||
source='stock_item.part',
|
source='stock_item.part',
|
||||||
many=False,
|
many=False,
|
||||||
read_only=True,
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
pricing=False,
|
pricing=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
stock_item_detail = StockItemSerializerBrief(
|
stock_item_detail = StockItemSerializerBrief(
|
||||||
source='stock_item', read_only=True, label=_('Stock Item')
|
source='stock_item', read_only=True, allow_null=True, label=_('Stock Item')
|
||||||
)
|
)
|
||||||
|
|
||||||
location = serializers.PrimaryKeyRelatedField(
|
location = serializers.PrimaryKeyRelatedField(
|
||||||
@ -1258,11 +1268,18 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
|||||||
)
|
)
|
||||||
|
|
||||||
location_detail = LocationBriefSerializer(
|
location_detail = LocationBriefSerializer(
|
||||||
label=_('Location'), source='stock_item.location', read_only=True
|
label=_('Location'),
|
||||||
|
source='stock_item.location',
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
build_detail = BuildSerializer(
|
build_detail = BuildSerializer(
|
||||||
label=_('Build'), source='build_line.build', many=False, read_only=True
|
label=_('Build'),
|
||||||
|
source='build_line.build',
|
||||||
|
many=False,
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
supplier_part_detail = company.serializers.SupplierPartSerializer(
|
supplier_part_detail = company.serializers.SupplierPartSerializer(
|
||||||
@ -1270,6 +1287,7 @@ class BuildItemSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
|||||||
source='stock_item.supplier_part',
|
source='stock_item.supplier_part',
|
||||||
many=False,
|
many=False,
|
||||||
read_only=True,
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
brief=True,
|
brief=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1339,6 +1357,9 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not part_detail:
|
if not part_detail:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
|
@ -333,7 +333,9 @@ class ProjectCodeSerializer(DataImportExportSerializerMixin, InvenTreeModelSeria
|
|||||||
model = common_models.ProjectCode
|
model = common_models.ProjectCode
|
||||||
fields = ['pk', 'code', 'description', 'responsible', 'responsible_detail']
|
fields = ['pk', 'code', 'description', 'responsible', 'responsible_detail']
|
||||||
|
|
||||||
responsible_detail = OwnerSerializer(source='responsible', read_only=True)
|
responsible_detail = OwnerSerializer(
|
||||||
|
source='responsible', read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
|
@ -14,6 +14,7 @@ import part.filters
|
|||||||
import part.serializers as part_serializers
|
import part.serializers as part_serializers
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
from importer.registry import register_importer
|
from importer.registry import register_importer
|
||||||
|
from InvenTree.ready import isGeneratingSchema
|
||||||
from InvenTree.serializers import (
|
from InvenTree.serializers import (
|
||||||
InvenTreeCurrencySerializer,
|
InvenTreeCurrencySerializer,
|
||||||
InvenTreeDecimalField,
|
InvenTreeDecimalField,
|
||||||
@ -256,6 +257,9 @@ class ManufacturerPartSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if part_detail is not True:
|
if part_detail is not True:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
@ -266,14 +270,14 @@ class ManufacturerPartSerializer(
|
|||||||
self.fields.pop('pretty_name', None)
|
self.fields.pop('pretty_name', None)
|
||||||
|
|
||||||
part_detail = part_serializers.PartBriefSerializer(
|
part_detail = part_serializers.PartBriefSerializer(
|
||||||
source='part', many=False, read_only=True
|
source='part', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
manufacturer_detail = CompanyBriefSerializer(
|
manufacturer_detail = CompanyBriefSerializer(
|
||||||
source='manufacturer', many=False, read_only=True
|
source='manufacturer', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
pretty_name = serializers.CharField(read_only=True)
|
pretty_name = serializers.CharField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
manufacturer = serializers.PrimaryKeyRelatedField(
|
manufacturer = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=Company.objects.filter(is_manufacturer=True)
|
queryset=Company.objects.filter(is_manufacturer=True)
|
||||||
@ -306,11 +310,11 @@ class ManufacturerPartParameterSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if not man_detail:
|
if not man_detail and not isGeneratingSchema():
|
||||||
self.fields.pop('manufacturer_part_detail', None)
|
self.fields.pop('manufacturer_part_detail', None)
|
||||||
|
|
||||||
manufacturer_part_detail = ManufacturerPartSerializer(
|
manufacturer_part_detail = ManufacturerPartSerializer(
|
||||||
source='manufacturer_part', many=False, read_only=True
|
source='manufacturer_part', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -389,6 +393,9 @@ class SupplierPartSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if part_detail is not True:
|
if part_detail is not True:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
@ -409,20 +416,28 @@ class SupplierPartSerializer(
|
|||||||
self.fields.pop('availability_updated')
|
self.fields.pop('availability_updated')
|
||||||
|
|
||||||
# Annotated field showing total in-stock quantity
|
# Annotated field showing total in-stock quantity
|
||||||
in_stock = serializers.FloatField(read_only=True, label=_('In Stock'))
|
in_stock = serializers.FloatField(
|
||||||
|
read_only=True, allow_null=True, label=_('In Stock')
|
||||||
|
)
|
||||||
|
|
||||||
on_order = serializers.FloatField(read_only=True, label=_('On Order'))
|
on_order = serializers.FloatField(
|
||||||
|
read_only=True, allow_null=True, label=_('On Order')
|
||||||
|
)
|
||||||
|
|
||||||
available = serializers.FloatField(required=False, label=_('Available'))
|
available = serializers.FloatField(required=False, label=_('Available'))
|
||||||
|
|
||||||
pack_quantity_native = serializers.FloatField(read_only=True)
|
pack_quantity_native = serializers.FloatField(read_only=True)
|
||||||
|
|
||||||
part_detail = part_serializers.PartBriefSerializer(
|
part_detail = part_serializers.PartBriefSerializer(
|
||||||
label=_('Part'), source='part', many=False, read_only=True
|
label=_('Part'), source='part', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
supplier_detail = CompanyBriefSerializer(
|
supplier_detail = CompanyBriefSerializer(
|
||||||
label=_('Supplier'), source='supplier', many=False, read_only=True
|
label=_('Supplier'),
|
||||||
|
source='supplier',
|
||||||
|
many=False,
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
manufacturer_detail = CompanyBriefSerializer(
|
manufacturer_detail = CompanyBriefSerializer(
|
||||||
@ -430,9 +445,10 @@ class SupplierPartSerializer(
|
|||||||
source='manufacturer_part.manufacturer',
|
source='manufacturer_part.manufacturer',
|
||||||
many=False,
|
many=False,
|
||||||
read_only=True,
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
pretty_name = serializers.CharField(read_only=True)
|
pretty_name = serializers.CharField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
supplier = serializers.PrimaryKeyRelatedField(
|
supplier = serializers.PrimaryKeyRelatedField(
|
||||||
label=_('Supplier'), queryset=Company.objects.filter(is_supplier=True)
|
label=_('Supplier'), queryset=Company.objects.filter(is_supplier=True)
|
||||||
@ -443,6 +459,7 @@ class SupplierPartSerializer(
|
|||||||
source='manufacturer_part',
|
source='manufacturer_part',
|
||||||
part_detail=False,
|
part_detail=False,
|
||||||
read_only=True,
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
MPN = serializers.CharField(
|
MPN = serializers.CharField(
|
||||||
@ -529,6 +546,9 @@ class SupplierPriceBreakSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not supplier_detail:
|
if not supplier_detail:
|
||||||
self.fields.pop('supplier_detail', None)
|
self.fields.pop('supplier_detail', None)
|
||||||
|
|
||||||
@ -553,10 +573,10 @@ class SupplierPriceBreakSerializer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
supplier_detail = CompanyBriefSerializer(
|
supplier_detail = CompanyBriefSerializer(
|
||||||
source='part.supplier', many=False, read_only=True
|
source='part.supplier', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Detail serializer for SupplierPart
|
# Detail serializer for SupplierPart
|
||||||
part_detail = SupplierPartSerializer(
|
part_detail = SupplierPartSerializer(
|
||||||
source='part', brief=True, many=False, read_only=True
|
source='part', brief=True, many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
@ -44,6 +44,7 @@ from InvenTree.helpers import (
|
|||||||
normalize,
|
normalize,
|
||||||
str2bool,
|
str2bool,
|
||||||
)
|
)
|
||||||
|
from InvenTree.ready import isGeneratingSchema
|
||||||
from InvenTree.serializers import (
|
from InvenTree.serializers import (
|
||||||
InvenTreeCurrencySerializer,
|
InvenTreeCurrencySerializer,
|
||||||
InvenTreeDecimalField,
|
InvenTreeDecimalField,
|
||||||
@ -128,11 +129,13 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria
|
|||||||
reference = serializers.CharField(required=True)
|
reference = serializers.CharField(required=True)
|
||||||
|
|
||||||
# Detail for point-of-contact field
|
# Detail for point-of-contact field
|
||||||
contact_detail = ContactSerializer(source='contact', many=False, read_only=True)
|
contact_detail = ContactSerializer(
|
||||||
|
source='contact', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
# Detail for responsible field
|
# Detail for responsible field
|
||||||
responsible_detail = OwnerSerializer(
|
responsible_detail = OwnerSerializer(
|
||||||
source='responsible', read_only=True, many=False
|
source='responsible', read_only=True, allow_null=True, many=False
|
||||||
)
|
)
|
||||||
|
|
||||||
project_code_label = serializers.CharField(
|
project_code_label = serializers.CharField(
|
||||||
@ -149,7 +152,7 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria
|
|||||||
|
|
||||||
# Detail for address field
|
# Detail for address field
|
||||||
address_detail = AddressBriefSerializer(
|
address_detail = AddressBriefSerializer(
|
||||||
source='address', many=False, read_only=True
|
source='address', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Boolean field indicating if this order is overdue (Note: must be annotated)
|
# Boolean field indicating if this order is overdue (Note: must be annotated)
|
||||||
@ -275,7 +278,7 @@ class AbstractExtraLineSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if order_detail is not True:
|
if order_detail is not True and not isGeneratingSchema():
|
||||||
self.fields.pop('order_detail', None)
|
self.fields.pop('order_detail', None)
|
||||||
|
|
||||||
quantity = serializers.FloatField()
|
quantity = serializers.FloatField()
|
||||||
@ -343,7 +346,7 @@ class PurchaseOrderSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if supplier_detail is not True:
|
if supplier_detail is not True and not isGeneratingSchema():
|
||||||
self.fields.pop('supplier_detail', None)
|
self.fields.pop('supplier_detail', None)
|
||||||
|
|
||||||
def skip_create_fields(self):
|
def skip_create_fields(self):
|
||||||
@ -384,7 +387,7 @@ class PurchaseOrderSerializer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
supplier_detail = CompanyBriefSerializer(
|
supplier_detail = CompanyBriefSerializer(
|
||||||
source='supplier', many=False, read_only=True
|
source='supplier', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -518,6 +521,9 @@ class PurchaseOrderLineItemSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if part_detail is not True:
|
if part_detail is not True:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
self.fields.pop('supplier_part_detail', None)
|
self.fields.pop('supplier_part_detail', None)
|
||||||
@ -608,11 +614,11 @@ class PurchaseOrderLineItemSerializer(
|
|||||||
total_price = serializers.FloatField(read_only=True)
|
total_price = serializers.FloatField(read_only=True)
|
||||||
|
|
||||||
part_detail = PartBriefSerializer(
|
part_detail = PartBriefSerializer(
|
||||||
source='get_base_part', many=False, read_only=True
|
source='get_base_part', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
supplier_part_detail = SupplierPartSerializer(
|
supplier_part_detail = SupplierPartSerializer(
|
||||||
source='part', brief=True, many=False, read_only=True
|
source='part', brief=True, many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
purchase_price = InvenTreeMoneySerializer(allow_null=True)
|
purchase_price = InvenTreeMoneySerializer(allow_null=True)
|
||||||
@ -633,7 +639,9 @@ class PurchaseOrderLineItemSerializer(
|
|||||||
help_text=_('Purchase price currency')
|
help_text=_('Purchase price currency')
|
||||||
)
|
)
|
||||||
|
|
||||||
order_detail = PurchaseOrderSerializer(source='order', read_only=True, many=False)
|
order_detail = PurchaseOrderSerializer(
|
||||||
|
source='order', read_only=True, allow_null=True, many=False
|
||||||
|
)
|
||||||
|
|
||||||
merge_items = serializers.BooleanField(
|
merge_items = serializers.BooleanField(
|
||||||
label=_('Merge Items'),
|
label=_('Merge Items'),
|
||||||
@ -699,7 +707,9 @@ class PurchaseOrderExtraLineSerializer(
|
|||||||
):
|
):
|
||||||
"""Serializer for a PurchaseOrderExtraLine object."""
|
"""Serializer for a PurchaseOrderExtraLine object."""
|
||||||
|
|
||||||
order_detail = PurchaseOrderSerializer(source='order', many=False, read_only=True)
|
order_detail = PurchaseOrderSerializer(
|
||||||
|
source='order', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(AbstractExtraLineMeta):
|
class Meta(AbstractExtraLineMeta):
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
@ -1025,7 +1035,7 @@ class SalesOrderSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if customer_detail is not True:
|
if customer_detail is not True and not isGeneratingSchema():
|
||||||
self.fields.pop('customer_detail', None)
|
self.fields.pop('customer_detail', None)
|
||||||
|
|
||||||
def skip_create_fields(self):
|
def skip_create_fields(self):
|
||||||
@ -1069,7 +1079,7 @@ class SalesOrderSerializer(
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
customer_detail = CompanyBriefSerializer(
|
customer_detail = CompanyBriefSerializer(
|
||||||
source='customer', many=False, read_only=True
|
source='customer', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
shipments_count = serializers.IntegerField(read_only=True, label=_('Shipments'))
|
shipments_count = serializers.IntegerField(read_only=True, label=_('Shipments'))
|
||||||
@ -1135,6 +1145,9 @@ class SalesOrderLineItemSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if part_detail is not True:
|
if part_detail is not True:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
@ -1233,10 +1246,14 @@ class SalesOrderLineItemSerializer(
|
|||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
order_detail = SalesOrderSerializer(source='order', many=False, read_only=True)
|
order_detail = SalesOrderSerializer(
|
||||||
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
source='order', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
part_detail = PartBriefSerializer(
|
||||||
|
source='part', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
customer_detail = CompanyBriefSerializer(
|
customer_detail = CompanyBriefSerializer(
|
||||||
source='order.customer', many=False, read_only=True
|
source='order.customer', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Annotated fields
|
# Annotated fields
|
||||||
@ -1289,7 +1306,7 @@ class SalesOrderShipmentSerializer(NotesFieldMixin, InvenTreeModelSerializer):
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if not order_detail:
|
if not order_detail and not isGeneratingSchema():
|
||||||
self.fields.pop('order_detail', None)
|
self.fields.pop('order_detail', None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -1306,7 +1323,9 @@ class SalesOrderShipmentSerializer(NotesFieldMixin, InvenTreeModelSerializer):
|
|||||||
read_only=True, label=_('Allocated Items')
|
read_only=True, label=_('Allocated Items')
|
||||||
)
|
)
|
||||||
|
|
||||||
order_detail = SalesOrderSerializer(source='order', read_only=True, many=False)
|
order_detail = SalesOrderSerializer(
|
||||||
|
source='order', read_only=True, allow_null=True, many=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
||||||
@ -1352,6 +1371,9 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not order_detail:
|
if not order_detail:
|
||||||
self.fields.pop('order_detail', None)
|
self.fields.pop('order_detail', None)
|
||||||
|
|
||||||
@ -1378,16 +1400,20 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Extra detail fields
|
# Extra detail fields
|
||||||
order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True)
|
order_detail = SalesOrderSerializer(
|
||||||
part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True)
|
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
|
||||||
|
)
|
||||||
item_detail = stock.serializers.StockItemSerializerBrief(
|
item_detail = stock.serializers.StockItemSerializerBrief(
|
||||||
source='item', many=False, read_only=True
|
source='item', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
location_detail = stock.serializers.LocationBriefSerializer(
|
location_detail = stock.serializers.LocationBriefSerializer(
|
||||||
source='item.location', many=False, read_only=True
|
source='item.location', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
customer_detail = CompanyBriefSerializer(
|
customer_detail = CompanyBriefSerializer(
|
||||||
source='line.order.customer', many=False, read_only=True
|
source='line.order.customer', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
shipment_detail = SalesOrderShipmentSerializer(
|
shipment_detail = SalesOrderShipmentSerializer(
|
||||||
@ -1833,7 +1859,9 @@ class SalesOrderExtraLineSerializer(
|
|||||||
|
|
||||||
model = order.models.SalesOrderExtraLine
|
model = order.models.SalesOrderExtraLine
|
||||||
|
|
||||||
order_detail = SalesOrderSerializer(source='order', many=False, read_only=True)
|
order_detail = SalesOrderSerializer(
|
||||||
|
source='order', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
@ -1869,7 +1897,7 @@ class ReturnOrderSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if customer_detail is not True:
|
if customer_detail is not True and not isGeneratingSchema():
|
||||||
self.fields.pop('customer_detail', None)
|
self.fields.pop('customer_detail', None)
|
||||||
|
|
||||||
def skip_create_fields(self):
|
def skip_create_fields(self):
|
||||||
@ -1902,7 +1930,7 @@ class ReturnOrderSerializer(
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
customer_detail = CompanyBriefSerializer(
|
customer_detail = CompanyBriefSerializer(
|
||||||
source='customer', many=False, read_only=True
|
source='customer', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -2081,6 +2109,9 @@ class ReturnOrderLineItemSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not order_detail:
|
if not order_detail:
|
||||||
self.fields.pop('order_detail', None)
|
self.fields.pop('order_detail', None)
|
||||||
|
|
||||||
@ -2090,17 +2121,21 @@ class ReturnOrderLineItemSerializer(
|
|||||||
if not part_detail:
|
if not part_detail:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
order_detail = ReturnOrderSerializer(source='order', many=False, read_only=True)
|
order_detail = ReturnOrderSerializer(
|
||||||
|
source='order', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
quantity = serializers.FloatField(
|
quantity = serializers.FloatField(
|
||||||
label=_('Quantity'), help_text=_('Quantity to return')
|
label=_('Quantity'), help_text=_('Quantity to return')
|
||||||
)
|
)
|
||||||
|
|
||||||
item_detail = stock.serializers.StockItemSerializer(
|
item_detail = stock.serializers.StockItemSerializer(
|
||||||
source='item', many=False, read_only=True
|
source='item', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True)
|
part_detail = PartBriefSerializer(
|
||||||
|
source='item.part', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
price = InvenTreeMoneySerializer(allow_null=True)
|
price = InvenTreeMoneySerializer(allow_null=True)
|
||||||
price_currency = InvenTreeCurrencySerializer(help_text=_('Line price currency'))
|
price_currency = InvenTreeCurrencySerializer(help_text=_('Line price currency'))
|
||||||
@ -2117,4 +2152,6 @@ class ReturnOrderExtraLineSerializer(
|
|||||||
|
|
||||||
model = order.models.ReturnOrderExtraLine
|
model = order.models.ReturnOrderExtraLine
|
||||||
|
|
||||||
order_detail = ReturnOrderSerializer(source='order', many=False, read_only=True)
|
order_detail = ReturnOrderSerializer(
|
||||||
|
source='order', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
@ -35,6 +35,7 @@ import users.models
|
|||||||
from build.status_codes import BuildStatusGroups
|
from build.status_codes import BuildStatusGroups
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
from importer.registry import register_importer
|
from importer.registry import register_importer
|
||||||
|
from InvenTree.ready import isGeneratingSchema
|
||||||
from InvenTree.tasks import offload_task
|
from InvenTree.tasks import offload_task
|
||||||
from users.serializers import UserSerializer
|
from users.serializers import UserSerializer
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ class CategorySerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if not path_detail:
|
if not path_detail and not isGeneratingSchema():
|
||||||
self.fields.pop('path', None)
|
self.fields.pop('path', None)
|
||||||
|
|
||||||
def get_starred(self, category) -> bool:
|
def get_starred(self, category) -> bool:
|
||||||
@ -133,7 +134,10 @@ class CategorySerializer(
|
|||||||
starred = serializers.SerializerMethodField()
|
starred = serializers.SerializerMethodField()
|
||||||
|
|
||||||
path = serializers.ListField(
|
path = serializers.ListField(
|
||||||
child=serializers.DictField(), source='get_path', read_only=True
|
child=serializers.DictField(),
|
||||||
|
source='get_path',
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
icon = serializers.CharField(
|
icon = serializers.CharField(
|
||||||
@ -383,7 +387,7 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if not pricing:
|
if not pricing and not isGeneratingSchema():
|
||||||
self.fields.pop('pricing_min', None)
|
self.fields.pop('pricing_min', None)
|
||||||
self.fields.pop('pricing_max', None)
|
self.fields.pop('pricing_max', None)
|
||||||
|
|
||||||
@ -444,15 +448,20 @@ class PartParameterSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not part_detail:
|
if not part_detail:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
if not template_detail:
|
if not template_detail:
|
||||||
self.fields.pop('template_detail', None)
|
self.fields.pop('template_detail', None)
|
||||||
|
|
||||||
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
part_detail = PartBriefSerializer(
|
||||||
|
source='part', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
template_detail = PartParameterTemplateSerializer(
|
template_detail = PartParameterTemplateSerializer(
|
||||||
source='template', many=False, read_only=True
|
source='template', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -782,6 +791,9 @@ class PartSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not category_detail:
|
if not category_detail:
|
||||||
self.fields.pop('category_detail', None)
|
self.fields.pop('category_detail', None)
|
||||||
|
|
||||||
@ -923,14 +935,19 @@ 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 = CategorySerializer(source='category', many=False, read_only=True)
|
category_detail = CategorySerializer(
|
||||||
|
source='category', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
category_path = serializers.ListField(
|
category_path = serializers.ListField(
|
||||||
child=serializers.DictField(), source='category.get_path', read_only=True
|
child=serializers.DictField(),
|
||||||
|
source='category.get_path',
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
default_location_detail = DefaultLocationSerializer(
|
default_location_detail = DefaultLocationSerializer(
|
||||||
source='default_location', many=False, read_only=True
|
source='default_location', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
category_name = serializers.CharField(
|
category_name = serializers.CharField(
|
||||||
@ -1003,7 +1020,7 @@ class PartSerializer(
|
|||||||
source='pricing_data.updated', allow_null=True, read_only=True
|
source='pricing_data.updated', allow_null=True, read_only=True
|
||||||
)
|
)
|
||||||
|
|
||||||
parameters = PartParameterSerializer(many=True, read_only=True)
|
parameters = PartParameterSerializer(many=True, read_only=True, allow_null=True)
|
||||||
|
|
||||||
# Extra fields used only for creation of a new Part instance
|
# Extra fields used only for creation of a new Part instance
|
||||||
duplicate = DuplicatePartSerializer(
|
duplicate = DuplicatePartSerializer(
|
||||||
@ -1617,6 +1634,9 @@ class BomItemSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not part_detail:
|
if not part_detail:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
@ -1648,10 +1668,12 @@ class BomItemSerializer(
|
|||||||
help_text=_('Select the parent assembly'),
|
help_text=_('Select the parent assembly'),
|
||||||
)
|
)
|
||||||
|
|
||||||
substitutes = BomItemSubstituteSerializer(many=True, read_only=True)
|
substitutes = BomItemSubstituteSerializer(
|
||||||
|
many=True, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
part_detail = PartBriefSerializer(
|
part_detail = PartBriefSerializer(
|
||||||
source='part', label=_('Assembly'), many=False, read_only=True
|
source='part', label=_('Assembly'), many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
sub_part = serializers.PrimaryKeyRelatedField(
|
sub_part = serializers.PrimaryKeyRelatedField(
|
||||||
@ -1661,7 +1683,11 @@ class BomItemSerializer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
sub_part_detail = PartBriefSerializer(
|
sub_part_detail = PartBriefSerializer(
|
||||||
source='sub_part', label=_('Component'), many=False, read_only=True
|
source='sub_part',
|
||||||
|
label=_('Component'),
|
||||||
|
many=False,
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
on_order = serializers.FloatField(label=_('On Order'), read_only=True)
|
on_order = serializers.FloatField(label=_('On Order'), read_only=True)
|
||||||
@ -1867,7 +1893,9 @@ class CategoryParameterTemplateSerializer(
|
|||||||
source='parameter_template', many=False, read_only=True
|
source='parameter_template', many=False, read_only=True
|
||||||
)
|
)
|
||||||
|
|
||||||
category_detail = CategorySerializer(source='category', many=False, read_only=True)
|
category_detail = CategorySerializer(
|
||||||
|
source='category', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PartCopyBOMSerializer(serializers.Serializer):
|
class PartCopyBOMSerializer(serializers.Serializer):
|
||||||
|
@ -30,6 +30,7 @@ from common.settings import get_global_setting
|
|||||||
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
from generic.states.fields import InvenTreeCustomStatusSerializerMixin
|
||||||
from importer.mixins import DataImportExportSerializerMixin
|
from importer.mixins import DataImportExportSerializerMixin
|
||||||
from importer.registry import register_importer
|
from importer.registry import register_importer
|
||||||
|
from InvenTree.ready import isGeneratingSchema
|
||||||
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
|
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
|
||||||
from users.serializers import UserSerializer
|
from users.serializers import UserSerializer
|
||||||
|
|
||||||
@ -218,13 +219,16 @@ class StockItemTestResultSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if user_detail is not True:
|
if user_detail is not True:
|
||||||
self.fields.pop('user_detail', None)
|
self.fields.pop('user_detail', None)
|
||||||
|
|
||||||
if template_detail is not True:
|
if template_detail is not True:
|
||||||
self.fields.pop('template_detail', None)
|
self.fields.pop('template_detail', None)
|
||||||
|
|
||||||
user_detail = UserSerializer(source='user', read_only=True)
|
user_detail = UserSerializer(source='user', read_only=True, allow_null=True)
|
||||||
|
|
||||||
template = serializers.PrimaryKeyRelatedField(
|
template = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=part_models.PartTestTemplate.objects.all(),
|
queryset=part_models.PartTestTemplate.objects.all(),
|
||||||
@ -236,7 +240,7 @@ class StockItemTestResultSerializer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
template_detail = part_serializers.PartTestTemplateSerializer(
|
template_detail = part_serializers.PartTestTemplateSerializer(
|
||||||
source='template', read_only=True
|
source='template', read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(
|
attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(
|
||||||
@ -442,6 +446,9 @@ class StockItemSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if not part_detail:
|
if not part_detail:
|
||||||
self.fields.pop('part_detail', None)
|
self.fields.pop('part_detail', None)
|
||||||
|
|
||||||
@ -473,7 +480,10 @@ class StockItemSerializer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
location_path = serializers.ListField(
|
location_path = serializers.ListField(
|
||||||
child=serializers.DictField(), source='location.get_path', read_only=True
|
child=serializers.DictField(),
|
||||||
|
source='location.get_path',
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
in_stock = serializers.BooleanField(read_only=True, label=_('In Stock'))
|
in_stock = serializers.BooleanField(read_only=True, label=_('In Stock'))
|
||||||
@ -617,18 +627,23 @@ class StockItemSerializer(
|
|||||||
part_detail=False,
|
part_detail=False,
|
||||||
many=False,
|
many=False,
|
||||||
read_only=True,
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
part_detail = part_serializers.PartBriefSerializer(
|
part_detail = part_serializers.PartBriefSerializer(
|
||||||
label=_('Part'), source='part', many=False, read_only=True
|
label=_('Part'), source='part', many=False, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
location_detail = LocationBriefSerializer(
|
location_detail = LocationBriefSerializer(
|
||||||
label=_('Location'), source='location', many=False, read_only=True
|
label=_('Location'),
|
||||||
|
source='location',
|
||||||
|
many=False,
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
tests = StockItemTestResultSerializer(
|
tests = StockItemTestResultSerializer(
|
||||||
source='test_results', many=True, read_only=True
|
source='test_results', many=True, read_only=True, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
quantity = InvenTreeDecimalField()
|
quantity = InvenTreeDecimalField()
|
||||||
@ -1184,7 +1199,7 @@ class LocationSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if not path_detail:
|
if not path_detail and not isGeneratingSchema():
|
||||||
self.fields.pop('path', None)
|
self.fields.pop('path', None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -1219,7 +1234,10 @@ class LocationSerializer(
|
|||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
path = serializers.ListField(
|
path = serializers.ListField(
|
||||||
child=serializers.DictField(), source='get_path', read_only=True
|
child=serializers.DictField(),
|
||||||
|
source='get_path',
|
||||||
|
read_only=True,
|
||||||
|
allow_null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# explicitly set this field, so it gets included for AutoSchema
|
# explicitly set this field, so it gets included for AutoSchema
|
||||||
@ -1263,6 +1281,9 @@ class StockTrackingSerializer(
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if isGeneratingSchema():
|
||||||
|
return
|
||||||
|
|
||||||
if item_detail is not True:
|
if item_detail is not True:
|
||||||
self.fields.pop('item_detail', None)
|
self.fields.pop('item_detail', None)
|
||||||
|
|
||||||
@ -1271,9 +1292,13 @@ class StockTrackingSerializer(
|
|||||||
|
|
||||||
label = serializers.CharField(read_only=True)
|
label = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True)
|
item_detail = StockItemSerializerBrief(
|
||||||
|
source='item', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
user_detail = UserSerializer(source='user', many=False, read_only=True)
|
user_detail = UserSerializer(
|
||||||
|
source='user', many=False, read_only=True, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
deltas = serializers.JSONField(read_only=True)
|
deltas = serializers.JSONField(read_only=True)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
|
from InvenTree.ready import isGeneratingSchema
|
||||||
from InvenTree.serializers import InvenTreeModelSerializer
|
from InvenTree.serializers import InvenTreeModelSerializer
|
||||||
|
|
||||||
from .models import ApiToken, Owner, RuleSet, UserProfile, check_user_role
|
from .models import ApiToken, Owner, RuleSet, UserProfile, check_user_role
|
||||||
@ -44,12 +45,12 @@ class GroupSerializer(InvenTreeModelSerializer):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not permission_detail:
|
if not permission_detail and not isGeneratingSchema():
|
||||||
self.fields.pop('permissions', None)
|
self.fields.pop('permissions', None)
|
||||||
except AppRegistryNotReady:
|
except AppRegistryNotReady:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
permissions = serializers.SerializerMethodField()
|
permissions = serializers.SerializerMethodField(allow_null=True)
|
||||||
|
|
||||||
def get_permissions(self, group: Group):
|
def get_permissions(self, group: Group):
|
||||||
"""Return a list of permissions associated with the group."""
|
"""Return a list of permissions associated with the group."""
|
||||||
@ -74,7 +75,7 @@ class RoleSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
user = serializers.IntegerField(source='pk')
|
user = serializers.IntegerField(source='pk')
|
||||||
roles = serializers.SerializerMethodField()
|
roles = serializers.SerializerMethodField()
|
||||||
permissions = serializers.SerializerMethodField()
|
permissions = serializers.SerializerMethodField(allow_null=True)
|
||||||
|
|
||||||
def get_roles(self, user: User) -> dict:
|
def get_roles(self, user: User) -> dict:
|
||||||
"""Roles associated with the user."""
|
"""Roles associated with the user."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user