From bb020a748f3a564333cd8a6138129df1ac9a803b Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 25 Oct 2017 10:46:52 +0200 Subject: [PATCH] Schema: Exclude OPTIONS/HEAD for ViewSet actions Closes #5528. Viewset custom actions (@detail_route etc) OPTIONS (and HEAD) methods were not being excluded from Schema Generations. This PR adds a test reproducing the reported error and adjusts `EndpointEnumerator.get_allowed_methods()` to filter ViewSet actions in the same way as other `APIView`s --- rest_framework/schemas/generators.py | 9 +++-- tests/test_schemas.py | 50 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index 5fac35276..393e21575 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -222,12 +222,11 @@ class EndpointEnumerator(object): if hasattr(callback, 'actions'): actions = set(callback.actions.keys()) http_method_names = set(callback.cls.http_method_names) - return [method.upper() for method in actions & http_method_names] + methods = [method.upper() for method in actions & http_method_names] + else: + methods = callback.cls().allowed_methods - return [ - method for method in - callback.cls().allowed_methods if method not in ('OPTIONS', 'HEAD') - ] + return [method for method in methods if method not in ('OPTIONS', 'HEAD')] class SchemaGenerator(object): diff --git a/tests/test_schemas.py b/tests/test_schemas.py index eec3060fb..df4910301 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -901,3 +901,53 @@ def test_is_list_view_recognises_retrieve_view_subclasses(): is_list = is_list_view(path, method, view) assert not is_list, "RetrieveAPIView subclasses should not be classified as list views." + + +def test_head_and_options_methods_are_excluded(): + """ + Regression test for #5528 + https://github.com/encode/django-rest-framework/issues/5528 + + Viewset OPTIONS actions were not being correctly excluded + + Initial cases here shown to be working as expected. + """ + + @api_view(['options', 'get']) + def fbv(request): + pass + + inspector = EndpointEnumerator() + + path = '/a/path/' + callback = fbv + + assert inspector.should_include_endpoint(path, callback) + assert inspector.get_allowed_methods(callback) == ["GET"] + + class AnAPIView(APIView): + + def get(self, request, *args, **kwargs): + pass + + def options(self, request, *args, **kwargs): + pass + + callback = AnAPIView.as_view() + + assert inspector.should_include_endpoint(path, callback) + assert inspector.get_allowed_methods(callback) == ["GET"] + + class AViewSet(ModelViewSet): + + @detail_route(methods=['options', 'get']) + def custom_action(self, request, pk): + pass + + callback = AViewSet.as_view({ + "options": "custom_action", + "get": "custom_action" + }) + + assert inspector.should_include_endpoint(path, callback) + assert inspector.get_allowed_methods(callback) == ["GET"]