Search code examples
djangohttpendpoint

My Django endpoint does not access the has_object_permission method


Here's a view class that I have:

from rest_framework.views import APIView
class MyView(APIView):
    permission_classes = [CustomAccessPermission]

    def get(self, request, id: int) -> Response:
        object = get_object_or_404(MyObjectClass, id)
        serializer = MySerializer(object)
        return Response(serializer.data)

    def delete(self, request, id: int):
        object = get_object_or_404(MyObjectClass, id)
        object.status = MyObjectClass.Status.DELETED
        object.save()
        return Response(status=status.HTTP_200_OK, data=id)

Here's my custom access permission class:

from rest_framework import permissions
from django.core.exceptions import PermissionDenied


class CustomAccessPermission(permissions.BasePermission):
    message = "You cannot access objects created by other users."

    def has_object_permission(self, request, view, obj):
        if obj.user_id != request.user.id:
            raise PermissionDenied(self.message)
        return True

So within my objects I store user_id that contains IDs of users that created this object. I want to check if the id from request is equal to that user_id to undestand if user can see this object. So, for example, when I run GET:http://localhost:8050/api/objects/4 i want to take the user_id of object with id=4 and if it's equal to request.user.id then we'll allow the user to see it, otherwise we should deny. The same with DELETE requests, they should be first checked against the user_id of object.

This code above doesn't work, it doesn't access the has_object_permission() method in my permission class. What should I modify?


Solution

  • It doesn't because you implement get and delete yourself, and use an APIView. Just like Django's View, an APIView only implements the basics of a view that handles API requests. It has an .check_object_permissions(…) that can check the permissions of the objects, but you need to do this yourself.

    This is why one almost always starts from a GenericAPIView that provides more functionality. Indeed,

    from rest_framework.views import GenericAPIView
    
    
    class MyView(GenericAPIView):
        permission_classes = [CustomAccessPermission]
        queryset = MyObjectClass.objects.all()
        serializer_class = MySerializer
        lookup_url_kwarg = 'id'
    
        def get(self, request, id: int) -> Response:
            object = self.get_object()
            serializer = MySerializer(object)
            return Response(serializer.data)
    
        def delete(self, request, id: int):
            object = self.get_object()
            object.status = MyObjectClass.Status.DELETED
            object.save()
            return Response(status=status.HTTP_200_OK, data=id)

    but still, we can reduce the amount of boilerplate code, and let the Django REST framework implement the methods itself:

    from rest_framework.generics import RetrieveDestroyAPIView
    
    
    class MyView(RetrieveDestroyAPIView):
        permission_classes = [CustomAccessPermission]
        queryset = MyObjectClass.objects.all()
        serializer_class = MySerializer
        lookup_url_kwarg = 'id'
    
        def perform_destroy(self, instance):
            instance.status = MyObjectClass.Status.DELETED
            instance.save()

    and that's it. If you implement the "soft delete" on your model itself: i.e. overriding the .delete() method of the MyObjectClass itself, you don't need to override perform_destroy.