I am implementing an API where I have nested structures. Lets say it is a zoo and I can call GET /api/cage/
to get a list of cages GET /api/cage/1/
to get cage ID 1, but then I can GET /api/cage/1/animals/
to get a list of animals in that cage. The problem I am having is with permissions. I should only be able to see animals in the cage if I can see the cage itself. I should be able to see the cage itself if has_object_permission() returns True in the relevant permission class. For some reason, has_object_permission() gets called when I do GET /api/cage/1/, but has_permission() gets called when I call GET /api/cage/1/animals/
. And with has_permission() I don't have access to the object to check the permissions. Am I missing something? How do I do this?
My cage viewset looks more or less like this
class CageViewSet(ModelViewSet): queryset = Cage.objects.all() serializer_class = CageSerializer permission_classes = [GeneralZooPermissions, ] authentication_classes = [ZooTicketCheck, ] def get_queryset(self): ... code to only list cages you have permission to see ... @detail_route(methods=['GET']) def animals(self, request, pk=None): return Request(AnimalSerializer(Animal.objects.filter(cage_id=pk), many=True).data)
my GeneralZooPermissions class looks like this (at the moment)
class GeneralZooPermissions(BasePermission): def has_permission(self, request, view): return True def has_object_permission(self, request, view, obj): return request.user.has_perm('view_cage', obj)
Update #1: It seems like this is a bug in DRF. A detail routes do not call the correct permission check. I have tried reporting this issue to DRF devs, but my report seems to have disappeared. Not sure what to do next. Ideas?
Update #2: The issue I posted with DRF is back and I got a response. Seems like checking only has_permission() and not has_object_permission() is the intended behavior. Doesn't help me any, so I am still looking for a solution. At this point, something like this would have to be done
class CustomPermission(BasePermission): def has_permission(self, request, view): """we need to do all permission checking here, since has_object_permission() is not guaranteed to be called""" if 'pk' in view.kwargs and view.kwargs['pk']: obj = view.get_queryset()[0] # check object permissions here else: # check model permissions here def has_object_permission(self, request, view, obj): """ nothing to do here, we already checked everything """ return True