Skip to content

Commit

Permalink
restructure request body for extend_schema #266 #279
Browse files Browse the repository at this point in the history
  • Loading branch information
tfranzel committed Feb 23, 2021
1 parent 80cc1b3 commit cf4be7d
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 21 deletions.
57 changes: 37 additions & 20 deletions drf_spectacular/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,9 +870,40 @@ def _get_request_body(self):
if self.method not in ('PUT', 'PATCH', 'POST'):
return None

serializer = force_instance(self.get_request_serializer())
request_serializer = self.get_request_serializer()

if isinstance(request_serializer, dict):
content = []
request_body_required = True
for media_type, serializer in request_serializer.items():
schema, partial_request_body_required = self._get_request_for_media_type(serializer)
examples = self._get_examples(serializer, 'request', media_type)
if not schema:
continue
content.append((media_type, schema, examples))
request_body_required &= partial_request_body_required
else:
schema, request_body_required = self._get_request_for_media_type(request_serializer)
if not schema:
return
content = [
(media_type, schema, self._get_examples(request_serializer, 'request', media_type))
for media_type in self.map_parsers()
]

request_body = {
'content': {
media_type: build_media_type_object(schema, examples)
for media_type, schema, examples in content
}
}
if request_body_required:
request_body['required'] = request_body_required
return request_body

def _get_request_for_media_type(self, serializer):
serializer = force_instance(serializer)

request_body_required = False
if is_list_serializer(serializer):
if is_serializer(serializer.child):
component = self.resolve_serializer(serializer.child, 'request')
Expand All @@ -886,7 +917,7 @@ def _get_request_body(self):
component = self.resolve_serializer(serializer, 'request')
if not component.schema:
# serializer is empty so skip content enumeration
return None
return None, False
schema = component.ref
# request body is only required if any required property is not read-only
readonly_props = [
Expand All @@ -896,8 +927,7 @@ def _get_request_body(self):
request_body_required = any(req not in readonly_props for req in required_props)
elif is_basic_type(serializer):
schema = build_basic_type(serializer)
if not schema:
return None
request_body_required = False
else:
warn(
f'could not resolve request body for {self.method} {self.path}. defaulting to generic '
Expand All @@ -907,21 +937,8 @@ def _get_request_body(self):
additionalProperties={},
description='Unspecified request body',
)

request_body = {
'content': {
media_type: build_media_type_object(
schema,
self._get_examples(serializer, 'request', media_type)
)
for media_type in self.map_parsers()
}
}

if request_body_required:
request_body['required'] = request_body_required

return request_body
request_body_required = False
return schema, request_body_required

def _get_response_bodies(self):
response_serializers = self.get_response_serializers()
Expand Down
8 changes: 7 additions & 1 deletion drf_spectacular/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,16 @@ def extend_schema(
- ``Serializer`` instance (e.g. ``Serializer(many=True)`` for listings)
- ``dict`` with status codes as keys and `Serializers` as values.
- ``dict`` with tuple (status_code, media_type) as keys and `Serializers` as values.
- basic types or instances of ``OpenApiTypes``
- :class:`.PolymorphicProxySerializer` for signaling that
the operation may yield data from different serializers depending
on the circumstances.
:param request: replaces the discovered ``Serializer``.
:param request: replaces the discovered ``Serializer``. Takes a variety of inputs
- ``Serializer`` class/instance
- basic types or instances of ``OpenApiTypes``
- :class:`.PolymorphicProxySerializer` for signaling that the operation
accepts a set of different types of objects.
- ``dict`` with media_type as keys and one of the above as values.
:param auth:
:param description: replaces discovered doc strings
:param summary: an optional short summary of the description
Expand Down
12 changes: 12 additions & 0 deletions tests/test_extend_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,18 @@ def manual(self, request):
def non_required_body(self, request):
return Response([]) # pragma: no cover

@extend_schema(
request={
'application/json': dict,
'application/pdf': bytes,
'text/html': OpenApiTypes.STR
},
responses=None
)
@action(detail=False, methods=['POST'])
def custom_request_override(self, request):
return Response([]) # pragma: no cover

@extend_schema(responses={
(200, 'application/pdf'): bytes
})
Expand Down
26 changes: 26 additions & 0 deletions tests/test_extend_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,32 @@ paths:
responses:
'201':
description: No response body
/doesitall/custom_request_override/:
post:
operationId: doesitall_custom_request_override_create
description: ''
tags:
- doesitall
requestBody:
content:
application/json:
schema:
type: object
additionalProperties: {}
application/pdf:
schema:
type: string
format: binary
text/html:
schema:
type: string
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
description: No response body
/doesitall/document/:
get:
operationId: doesitall_document_retrieve
Expand Down

0 comments on commit cf4be7d

Please sign in to comment.