Can you set permission classes per function in class-based views?

Let's say we have the following class-based view (CBV) implementing DRF's APIView:

class ExampleListAPIView(APIView):
    permission_classes = [IsAuthenticatedOrReadOnly]

    def get(self, request):
        '''
        List all examples
        '''
        examples = Example.objects.all()
        serializer = ExampleListSerializer(examples, many=True)

        return Response(serializer.data, status.HTTP_200_OK)

    def post(self, request):
        '''
        Create new example
        '''
        serializer = ExampleListSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Using permission_classes to set authentication policy in CBV, the policy is applied to all methods of class. Is it possible to set permission per function in a CBV, e.g.

class ExampleListAPIView(APIView):
    def get(self, request): # Apply [AllowAny]
        # logic

    def post(self, request): # Apply [IsAuthenticated]
        # logic

Or you have to use function-based views to achieve this behavior?

You could make something that only checks a permission for a certain method:

class ForMethod(BasePermission):
    def __init__(self, permission, *methods):
        self.permission = permission
        self.methods = set(map(str.casefold, methods))

    def __call__(self):
        return self

    def has_permission(self, request, view):
        if request.method.casefold() in self.methods:
            self.permission().has_permission(request, view)
        return True

    def has_object_permission(self, request, view, obj):
        if request.method.casefold() in self.methods:
            self.permission().has_object_permission(request, view, obj)
        return True

and work with:

class ExampleListAPIView(APIView):
    permission_classes = [
        ForMethod(AllowAny, 'get'),
        ForMethod(IsAuthenticated, 'post'),
    ]

    def get(self, request):  # Apply [AllowAny]
        # logic
        pass

    def post(self, request):  # Apply [IsAuthenticated]
        # logic
        pass

You can even work with this as an algebra of permissions:

PostAuthenticated = ForMethod(AllowAny, 'get') & ForMethod(
    IsAuthenticated, 'post'
)


class ExampleListAPIView(APIView):
    permission_classes = [PostAuthenticated]
    # …

Based on willeM_ Van Onsem's answer, I'm posting a workaround that overcomes the issue with combining custom permissions (see my comment).

class MyPermission(BasePermission):
    def __init__(self, permissions_dict):
        self.permissions_dict = {k.casefold():v for k, v in permissions_dict.items()}

    def __call__(self):
        return self

    def has_permission(self, request, view):
        if request.method.casefold() in self.permissions_dict.keys():
            return self.permissions_dict[request.method.casefold()]().has_permission(request, view)
        return False

    def has_object_permission(self, request, view, obj):
        if request.method.casefold() in self.permissions_dict.keys():
            return self.permissions_dict[request.method.casefold()]().has_object_permission(request, view, obj)
        return False

class ExampleAPIView(APIView):
    permissions_dict = {'GET': AllowAny,
                       'POST': IsAuthenticated}
    permission_classes = [
        MyPermission(permissions_dict)
    ]

    # code

It's very similar to aforementioned answer, except for using a dictionary to define permissions as:

# {<request.method>: <built-in permission>}
Back to Top