Skip to content

Commit 8b06ce7

Browse files
knivetscarltongibson
authored andcommitted
OpenAPI: Map renderers/parsers for request/response media-types. (encode#6865)
1 parent 14d740d commit 8b06ce7

File tree

2 files changed

+102
-5
lines changed

2 files changed

+102
-5
lines changed

rest_framework/schemas/openapi.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import warnings
2+
from operator import attrgetter
23
from urllib.parse import urljoin
34

45
from django.core.validators import (
@@ -8,7 +9,7 @@
89
from django.db import models
910
from django.utils.encoding import force_str
1011

11-
from rest_framework import exceptions, serializers
12+
from rest_framework import exceptions, renderers, serializers
1213
from rest_framework.compat import uritemplate
1314
from rest_framework.fields import _UnvalidatedField, empty
1415

@@ -78,7 +79,9 @@ def get_schema(self, request=None, public=False):
7879

7980
class AutoSchema(ViewInspector):
8081

81-
content_types = ['application/json']
82+
request_media_types = []
83+
response_media_types = []
84+
8285
method_mapping = {
8386
'get': 'Retrieve',
8487
'post': 'Create',
@@ -339,6 +342,12 @@ def _map_field(self, field):
339342
self._map_min_max(field, content)
340343
return content
341344

345+
if isinstance(field, serializers.FileField):
346+
return {
347+
'type': 'string',
348+
'format': 'binary'
349+
}
350+
342351
# Simplest cases, default to 'string' type:
343352
FIELD_CLASS_SCHEMA_TYPE = {
344353
serializers.BooleanField: 'boolean',
@@ -434,9 +443,20 @@ def _get_paginator(self):
434443
pagination_class = getattr(self.view, 'pagination_class', None)
435444
if pagination_class:
436445
return pagination_class()
437-
438446
return None
439447

448+
def map_parsers(self, path, method):
449+
return list(map(attrgetter('media_type'), self.view.parser_classes))
450+
451+
def map_renderers(self, path, method):
452+
media_types = []
453+
for renderer in self.view.renderer_classes:
454+
# BrowsableAPIRenderer not relevant to OpenAPI spec
455+
if renderer == renderers.BrowsableAPIRenderer:
456+
continue
457+
media_types.append(renderer.media_type)
458+
return media_types
459+
440460
def _get_serializer(self, method, path):
441461
view = self.view
442462

@@ -456,6 +476,8 @@ def _get_request_body(self, path, method):
456476
if method not in ('PUT', 'PATCH', 'POST'):
457477
return {}
458478

479+
self.request_media_types = self.map_parsers(path, method)
480+
459481
serializer = self._get_serializer(path, method)
460482

461483
if not isinstance(serializer, serializers.Serializer):
@@ -473,7 +495,7 @@ def _get_request_body(self, path, method):
473495
return {
474496
'content': {
475497
ct: {'schema': content}
476-
for ct in self.content_types
498+
for ct in self.request_media_types
477499
}
478500
}
479501

@@ -486,6 +508,8 @@ def _get_responses(self, path, method):
486508
}
487509
}
488510

511+
self.response_media_types = self.map_renderers(path, method)
512+
489513
item_schema = {}
490514
serializer = self._get_serializer(path, method)
491515

@@ -513,7 +537,7 @@ def _get_responses(self, path, method):
513537
'200': {
514538
'content': {
515539
ct: {'schema': response_schema}
516-
for ct in self.content_types
540+
for ct in self.response_media_types
517541
},
518542
# description is a mandatory property,
519543
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject

tests/schemas/test_openapi.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from rest_framework import filters, generics, pagination, routers, serializers
77
from rest_framework.compat import uritemplate
8+
from rest_framework.parsers import JSONParser, MultiPartParser
9+
from rest_framework.renderers import JSONRenderer
810
from rest_framework.request import Request
911
from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator
1012

@@ -364,6 +366,77 @@ class View(generics.DestroyAPIView):
364366
},
365367
}
366368

369+
def test_parser_mapping(self):
370+
"""Test that view's parsers are mapped to OA media types"""
371+
path = '/{id}/'
372+
method = 'POST'
373+
374+
class View(generics.CreateAPIView):
375+
serializer_class = views.ExampleSerializer
376+
parser_classes = [JSONParser, MultiPartParser]
377+
378+
view = create_view(
379+
View,
380+
method,
381+
create_request(path),
382+
)
383+
inspector = AutoSchema()
384+
inspector.view = view
385+
386+
request_body = inspector._get_request_body(path, method)
387+
388+
assert len(request_body['content'].keys()) == 2
389+
assert 'multipart/form-data' in request_body['content']
390+
assert 'application/json' in request_body['content']
391+
392+
def test_renderer_mapping(self):
393+
"""Test that view's renderers are mapped to OA media types"""
394+
path = '/{id}/'
395+
method = 'GET'
396+
397+
class View(generics.CreateAPIView):
398+
serializer_class = views.ExampleSerializer
399+
renderer_classes = [JSONRenderer]
400+
401+
view = create_view(
402+
View,
403+
method,
404+
create_request(path),
405+
)
406+
inspector = AutoSchema()
407+
inspector.view = view
408+
409+
responses = inspector._get_responses(path, method)
410+
# TODO this should be changed once the multiple response
411+
# schema support is there
412+
success_response = responses['200']
413+
414+
assert len(success_response['content'].keys()) == 1
415+
assert 'application/json' in success_response['content']
416+
417+
def test_serializer_filefield(self):
418+
path = '/{id}/'
419+
method = 'POST'
420+
421+
class ItemSerializer(serializers.Serializer):
422+
attachment = serializers.FileField()
423+
424+
class View(generics.CreateAPIView):
425+
serializer_class = ItemSerializer
426+
427+
view = create_view(
428+
View,
429+
method,
430+
create_request(path),
431+
)
432+
inspector = AutoSchema()
433+
inspector.view = view
434+
435+
request_body = inspector._get_request_body(path, method)
436+
mp_media = request_body['content']['multipart/form-data']
437+
attachment = mp_media['schema']['properties']['attachment']
438+
assert attachment['format'] == 'binary'
439+
367440
def test_retrieve_response_body_generation(self):
368441
"""
369442
Test that a list of properties is returned for retrieve item views.

0 commit comments

Comments
 (0)