From aca0c4713e0163fb0deea8ea397368084a7c83e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=20V=C3=AEjdea?= Date: Mon, 14 May 2018 19:15:14 +0300 Subject: [PATCH] Allow body on HTTP DELETE view methods (#122) * Allow body in delete requests * Do not add request body to DELETE by default * Check manual form parameters against body_methods * Add tests * Add changelog Closes #118 --- docs/changelog.rst | 1 + src/drf_yasg/inspectors/base.py | 5 +++- src/drf_yasg/inspectors/view.py | 7 +++-- testproj/snippets/views.py | 47 ++++++++++++++++++++++++--------- tests/reference.yaml | 24 ++++++++++++++++- 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 518203a..e9d1968 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,7 @@ Changelog - **IMPROVED:** updated ``swagger-ui`` to version 3.14.2 - **IMPROVED:** updated ``ReDoc`` to version 2.0.0-alpha.20 - **FIXED:** ignore ``None`` return from ``get_operation`` to avoid empty ``Path`` objects in output +- **FIXED:** request body is now allowed on ``DELETE`` endpoints (:issue:`118`) ********* **1.7.3** diff --git a/src/drf_yasg/inspectors/base.py b/src/drf_yasg/inspectors/base.py index 040ec05..c54fe7d 100644 --- a/src/drf_yasg/inspectors/base.py +++ b/src/drf_yasg/inspectors/base.py @@ -276,7 +276,10 @@ class SerializerInspector(FieldInspector): class ViewInspector(BaseInspector): - body_methods = ('PUT', 'PATCH', 'POST') #: methods that are allowed to have a request body + body_methods = ('PUT', 'PATCH', 'POST', 'DELETE') #: methods that are allowed to have a request body + + #: methods that are assumed to require a request body determined by the view's ``serializer_class`` + implicit_body_methods = ('PUT', 'PATCH', 'POST') # real values set in __init__ to prevent import errors field_inspectors = [] #: diff --git a/src/drf_yasg/inspectors/view.py b/src/drf_yasg/inspectors/view.py index a76393d..836dbb7 100644 --- a/src/drf_yasg/inspectors/view.py +++ b/src/drf_yasg/inspectors/view.py @@ -101,7 +101,7 @@ class SwaggerAutoSchema(ViewInspector): if isinstance(body_override, openapi.Schema.OR_REF): return body_override return force_serializer_instance(body_override) - elif self.method in self.body_methods: + elif self.method in self.implicit_body_methods: return self.get_view_serializer() return None @@ -144,8 +144,11 @@ class SwaggerAutoSchema(ViewInspector): raise SwaggerGenerationError("specify the body parameter as a Schema or Serializer in request_body") if any(param.in_ == openapi.IN_FORM for param in manual_parameters): # pragma: no cover if any(param.in_ == openapi.IN_BODY for param in parameters.values()): - raise SwaggerGenerationError("cannot add form parameters when the request has a request schema; " + raise SwaggerGenerationError("cannot add form parameters when the request has a request body; " "did you forget to set an appropriate parser class on the view?") + if self.method not in self.body_methods: + raise SwaggerGenerationError("form parameters can only be applied to (" + ','.join(self.body_methods) + + ") HTTP methods") parameters.update(param_list_to_odict(manual_parameters)) return list(parameters.values()) diff --git a/testproj/snippets/views.py b/testproj/snippets/views.py index f325c7e..f3978c3 100644 --- a/testproj/snippets/views.py +++ b/testproj/snippets/views.py @@ -33,6 +33,21 @@ class SnippetList(generics.ListCreateAPIView): """post method docstring""" return super(SnippetList, self).post(request, *args, **kwargs) + @swagger_auto_schema( + operation_id='snippets_delete_bulk', + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + 'body': openapi.Schema( + type=openapi.TYPE_STRING, + description='this should not crash (request body on DELETE method)' + ) + } + ), + ) + def delete(self, *args, **kwargs): + pass + class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): """ @@ -56,18 +71,26 @@ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): """patch method docstring""" return super(SnippetDetail, self).patch(request, *args, **kwargs) - @swagger_auto_schema(manual_parameters=[ - openapi.Parameter( - name='id', in_=openapi.IN_PATH, - type=openapi.TYPE_INTEGER, - description="path parameter override", - required=True - ), - ], responses={ - status.HTTP_204_NO_CONTENT: openapi.Response( - description="This should not crash" - ) - }) + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter( + name='id', in_=openapi.IN_PATH, + type=openapi.TYPE_INTEGER, + description="path parameter override", + required=True + ), + openapi.Parameter( + name='delete_form_param', in_=openapi.IN_FORM, + type=openapi.TYPE_INTEGER, + description="this should not crash (form parameter on DELETE method)" + ), + ], + responses={ + status.HTTP_204_NO_CONTENT: openapi.Response( + description="this should not crash (response object with no schema)" + ) + } + ) def delete(self, request, *args, **kwargs): """delete method docstring""" return super(SnippetDetail, self).patch(request, *args, **kwargs) diff --git a/tests/reference.yaml b/tests/reference.yaml index f167efc..66a9d6a 100644 --- a/tests/reference.yaml +++ b/tests/reference.yaml @@ -388,6 +388,24 @@ paths: $ref: '#/definitions/Snippet' tags: - snippets + delete: + operationId: snippetsDeleteBulk + description: SnippetList classdoc + parameters: + - name: data + in: body + required: true + schema: + type: object + properties: + body: + description: this should not crash (request body on DELETE method) + type: string + responses: + '204': + description: '' + tags: + - snippets parameters: [] /snippets/{id}/: get: @@ -442,9 +460,13 @@ paths: description: path parameter override required: true type: integer + - name: delete_form_param + in: formData + description: this should not crash (form parameter on DELETE method) + type: integer responses: '204': - description: This should not crash + description: this should not crash (response object with no schema) tags: - snippets parameters: