How to set permissions per handler function with APIView?

Question:

I am writing an API for a small school-related project using Django and Django-Rest-Framework. I am trying to figure out permissions.

I am using APIView for my views, and am looking to set different permissions for each handler method without writing more views.

Here is one of the views for the blog module that I have:

(This view is used for /posts/)

from .serializers import *
from django.db.models import Case, When
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.decorators import permission_classes
from rest_framework import status, permissions


# Create your views here.
class PostList(APIView):
    """
    API Endpoint to List Posts
    METHODS: GET, POST
    """

    @permission_classes([permissions.AllowAny,])
    def get(self, request, format=None):
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @permission_classes([permissions.IsAuthenticated])
    def post(self, request, format=None):
        serializer = PostCreationSerializer(data=request.data)
        if serializer.is_valid():
            obj = serializer.save()
            data = serializer.data
            data["id"] = obj.id
            return Response(data, status=status.HTTP_201_CREATED)
        return Response(status=status.HTTP_400_BAD_REQUEST)

The current default is permissions.IsAuthenticated, though changing that would not necessarily help as I would still need to change some permissions individually.

So I should be able to make a GET request while not authenticated to /posts/ and get a list of all posts, while I should get a 401 from a POST request to /posts/. However, even the GET request returns a 401 status code.

What is the right way to do this… if there even is one.

Edit: Though I provided an answer to my own question, I would still like to know if there are any more correct ways to solve this.

Asked By: user1720845

||

Answers:

I figured out a solution, though I am not sure that it is the best way to do this.

First, we create a subclass of APIView:

from rest_framework.views import APIView


class CustomAPIView(APIView):
    def get_permissions(self):
        # Instances and returns the dict of permissions that the view requires.
        return {key: [permission() for permission in permissions] for key, permissions in self.permission_classes.items()}

    def check_permissions(self, request):
        # Gets the request method and the permissions dict, and checks the permissions defined in the key matching
        # the method.
        method = request.method.lower()
        for permission in self.get_permissions()[method]:
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )

Then we use this CustomAPIView to write out views, and use a dict for permission_classes where the keys are the methods.

Example:

# Create your views here.
class PostList(CustomAPIView):
    """
    API Endpoint to List Posts
    METHODS: GET, POST
    """
    permission_classes = {"get": [permissions.AllowAny], "post": [permissions.IsAuthenticated]}

    def get(self, request, format=None):
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def post(self, request, format=None):
        serializer = PostCreationSerializer(data=request.data)
        if serializer.is_valid():
            obj = serializer.save()
            data = serializer.data
            data["id"] = obj.id
            return Response(data, status=status.HTTP_201_CREATED)
        return Response(status=status.HTTP_400_BAD_REQUEST)
Answered By: user1720845

While I have come up with a solution, I am uncertain if it is the optimal approach.

So, you can override get_permissions() of the APIView class. Then add your permissions to a specific method.

This is an example to add IsAuthenticated permission to the post method.

def get_permissions(self):
    permissions = super().get_permissions()

    if self.request.method.lower() == 'post':
        permissions.append(IsAuthenticated())

    return permissions
Answered By: Bader Eddine