mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +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:
		@@ -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."""
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user