mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-28 20:44:03 +03:00
OpenAPI: Map renderers/parsers for request/response media-types. (#6865)
This commit is contained in:
parent
14d740d088
commit
8b06ce72d7
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user