Fix OpenAPI multipart file fields to use binary format

Fixes #11246
This commit is contained in:
Miklós Márton 2026-04-19 23:00:30 +02:00 committed by Miklos Marton
parent 836a05a104
commit 60caba13fd
2 changed files with 45 additions and 0 deletions

View File

@ -16,6 +16,7 @@ from drf_spectacular.utils import (
extend_schema, extend_schema,
extend_schema_view, extend_schema_view,
) )
from rest_framework import serializers
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from InvenTree.permissions import OASTokenMixin from InvenTree.permissions import OASTokenMixin
@ -46,6 +47,20 @@ class ExtendedOAuth2Scheme(DjangoOAuthToolkitScheme):
class ExtendedAutoSchema(AutoSchema): class ExtendedAutoSchema(AutoSchema):
"""Extend drf-spectacular to allow customizing the schema to match the actual API behavior.""" """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: def is_bulk_action(self, ref: str) -> bool:
"""Check the class of the current view for the bulk mixins.""" """Check the class of the current view for the bulk mixins."""
return ref in [c.__name__ for c in type(self.view).__mro__] return ref in [c.__name__ for c in type(self.view).__mro__]

View File

@ -22,6 +22,7 @@ from djmoney.contrib.exchange.exceptions import MissingRate
from djmoney.contrib.exchange.models import Rate, convert_money from djmoney.contrib.exchange.models import Rate, convert_money
from djmoney.money import Money from djmoney.money import Money
from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
from rest_framework import serializers
from sesame.utils import get_user from sesame.utils import get_user
from stdimage.models import StdImageFieldFile from stdimage.models import StdImageFieldFile
@ -1817,6 +1818,35 @@ class SchemaPostprocessingTest(TestCase):
# required key removed when empty # required key removed when empty
self.assertNotIn('required', schemas_out.get('SalesOrderShipment')) 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): class URLCompatibilityTest(InvenTreeTestCase):
"""Unit test for legacy URL compatibility.""" """Unit test for legacy URL compatibility."""