From 5df2adae6da3eba4fe7b7df1d8515a689f940e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20M=C3=A1rton?= Date: Fri, 3 Jul 2026 23:58:53 +0200 Subject: [PATCH] Fix OpenAPI multipart file fields to use binary format (#12298) * Fix OpenAPI multipart file fields to use binary format Fixes #11246 * Update API version * fix api version --------- Co-authored-by: Matthias Mair --- .../InvenTree/InvenTree/api_version.py | 5 +++- src/backend/InvenTree/InvenTree/schema.py | 15 ++++++++++ src/backend/InvenTree/InvenTree/tests.py | 30 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 26ce0c47d7..952bd5fa63 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,14 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 514 +INVENTREE_API_VERSION = 515 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v515 -> 2026-07-03 : https://github.com/inventree/InvenTree/pull/12298 + - Change the file fields definition to binary (from uri) in the upload requests + v514 -> 2026-07-02 : https://github.com/inventree/InvenTree/pull/12294 - Adds "duplicate" field to the BuildOrder, Company, ManufacturerPart, SupplierPart and SalesOrderShipment API endpoints - Order duplication options: renames "order_id" field to "original", which now performs primary-key validation diff --git a/src/backend/InvenTree/InvenTree/schema.py b/src/backend/InvenTree/InvenTree/schema.py index edea87bfef..ebd3bda9d8 100644 --- a/src/backend/InvenTree/InvenTree/schema.py +++ b/src/backend/InvenTree/InvenTree/schema.py @@ -16,6 +16,7 @@ from drf_spectacular.utils import ( extend_schema, extend_schema_view, ) +from rest_framework import serializers from rest_framework.pagination import LimitOffsetPagination from InvenTree.permissions import OASTokenMixin @@ -46,6 +47,20 @@ class ExtendedOAuth2Scheme(DjangoOAuthToolkitScheme): class ExtendedAutoSchema(AutoSchema): """Extend drf-spectacular to allow customizing the schema to match the actual API behavior.""" + def _map_serializer_field(self, field, direction, *args, **kwargs): + """Custom field mapping overrides, falling back to default behavior.""" + schema = super()._map_serializer_field(field, direction, *args, **kwargs) + + direction_value = getattr(direction, 'value', direction) + + # File and image fields in request schemas must be represented as binary + # payloads. In response schemas they are still rendered as URLs. + if direction_value == 'request' and isinstance(field, serializers.FileField): + schema['type'] = 'string' + schema['format'] = 'binary' + + return schema + def is_bulk_action(self, ref: str) -> bool: """Check the class of the current view for the bulk mixins.""" return ref in [c.__name__ for c in type(self.view).__mro__] diff --git a/src/backend/InvenTree/InvenTree/tests.py b/src/backend/InvenTree/InvenTree/tests.py index ea2f6d4171..7934f91324 100644 --- a/src/backend/InvenTree/InvenTree/tests.py +++ b/src/backend/InvenTree/InvenTree/tests.py @@ -22,6 +22,7 @@ from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.models import Rate, convert_money from djmoney.money import Money from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode +from rest_framework import serializers from sesame.utils import get_user from stdimage.models import StdImageFieldFile @@ -1817,6 +1818,35 @@ class SchemaPostprocessingTest(TestCase): # required key removed when empty self.assertNotIn('required', schemas_out.get('SalesOrderShipment')) + def test_file_field_request_schema_binary(self): + """Verify only request file fields are exposed as binary.""" + auto_schema = object.__new__(schema.ExtendedAutoSchema) + + mapped_schemas = [ + {'type': 'string', 'format': 'uri', 'nullable': True}, + {'type': 'string', 'format': 'uri'}, + {'type': 'string', 'format': 'uri', 'nullable': True}, + ] + + with mock.patch( + 'drf_spectacular.openapi.AutoSchema._map_serializer_field', + side_effect=mapped_schemas, + ): + file_request = auto_schema._map_serializer_field( + serializers.FileField(allow_null=True), 'request' + ) + url_request = auto_schema._map_serializer_field( + serializers.URLField(), 'request' + ) + file_response = auto_schema._map_serializer_field( + serializers.FileField(allow_null=True), 'response' + ) + + self.assertEqual(file_request['format'], 'binary') + self.assertTrue(file_request['nullable']) + self.assertEqual(url_request['format'], 'uri') + self.assertEqual(file_response['format'], 'uri') + class URLCompatibilityTest(InvenTreeTestCase): """Unit test for legacy URL compatibility."""