Django-rest-framework: permission_classes not being honored for a detail route when running a test

Created on 18 Aug 2016  路  9Comments  路  Source: encode/django-rest-framework

class UserViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
.....
@detail_route(methods=['get'], permission_classes=[IsCurrentUser])
def permissions(self, request, pk=None, format=None):
user = self.get_object()
serializer = UserPermissionsSerializer(user)
return Response(serializer.data)

If I test this view from the browser or PostMan the permission_classes are honored and I get a 403 back if I麓m not the current user (i.e. trying to view permissions for someone else)

If I write a test using APIRequestFactory and run this I get a 200 response.

I debugged and the permission class is not even executed.
However, if I add the permission at class level (permission_classes = [IsCurrentUser,]) inside the ViewSet instead of as a decorator I get a 403 back.

Most helpful comment

I just ran into the same issue. I can reproduce that permission_classes on the viewset are honored by the view function, whereas the one set on the detail_route is not. In fact, when I check self.permission_classes on the detail view, it returns the one from the viewset, not from the detail view, which seems to me like a bug.

All 9 comments

By using APIRequestFactory you're bypassing the permissions. Use APIClient instead.

How come it works outside of the detail_route decorator then?...they are not bypassed then. Seems odd to me.

What do you mean ?

@xordoquy Like I tried to explain. I have a ViewSet with a view that has a detail_decorator (route) applied to it and that route has a specific permission class applied. That permission class is not executed when I run it through a testcase that uses the APIReuestFactory. However, if I add the same permission class to the ViewSet itself (appended to the permission_classes attribute) it runs and executes perfecly fine. That seems odd to me.

Executing a view directly misses out some of the request-response cycle, eg. middleware.
That's generally not the right way to go about testing.
RequestFactory can be useful for things like unit tests, but for testing the complete request-response cycle it's best to use the test client.

@jole78 this will depend on how you call the view and what argument you pass to the as_view.

@tomchristie @xordoquy this is just that, a unit test. btw, thanks for the quick response and help around this. It's highly appreciative :)

The url is _/api/users/[id]/permissions_
and the view looks like:

@detail_route(methods=['get'], permission_classes=[IsCurrentUser])
def permissions(self, request, pk=None, format=None):

My test looks like this (sort of):

view = UserViewSet.as_view({'get':'permissions'})
...some other code that sets up 2 users (user and another_user)
url = reverse('api:user-permissions', kwargs={'pk': another_user.id})
request = self.factory.get(url)
force_authenticate(request, user=user)
response = view(request, pk=another_user.id)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

and it works if instead do this:

class UserViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
permission_classes=[IsCurrentUser,]

I just ran into the same issue. I can reproduce that permission_classes on the viewset are honored by the view function, whereas the one set on the detail_route is not. In fact, when I check self.permission_classes on the detail view, it returns the one from the viewset, not from the detail view, which seems to me like a bug.

Unfortunately your side of the code is missing some essential part.
By calling "UserViewSet.as_view({'get':'permissions'})" you discard all the arguments passed to the decorator that are automatically injected back by the router

Was this page helpful?
0 / 5 - 0 ratings