Skip to content

Commit

Permalink
improve FileField, add test and documentation. #69
Browse files Browse the repository at this point in the history
  • Loading branch information
tfranzel committed May 27, 2020
1 parent 573cf09 commit 45c77bf
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 14 deletions.
13 changes: 13 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,16 @@ You can easily specify a custom authentication with
:py:class:`OpenApiAuthenticationExtension <drf_spectacular.extensions.OpenApiAuthenticationExtension>`.
Have a look at :ref:`customization` on how to use ``Extensions``


FileField (ImageField) is not handled properly in the schema
------------------------------------------------------------
In contrast to most other fields, ``FileField`` behaves differently for requests and responses.
This duality is impossible to represent in a single component schema.

For these cases, there is an option to split components into request and response parts
by setting ``COMPONENT_SPLIT_REQUEST = True``. Note that this influences the whole schema,
not just components with ``FileFields``.

Also consider explicitly setting ``parser_classes = [parsers.MultiPartParser]`` (or any file compatible parser)
on your `View` or write a custom `get_parser_classes`. These fields do not work with the default ``JsonParser``
and that fact should be represented in the schema.
14 changes: 5 additions & 9 deletions drf_spectacular/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,16 +537,12 @@ def _map_serializer_field(self, field, direction):
return append_meta(content, meta)

if isinstance(field, serializers.FileField):
if direction == 'response':
use_url = getattr(field, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
if use_url:
return append_meta(build_basic_type(OpenApiTypes.URI), meta)
else:
return append_meta(build_basic_type(OpenApiTypes.STR), meta)
if spectacular_settings.COMPONENT_SPLIT_REQUEST and direction == 'request':
content = build_basic_type(OpenApiTypes.BINARY)
else:
content = build_basic_type(OpenApiTypes.STR)
content['format'] = 'binary'
return append_meta(content, meta)
use_url = getattr(field, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
content = build_basic_type(OpenApiTypes.URI if use_url else OpenApiTypes.STR)
return append_meta(content, meta)

if isinstance(field, serializers.SerializerMethodField):
method = getattr(field.parent, field.method_name)
Expand Down
8 changes: 4 additions & 4 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def generate_schema(route, viewset=None, view=None, view_function=None):
return schema


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


def get_request_schema(operation):
return operation['requestBody']['content']['application/json']['schema']
def get_request_schema(operation, content_type='application/json'):
return operation['requestBody']['content'][content_type]['schema']
23 changes: 22 additions & 1 deletion tests/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.db.models import fields
from django.db import models
from django.urls import path, re_path
from rest_framework import serializers, viewsets, mixins, routers, views, generics
from rest_framework import serializers, viewsets, mixins, routers, views, generics, parsers
from rest_framework.decorators import action, api_view
from rest_framework.views import APIView

Expand Down Expand Up @@ -569,3 +569,24 @@ class XView(generics.ListAPIView):
operation = schema['paths']['/x']['get']
assert operation['operationId'] == 'x_list'
assert get_response_schema(operation)['type'] == 'array'


@mock.patch('drf_spectacular.settings.spectacular_settings.COMPONENT_SPLIT_REQUEST', True)
def test_file_field_duality_on_split_request(no_warnings):
class XSerializer(serializers.Serializer):
file = serializers.FileField()

class XView(generics.ListCreateAPIView):
serializer_class = XSerializer
parser_classes = [parsers.MultiPartParser]

schema = generate_schema('/x', view=XView)
assert get_response_schema(
schema['paths']['/x']['get']
)['items']['$ref'] == '#/components/schemas/X'
assert get_request_schema(
schema['paths']['/x']['post'], content_type='multipart/form-data'
)['$ref'] == '#/components/schemas/XRequest'

assert schema['components']['schemas']['X']['properties']['file']['format'] == 'uri'
assert schema['components']['schemas']['XRequest']['properties']['file']['format'] == 'binary'

0 comments on commit 45c77bf

Please sign in to comment.