8000 Add distinction between request and response serializers for OpenAPI … · encode/django-rest-framework@8812394 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8812394

Browse files
Add distinction between request and response serializers for OpenAPI (#7424)
* Add distinction between request and response serializers * Add docs * document new functions in schemas.md * add a test case for different request vs response objects * Correct formatting for flake8 Co-authored-by: Shaun Gosse <shaun.gosse@emburse.com>
1 parent 010c8d4 commit 8812394

File tree

3 files changed

+128
-8
lines changed

3 files changed

+128
-8
lines changed

docs/api-guide/schemas.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,20 @@ operationIds.
375375
In order to work around this, you can override `get_operation_id_base()` to
376376
provide a different base for name part of the ID.
377377

378+
#### `get_serializer()`
379+
380+
If the view has implemented `get_serializer()`, returns the result.
381+
382+
#### `get_request_serializer()`
383+
384+
By default returns `get_serializer()` but can be overridden to
385+
differentiate between request and response objects.
386+
387+
#### `get_response_serializer()`
388+
389+
By default returns `get_serializer()` but can be overridden to
390+
differentiate between request and response objects.
391+
378392
### `AutoSchema.__init__()` kwargs
379393

380394
`AutoSchema` provides a number of `__init__()` kwargs that can be used for

rest_framework/schemas/openapi.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,22 @@ def get_components(self, path, method):
192192
if method.lower() == 'delete':
193193
return {}
194194

195-
serializer = self.get_serializer(path, method)
195+
request_serializer = self.get_request_serializer(path, method)
196+
response_serializer = self.get_response_serializer(path, method)
196197

197-
if not isinstance(serializer, serializers.Serializer):
198-
return {}
198+
components = {}
199+
200+
if isinstance(request_serializer, serializers.Serializer):
201+
component_name = self.get_component_name(request_serializer)
202+
content = self.map_serializer(request_serializer)
203+
components.setdefault(component_name, content)
199204

200-
component_name = self.get_component_name(serializer)
205+
if isinstance(response_serializer, serializers.Serializer):
206+
component_name = self.get_component_name(response_serializer)
207+
content = self.map_serializer(response_serializer)
208+
components.setdefault(component_name, content)
201209

202-
content = self.map_serializer(serializer)
203-
return {component_name: content}
210+
return components
204211

205212
def _to_camel_case(self, snake_str):
206213
components = snake_str.split('_')
@@ -615,6 +622,20 @@ def get_serializer(self, path, method):
615622
.format(view.__class__.__name__, method, path))
616623
return None
617624

625+
def get_request_serializer(self, path, method):
626+
"""
627+
Override this method if your view uses a different serializer for
628+
handling request body.
629+
"""
630+
return self.get_serializer(path, method)
631+
632+
def get_response_serializer(self, path, method):
633+
"""
634+
Override this method if your view uses a different serializer for
635+
populating response data.
636+
"""
637+
return self.get_serializer(path, method)
638+
618639
def _get_reference(self, serializer):
619640
return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))}
620641

@@ -624,7 +645,7 @@ def get_request_body(self, path, method):
624645

625646
self.request_media_types = self.map_parsers(path, method)
626647

627-
serializer = self.get_serializer(path, method)
648+
serializer = self.get_request_serializer(path, method)
628649

629650
if not isinstance(serializer, serializers.Serializer):
630651
item_schema = {}
@@ -648,7 +669,7 @@ def get_responses(self, path, method):
648669

649670
self.response_media_types = self.map_renderers(path, method)
650671

651-
serializer = self.get_serializer(path, method)
672+
serializer = self.get_response_serializer(path, method)
652673

653674
if not isinstance(serializer, serializers.Serializer):
654675
item_schema = {}

tests/schemas/test_openapi.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,91 @@ def get_operation_id_base(self, path, method, action):
712712
operationId = inspector.get_operation_id(path, method)
713713
assert operationId == 'listItem'
714714

715+
def test_different_request_response_objects(self):
716+
class RequestSerializer(serializers.Serializer):
717+
text = serializers.CharField()
718+
719+
class ResponseSerializer(serializers.Serializer):
720+
text = serializers.BooleanField()
721+
722+
class CustomSchema(AutoSchema):
723+
def get_request_serializer(self, path, method):
724+
return RequestSerializer()
725+
726+
def get_response_serializer(self, path, method):
727+
return ResponseSerializer()
728+
729+
path = '/'
730+
method = 'POST'
731+
view = create_view(
732+
views.ExampleGenericAPIView,
733+
method,
734+
create_request(path),
735+
)
736+
inspector = CustomSchema()
737+
inspector.view = view
738+
739+
components = inspector.get_components(path, method)
740+
assert components == {
741+
'Request': {
742+
'properties': {
743+
'text': {
744+
'type': 'string'
745+
}
746+
},
747+
'required': ['text'],
748+
'type': 'object'
749+
},
750+
'Response': {
751+
'properties': {
752+
'text': {
753+
'type': 'boolean'
754+
}
755+
},
756+
'required': ['text'],
757+
'type': 'object'
758+
}
759+
}
760+
761+
operation = inspector.get_operation(path, method)
762+
assert operation == {
763+
'operationId': 'createExample',
764+
'description': '',
765+
'parameters': [],
766+
'requestBody': {
767+
'content': {
768+
'application/json': {
769+
'schema': {
770+
'$ref': '#/components/schemas/Request'
771+
}
772+
},
773+
'application/x-www-form-urlencoded': {
774+
'schema': {
775+
'$ref': '#/components/schemas/Request'
776+
}
777+
},
778+
'multipart/form-data': {
779+
'schema': {
780+
'$ref': '#/components/schemas/Request'
781+
}
782+
}
783+
}
784+
},
785+
'responses': {
786+
'201': {
787+
'content': {
788+
'application/json': {
789+
'schema': {
790+
'$ref': '#/components/schemas/Response'
791+
}
792+
}
793+
},
794+
'description': ''
795+
}
796+
},
797+
'tags': ['']
798+
}
799+
715800
def test_repeat_operation_ids(self):
716801
router = routers.SimpleRouter()
717802
router.register('account', views.ExampleGenericViewSet, basename="account")

0 commit comments

Comments
 (0)
0