[Django]-Django Rest Framework – GenericViewSet with Authentication/Permission decorator

11👍

Option 1.

Change the decorator to a class attribute and simply set the permission_classes to IsAuthenticatedOrReadOnly.
IsAuthenticatedOrReadOnly will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the “safe” methods; GET, HEAD or OPTIONS.

from rest_framework.permissions import IsAuthenticatedOrReadOnly

class EntityViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet
):

    queryset = Entity.objects.all()
    serializer_class = EntitySerializer
    permission_classes = (IsAuthenticatedOrReadOnly,)

Option 2.

Create your own permission and tailor it to your needs. Then, set it as a class attribute.

Example permissions.py:

from rest_framework.permissions import BasePermission

class IsStaff(BasePermission):
    def has_permission(self, request, view):
        return request.user.is_authenticated and request.user.is_staff

Your file:

from .permissions import IsStaff
class EntityViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet
):

    queryset = Entity.objects.all()
    serializer_class = EntitySerializer
    permission_classes = (IsStaff,)

Option 3.

Override the get_permissions method in your class.

from rest_framework.permissions import AllowAny

def get_permissions(self):
    if self.action == 'retrieve':
        return [AllowAny]
    return [IsAuthenticated]

Option 4.

If you don’t specify any permissions for the retrieve method, Django Rest Framework is gonna apply the default one that you have specified in your settings.py, which in this case is IsAuthenticated. What you should do is, for the retrieve method, allow any user.

from rest_framework.permissions import AllowAny

@permission_classes([AllowAny])
def retrieve(self, request, *args, **kwargs):
    return super().retrieve(request, *args, **kwargs)

Note that when you set new permission classes through class attribute or decorators you’re telling the view to ignore the default list set over the settings.py file.

Don’t worry too much about the authentication_classes, since the AllowAny permission class will allow unrestricted access, regardless of if the request was authenticated or unauthenticated.

3👍

If you are using Class Based DRF Views, use, override get_permissions method to set the permission properly,
for example,


from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated


class MyViewSet(...):
    def get_permissions(self):
        if self.action == 'retrieve':
            retrieve_permission_list = [AllowAny, IsAdminUser, ]
            return [permission() for permission in retrieve_permission_list]
        elif self.action == 'create':
            create_permission_list = [IsAuthenticated, IsAdminUser]
            return [permission() for permission in create_permission_list]
        else:
            return super().get_permissions()

In your case,

class EntityViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    ...

    def get_permissions(self):
        if self.action == 'retrieve':
            return []
        else:
            return super().get_permissions()
👤JPG

2👍

The permission checks are actually done in the check_permissions method, which calls each of the defined permissions retrieved via the get_permissions method.

So the simplest and easiest would be to override check_permissions and add your permission logic there based on the action that is requested (based on method-action map). Add the following to your EntityViewSet class:

def check_permissions(self, request):
    if self.action == 'retrieve':
        return
    super().check_permissions(request)

So no permission check when action is retrieve, otherwise do as usual.

FWIW, check_permissions is actually defined in views.APIView (superclass of generics.GenericAPIView).


Why your authentication_classes/permission_classes decorators are ignored?

The authentication_classes/permission_classes decorators are designed for function based views e.g. created by the api_view decorator.

The permission_classes decorator sets permission_classes attribute on the decorated function:

def permission_classes(permission_classes):
    def decorator(func):
        func.permission_classes = permission_classes
        return func
    return decorator

In your case, that’s the retrieve method but you need to set the attribute on the (to be created) instance or on the class (EntityViewSet). As a result the permissions from your permission_classes
decorator on the retrieve method does not have any impact. authentication_classes decorator also works the same way so that didn’t work either.

FWIW, api_view actually creates a subclass of views.APIView class dynamically and sets the permission_classes, authentication_classes attributes to the subclass from the decorated function 1. So in that case, it makes sense for the decorated classes to use the authentication_classes/permission_classes decorators as those attributes will eventually be applied as-is.

1 The default setting is used if the decorated function does not have the attribute e.g. the relevant decorator was not applied.

2👍

My suggestion is to change the default permission class in the settings file

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'api.authentication.token_authentication.TokenAuthentication'
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly'
    ]
}

Now you don’t have to apply permission class on the views. This will be applied by default.

Leave a comment