Repository: encode/django-rest-framework

Issue:
Documentation fails when using detail_route with 'options' method
## Checklist

- [X] I have verified that that issue exists against the `master` branch of Django REST framework.
- [X] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [X] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [X] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [X] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

Hello, thanks for this awesome piece of software!
The issue is the following:
When using detail_route in ModelViewSet and using "options" method , the documentation page fails with an exception.

## Steps to reproduce
1. Create a project with ModelViewSet
2. Add this method in the class

```
@detail_route(methods=['options', 'get', 'put'])
def test(self, request, *args, **kwargs):
  pass
```

3. Check that the documentation is not working

## Expected behavior
The documentation should work using the "options" method

## Actual behavior
The documentation page is crashing with exception
KeyError: 'options'


Thanks for the help!
Amit.



Discussion:

Resolving patch:
diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py
--- a/rest_framework/schemas/generators.py
+++ b/rest_framework/schemas/generators.py
@@ -222,12 +222,11 @@ def get_allowed_methods(self, callback):
         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):


Test patch:
diff --git a/tests/test_schemas.py b/tests/test_schemas.py
--- a/tests/test_schemas.py
+++ b/tests/test_schemas.py
@@ -901,3 +901,53 @@ class TestView(generics.RetrieveAPIView):
 
     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"]