diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 0af7510cd..f10ea06af 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -1,4 +1,5 @@ import warnings +from operator import attrgetter from urllib.parse import urljoin from django.core.validators import ( @@ -8,7 +9,7 @@ from django.core.validators import ( from django.db import models from django.utils.encoding import force_str -from rest_framework import exceptions, serializers +from rest_framework import exceptions, serializers, renderers from rest_framework.compat import uritemplate from rest_framework.fields import _UnvalidatedField, empty @@ -78,7 +79,9 @@ class SchemaGenerator(BaseSchemaGenerator): class AutoSchema(ViewInspector): - content_types = ['application/json'] + request_media_types = [] + response_media_types = [] + method_mapping = { 'get': 'Retrieve', 'post': 'Create', @@ -90,6 +93,7 @@ class AutoSchema(ViewInspector): def get_operation(self, path, method): operation = {} + operation['operationId'] = self._get_operation_id(path, method) parameters = [] @@ -337,6 +341,12 @@ class AutoSchema(ViewInspector): self._map_min_max(field, content) return content + if isinstance(field, serializers.FileField): + return { + 'type': 'string', + 'format': 'binary' + } + # Simplest cases, default to 'string' type: FIELD_CLASS_SCHEMA_TYPE = { serializers.BooleanField: 'boolean', @@ -423,6 +433,17 @@ class AutoSchema(ViewInspector): schema['maximum'] = int(digits * '9') + 1 schema['minimum'] = -schema['maximum'] + def map_parsers(self, path, method): + return list(map(attrgetter('media_type'), self.view.parser_classes)) + + def map_renderers(self, path, method): + media_types = [] + for renderer in self.view.renderer_classes: + # I assume this is not relevant to OpenAPI spec + if renderer != renderers.BrowsableAPIRenderer: + media_types.append(renderer.media_type) + return media_types + def _get_serializer(self, method, path): view = self.view @@ -442,6 +463,8 @@ class AutoSchema(ViewInspector): if method not in ('PUT', 'PATCH', 'POST'): return {} + self.request_media_types = self.map_parsers(path, method) + serializer = self._get_serializer(path, method) if not isinstance(serializer, serializers.Serializer): @@ -459,7 +482,7 @@ class AutoSchema(ViewInspector): return { 'content': { ct: {'schema': content} - for ct in self.content_types + for ct in self.request_media_types } } @@ -472,6 +495,8 @@ class AutoSchema(ViewInspector): } } + self.response_media_types = self.map_renderers(path, method) + item_schema = {} serializer = self._get_serializer(path, method) @@ -496,7 +521,7 @@ class AutoSchema(ViewInspector): '200': { 'content': { ct: {'schema': response_schema} - for ct in self.content_types + for ct in self.response_media_types }, # description is a mandatory property, # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 78a5609da..5be61448c 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -287,6 +287,30 @@ class TestOperationIntrospection(TestCase): }, } + def test_multipart_request_body_generation(self): + """Test that a view's delete method generates a proper response body schema.""" + path = '/{id}/' + method = 'POST' + + class ItemSerializer(serializers.Serializer): + attachment = serializers.FileField() + + class View(generics.CreateAPIView): + serializer_class = ItemSerializer + + view = create_view( + View, + method, + create_request(path), + ) + inspector = AutoSchema() + inspector.view = view + + request_body = inspector._get_request_body(path, method) + assert 'multipart/form-data' in request_body['content'] + attachment = request_body['content']['multipart/form-data']['schema']['properties']['attachment'] + assert attachment['format'] == 'binary' + def test_retrieve_response_body_generation(self): """Test that a list of properties is returned for retrieve item views.""" path = '/{id}/'