Repository: encode/django-rest-framework

Issue:
API browser checks some custom permissions inconsistently compared to real API endpoints.
The API browser makes use of an `override_method` context manager to change the request method on a view so that it can check permissions for different endpoints (than the current request) and determine whether or not a form should be created.

My permissions are `GET/list` (deny) and `POST/create` (allow) on one URL, and `GET/retrieve` (allow), `PUT/update` (deny) and `PATCH/partial_update` (deny) on another. I want anonymous users to be able to create objects and retrieve specific objects, but not list or update objects.

All of these action methods are defined on one `ModelViewSet`, which is mapped to two URLs (one maps `GET` to `list` and the other maps `GET` to `retrieve`). I have a custom permission (applied to the whole viewset) that checks `view.action` and allows `create` and `retrieve` actions but denies `list`, `update` and `partial_update` actions for anonymous users.

I think this should work for real clients making real requests against the API. But the API browser gets confused on the list page because it displays the results for `GET/list` **and** a form for `POST/create` endpoints on the same page.

The API browser uses `override_method` to change the request method from `GET` to `POST` when checking if it should render a form, but it does not change the action from `list` to `create`.

I suppose in this particular case I could check a combination of request method and action. E.g. use `view.action` to differentiate between `GET/list` and `GET/retrieve` and use `request.method` for everything else?

But it feels wrong that the API browser doesn't behave consistently with the real API when checking permissions for various endpoints. If `view.action` is there to be tested when implementing custom permissions, then it should be consistently overridden along with `request.method` when the API browser checks for permissions.



Discussion:
Whilst the specifics are different, this seems essentially a Duplicate of #1105. 

By default an form is shown where it would not be in this particular use-case if permissions were checked. The important thing is that the permissions would be honoured on the submitted request. Users are free to customise the Browseable API if it doesn't serve their needs.

Actually although there's some similarity I'd be more minded to accept a pull request on this than on #1105 - it probably would be a good idea to temporarily set the `.action` on the view in the same way that we temporarily set the request on the view.

If @mrmachine is interested in taking that on then the right approach would be to modify the context manager to also temporarily set `.action` on the view _if_ the `action_map` attribute exists.  Of course it'd also need a test that uses a custom permission which uses the `.action` to determine if permission should be allowed.


Resolving patch:
diff --git a/rest_framework/request.py b/rest_framework/request.py
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -42,12 +42,16 @@ def __init__(self, view, request, method):
         self.view = view
         self.request = request
         self.method = method
+        self.action = getattr(view, 'action', None)
 
     def __enter__(self):
         self.view.request = clone_request(self.request, self.method)
+        action_map = getattr(self.view, 'action_map', {})
+        self.view.action = action_map.get(self.method.lower())
         return self.view.request
 
     def __exit__(self, *args, **kwarg):
+        self.view.action = self.action
         self.view.request = self.request
 
 


Test patch: