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.
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
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_classeson the detail view, it returns the one from the viewset, not from the detail view, which seems to me like a bug.