Django-rest-framework: Allow separate permissions per View in ViewSet

Created on 28 Aug 2013  路  14Comments  路  Source: encode/django-rest-framework

Hi guys,
I've just updated to the latest version(2.3.7) and was exited! It is awesome!

My question is:
Can I specify permission_classes per view in ViewSet?
I want allow ALL users to create instances, but only AUTHENTICATED can view them.Please see code below:

class MyModelViewSet(viewsets.ModelViewSet):
    authentication_classes = (SessionAuthentication, )
    permission_classes = (IsAuthenticated, )

    model = MyModel
    serializer_class = MyModelSerializer

    def retrieve(self, request, *args, **kwargs):
        #retrieve code goes here

    def update(self, request, *args, **kwargs):
        #update code goes here

    @decorators.action(permission_classes=(AllowAny, ))
    def create(self, request, *args, **kwargs):
        #how to allow UNAUTHENTICATED users access the url?

I use the approach registering customers. Is there any feature that can help me? The code above actually not working, it checks only "IsAuthenticated" permission, so only authenticated users can access "create" view.

Most helpful comment

I've been looking at how to implement this for a while, and the crucial thing to know is that self on .get_permissions() has an action property which actually holds the name of the method that was called, so it's possible to switch on that.

However, a IsCreationOrIsAuthenticated is certainly a useful one (I'd probably call it IsAuthenticatedOrWriteOnly), so here's a very simple implementation for people coming here from googling:

class IsCreationOrIsAuthenticated(permissions.BasePermission):

    def has_permission(self, request, view):
        if not request.user.is_authenticated():
            if view.action == 'create':
                return True
            else:
                return False
        else:
            return True

All 14 comments

Can I specify permission_classes per view in ViewSet?

You could override .get_permissions() if needed, see here, and brief mention in docs. If you checked request.kwargs you'd be able to determine if you're in a list or detaill type view and modify which permissions to return accordingly

However the more sensible way to tackle this would be to create a custom permission class that allows POST requests from any user and only allows other other requests from authenticated users.

Take a look at the custom permissions docs and consider overriding IsAuthenticated to a custom IsCreationOrIsAuthenticated.

Incidentally, usage questions are probably best directed at the discussion group.

Hope that helps! :)

IsCreationOrIsAuthenticated - great idea!

Thanks :)

I've been looking at how to implement this for a while, and the crucial thing to know is that self on .get_permissions() has an action property which actually holds the name of the method that was called, so it's possible to switch on that.

However, a IsCreationOrIsAuthenticated is certainly a useful one (I'd probably call it IsAuthenticatedOrWriteOnly), so here's a very simple implementation for people coming here from googling:

class IsCreationOrIsAuthenticated(permissions.BasePermission):

    def has_permission(self, request, view):
        if not request.user.is_authenticated():
            if view.action == 'create':
                return True
            else:
                return False
        else:
            return True

Why downvote the last answer?

The code example violates the separation of concerns that DRF tries to isolate.
This being said, I'd rather have someone explaining than just down voting :(

Alternatively (for Googlers etc), here's a couple of custom permissions classes with more restrictions than @kot-behemoth's suggestion (which when used alone, allows for any authenticated user to view/update/delete any user's records).

When used together, these permissions permit:

  • Anonymous create/POST
  • Logged-in users to retrieve/GET and update/PUT/PATCH their own records (and nobody else's)
  • All actions for staff/admin users

They disallow:

  • Deletion/DELETE of any records unless staff/admin
  • Viewing/GET all records (list) unless staff/admin

I would expect this to be a fairly common requirement of a User API endpoint.

Note: AnonCreateAndUpdateOwnerOnly relies on each record having an 'id' field. For an endpoint other than User, just change this whatever your record's user ownership field is (e.g. 'owner').

class AnonCreateAndUpdateOwnerOnly(permissions.BasePermission):
    """
    Custom permission:
        - allow anonymous POST
        - allow authenticated GET and PUT on *own* record
        - allow all actions for staff
    """

    def has_permission(self, request, view):
        return view.action == 'create' or request.user and request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        return view.action in ['retrieve', 'update', 'partial_update'] and obj.id == request.user.id or request.user.is_staff

class ListAdminOnly(permissions.BasePermission):
    """
    Custom permission to only allow access to lists for admins
    """

    def has_permission(self, request, view):
        return view.action != 'list' or request.user and request.user.is_staff

@assembledadam
That part should be updated:
and obj.id == request.user.id
to:
and obj.user.id == request.user.id
And empty user field checks should be added too.

In my case, this permission is exclusively used on the 'User' endpoint (model), so the code is correct for that. It needs to be slightly modified to match your fields if not for User, as mentioned in the italics section of the post.

Is possible to implement the JSONWebTokenAuthentication for a ModelViewsets for specific actions like PUT or DELETE?

Thanks in advance

Hi @Allan-Nava. The GitHub issue tracker is intended for bug reports and feature requests. If you need help or support, there are a few places to ask questions, such as IRC, Stack Overflow, and the Google Group. My recommendation would be to start by searching Stack Overflow for questions related to DRF and JWT.

So @tomchristie , if I need to add a permission for a specific action like create of my viewsets.ModelViewsets I have to write:

def create(self, request, *args, **kwargs):
        self.get_permissions(IsAuthenticated)

Thanks in advance

Hi @Allan-Nava. The GitHub issue tracker is intended for bug reports and feature requests. If you need help or support, there are a few places to ask questions, such as IRC, Stack Overflow, and the Google Group. My recommendation would be to start by searching Stack Overflow for questions related to DRF and JWT.

If overriding the permission_classes of the ViewSet isn't the behavior of @action(permission_classes=(,)), then what is this keyword argument of the action decorator used for?

@serpulga @action decorator is not meant to be used with the generic methods such as create / list / retrieve / update / delete / partial_update.
However that's the way to go for extra actions.

Was this page helpful?
0 / 5 - 0 ratings