OpenAPI: Map renderers/parsers for request/response media-types. (#6865)

This commit is contained in:
Dima Knivets 2019-11-06 22:44:51 +02:00 committed by Carlton Gibson
parent 14d740d088
commit 8b06ce72d7
2 changed files with 102 additions and 5 deletions

View File

@ -1,4 +1,5 @@
import warnings import warnings
from operator import attrgetter
from urllib.parse import urljoin from urllib.parse import urljoin
from django.core.validators import ( from django.core.validators import (
@ -8,7 +9,7 @@ from django.core.validators import (
from django.db import models from django.db import models
from django.utils.encoding import force_str from django.utils.encoding import force_str
from rest_framework import exceptions, serializers from rest_framework import exceptions, renderers, serializers
from rest_framework.compat import uritemplate from rest_framework.compat import uritemplate
from rest_framework.fields import _UnvalidatedField, empty from rest_framework.fields import _UnvalidatedField, empty
@ -78,7 +79,9 @@ class SchemaGenerator(BaseSchemaGenerator):
class AutoSchema(ViewInspector): class AutoSchema(ViewInspector):
content_types = ['application/json'] request_media_types = []
response_media_types = []
method_mapping = { method_mapping = {
'get': 'Retrieve', 'get': 'Retrieve',
'post': 'Create', 'post': 'Create',
@ -339,6 +342,12 @@ class AutoSchema(ViewInspector):
self._map_min_max(field, content) self._map_min_max(field, content)
return content return content
if isinstance(field, serializers.FileField):
return {
'type': 'string',
'format': 'binary'
}
# Simplest cases, default to 'string' type: # Simplest cases, default to 'string' type:
FIELD_CLASS_SCHEMA_TYPE = { FIELD_CLASS_SCHEMA_TYPE = {
serializers.BooleanField: 'boolean', serializers.BooleanField: 'boolean',
@ -434,9 +443,20 @@ class AutoSchema(ViewInspector):
pagination_class = getattr(self.view, 'pagination_class', None) pagination_class = getattr(self.view, 'pagination_class', None)
if pagination_class: if pagination_class:
return pagination_class() return pagination_class()
return None return None
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:
# BrowsableAPIRenderer not relevant to OpenAPI spec
if renderer == renderers.BrowsableAPIRenderer:
continue
media_types.append(renderer.media_type)
return media_types
def _get_serializer(self, method, path): def _get_serializer(self, method, path):
view = self.view view = self.view
@ -456,6 +476,8 @@ class AutoSchema(ViewInspector):
if method not in ('PUT', 'PATCH', 'POST'): if method not in ('PUT', 'PATCH', 'POST'):
return {} return {}
self.request_media_types = self.map_parsers(path, method)
serializer = self._get_serializer(path, method) serializer = self._get_serializer(path, method)
if not isinstance(serializer, serializers.Serializer): if not isinstance(serializer, serializers.Serializer):
@ -473,7 +495,7 @@ class AutoSchema(ViewInspector):
return { return {
'content': { 'content': {
ct: {'schema': content} ct: {'schema': content}
for ct in self.content_types for ct in self.request_media_types
} }
} }
@ -486,6 +508,8 @@ class AutoSchema(ViewInspector):
} }
} }
self.response_media_types = self.map_renderers(path, method)
item_schema = {} item_schema = {}
serializer = self._get_serializer(path, method) serializer = self._get_serializer(path, method)
@ -513,7 +537,7 @@ class AutoSchema(ViewInspector):
'200': { '200': {
'content': { 'content': {
ct: {'schema': response_schema} ct: {'schema': response_schema}
for ct in self.content_types for ct in self.response_media_types
}, },
# description is a mandatory property, # description is a mandatory property,
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject

View File

@ -5,6 +5,8 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import filters, generics, pagination, routers, serializers from rest_framework import filters, generics, pagination, routers, serializers
from rest_framework.compat import uritemplate from rest_framework.compat import uritemplate
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator
@ -364,6 +366,77 @@ class TestOperationIntrospection(TestCase):
}, },
} }
def test_parser_mapping(self):
"""Test that view's parsers are mapped to OA media types"""
path = '/{id}/'
method = 'POST'
class View(generics.CreateAPIView):
serializer_class = views.ExampleSerializer
parser_classes = [JSONParser, MultiPartParser]
view = create_view(
View,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view
request_body = inspector._get_request_body(path, method)
assert len(request_body['content'].keys()) == 2
assert 'multipart/form-data' in request_body['content']
assert 'application/json' in request_body['content']
def test_renderer_mapping(self):
"""Test that view's renderers are mapped to OA media types"""
path = '/{id}/'
method = 'GET'
class View(generics.CreateAPIView):
serializer_class = views.ExampleSerializer
renderer_classes = [JSONRenderer]
view = create_view(
View,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view
responses = inspector._get_responses(path, method)
# TODO this should be changed once the multiple response
# schema support is there
success_response = responses['200']
assert len(success_response['content'].keys()) == 1
assert 'application/json' in success_response['content']
def test_serializer_filefield(self):
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)
mp_media = request_body['content']['multipart/form-data']
attachment = mp_media['schema']['properties']['attachment']
assert attachment['format'] == 'binary'
def test_retrieve_response_body_generation(self): def test_retrieve_response_body_generation(self):
""" """
Test that a list of properties is returned for retrieve item views. Test that a list of properties is returned for retrieve item views.