Skip to content

Commit 45c77bf

Browse files
committed
improve FileField, add test and documentation. #69
1 parent 573cf09 commit 45c77bf

4 files changed

Lines changed: 44 additions & 14 deletions

File tree

docs/faq.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,16 @@ You can easily specify a custom authentication with
8282
:py:class:`OpenApiAuthenticationExtension <drf_spectacular.extensions.OpenApiAuthenticationExtension>`.
8383
Have a look at :ref:`customization` on how to use ``Extensions``
8484

85+
86+
FileField (ImageField) is not handled properly in the schema
87+
------------------------------------------------------------
88+
In contrast to most other fields, ``FileField`` behaves differently for requests and responses.
89+
This duality is impossible to represent in a single component schema.
90+
91+
For these cases, there is an option to split components into request and response parts
92+
by setting ``COMPONENT_SPLIT_REQUEST = True``. Note that this influences the whole schema,
93+
not just components with ``FileFields``.
94+
95+
Also consider explicitly setting ``parser_classes = [parsers.MultiPartParser]`` (or any file compatible parser)
96+
on your `View` or write a custom `get_parser_classes`. These fields do not work with the default ``JsonParser``
97+
and that fact should be represented in the schema.

drf_spectacular/openapi.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -537,16 +537,12 @@ def _map_serializer_field(self, field, direction):
537537
return append_meta(content, meta)
538538

539539
if isinstance(field, serializers.FileField):
540-
if direction == 'response':
541-
use_url = getattr(field, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
542-
if use_url:
543-
return append_meta(build_basic_type(OpenApiTypes.URI), meta)
544-
else:
545-
return append_meta(build_basic_type(OpenApiTypes.STR), meta)
540+
if spectacular_settings.COMPONENT_SPLIT_REQUEST and direction == 'request':
541+
content = build_basic_type(OpenApiTypes.BINARY)
546542
else:
547-
content = build_basic_type(OpenApiTypes.STR)
548-
content['format'] = 'binary'
549-
return append_meta(content, meta)
543+
use_url = getattr(field, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
544+
content = build_basic_type(OpenApiTypes.URI if use_url else OpenApiTypes.STR)
545+
return append_meta(content, meta)
550546

551547
if isinstance(field, serializers.SerializerMethodField):
552548
method = getattr(field.parent, field.method_name)

tests/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ def generate_schema(route, viewset=None, view=None, view_function=None):
3939
return schema
4040

4141

42-
def get_response_schema(operation, status='200'):
43-
return operation['responses'][status]['content']['application/json']['schema']
42+
def get_response_schema(operation, status='200', content_type='application/json'):
43+
return operation['responses'][status]['content'][content_type]['schema']
4444

4545

46-
def get_request_schema(operation):
47-
return operation['requestBody']['content']['application/json']['schema']
46+
def get_request_schema(operation, content_type='application/json'):
47+
return operation['requestBody']['content'][content_type]['schema']

tests/test_regressions.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.db.models import fields
66
from django.db import models
77
from django.urls import path, re_path
8-
from rest_framework import serializers, viewsets, mixins, routers, views, generics
8+
from rest_framework import serializers, viewsets, mixins, routers, views, generics, parsers
99
from rest_framework.decorators import action, api_view
1010
from rest_framework.views import APIView
1111

@@ -569,3 +569,24 @@ class XView(generics.ListAPIView):
569569
operation = schema['paths']['/x']['get']
570570
assert operation['operationId'] == 'x_list'
571571
assert get_response_schema(operation)['type'] == 'array'
572+
573+
574+
@mock.patch('drf_spectacular.settings.spectacular_settings.COMPONENT_SPLIT_REQUEST', True)
575+
def test_file_field_duality_on_split_request(no_warnings):
576+
class XSerializer(serializers.Serializer):
577+
file = serializers.FileField()
578+
579+
class XView(generics.ListCreateAPIView):
580+
serializer_class = XSerializer
581+
parser_classes = [parsers.MultiPartParser]
582+
583+
schema = generate_schema('/x', view=XView)
584+
assert get_response_schema(
585+
schema['paths']['/x']['get']
586+
)['items']['$ref'] == '#/components/schemas/X'
587+
assert get_request_schema(
588+
schema['paths']['/x']['post'], content_type='multipart/form-data'
589+
)['$ref'] == '#/components/schemas/XRequest'
590+
591+
assert schema['components']['schemas']['X']['properties']['file']['format'] == 'uri'
592+
assert schema['components']['schemas']['XRequest']['properties']['file']['format'] == 'binary'

0 commit comments

Comments
 (0)