6

I have the following viewset:

class ActivityViewSet(viewsets.ModelViewSet):
    queryset = Activity.objects.all()
    serializer_class = ActivitySerializer

    def get_permissions(self):
        if self.action in ['update','partial_update','destroy','list']:
            self.permission_classes = [permissions.IsAdminUser,]
        elif self.action in ['create']:
            self.permission_classes = [permissions.IsAuthenticated,]
        else :
            self.permission_classes = [permissions.AllowAny,]
        return super(self.__class__, self).get_permissions()

As seen, Im trying to allow the 'create' method without allowing the 'list', for an Authenticated user (which is not an admin). Weirdly, this Viewset results no create nor list for the Authenticated user. Iv'e checked, just to rull off, the following code:

class RouteOrderingDetail(mixins.CreateModelMixin, 
                   mixins.RetrieveModelMixin, 
                   mixins.DestroyModelMixin,
                   mixins.UpdateModelMixin,
                   viewsets.GenericViewSet):
    queryset = RouteOrdering.objects.all()
    serializer_class = RouteOrderingSerializer

This did allowed for a view in which there is create but not list (but its not usable for me, since i do need the list option avilable.

Hope the problem is clear. Any help will be appriciated.

idik
  • 872
  • 2
  • 10
  • 19
  • 1
    Check how you can create custom permissions http://www.django-rest-framework.org/api-guide/permissions/#custom-permissions – dado_eyad Sep 08 '16 at 13:31
  • I did make Custom permissions as you can clearly see in the code. the problem is beyond this. – idik Sep 08 '16 at 13:32
  • You didn't make a custom permission class. You've edited the `get_permissions ` method in your viewset. A custom permission class would allow you to return permissions depending on `request.method` – dado_eyad Sep 08 '16 at 13:34
  • And how would I create a custom permission class? I've tried to do it and didnt find any example of it. where should the class be? how do i ref it? etc. – idik Sep 08 '16 at 13:36
  • I actually checked this way - and it doesnt allow it either. – idik Sep 09 '16 at 09:24
  • Here has a very elegant way to do this, https://stackoverflow.com/a/46066153/2803344 – Belter Sep 06 '17 at 03:07

2 Answers2

24

I realize this has already been answered, but wanted to share my implementation in case it better suits the OPS use case, or someone else's:

from rest_framework.authentication import TokenAuthentication, SessionAuthentication
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.viewsets import ReadOnlyModelViewSet

from ..models import MyModel
from .serializers import MyModelSerializer


class ActionBasedPermission(AllowAny):
    """
    Grant or deny access to a view, based on a mapping in view.action_permissions
    """
    def has_permission(self, request, view):
        for klass, actions in getattr(view, 'action_permissions', {}).items():
            if view.action in actions:
                return klass().has_permission(request, view)
        return False


class MyModelViewSet(ReadOnlyModelViewSet):
    serializer_class = MyModelSerializer
    queryset = MyModel.objects.all()

    permission_classes = (ActionBasedPermission,)
    action_permissions = {
        IsAuthenticated: ['update', 'partial_update', 'destroy', 'list', 'create'],
        AllowAny: ['retrieve']
    }

    authentication_classes = (TokenAuthentication, SessionAuthentication)

Hopefully this help's someone out :)

farridav
  • 933
  • 8
  • 18
  • Works great - has an issue with the APIView since in your loop `if view.action in actions:`, the view.action is None for OPTIONS queries. I just added `elif view.action is None: return True` to always allow OPTIONS to appear possible. You also need to add `'metadata'` to at least your `IsAuthenticated` list. It's a bit weird to block OPTIONS. – Dougyfresh Sep 16 '20 at 01:13
13

Maybe you can try this:

class NotCreateAndIsAdminUser(permissions.IsAdminUser):
    def has_permission(self, request, view):
        return (view.action in ['update', 'partial_update', 'destroy', 'list'] 
                and super(NotCreateAndIsAdminUser, self).has_permission(request, view))


class CreateAndIsAuthenticated(permissions.IsAuthenticated):
    def has_permission(self, request, view):
        return (view.action == 'create'
                and super(CreateAndIsAuthenticated, self).has_permission(request, view))

class NotSafeMethodAndAllowAny(permissions.AllowAny)
    def has_permission(self, request, view):
        return (view.action is not in ['update', 'partial_update', 'destroy', 'list', 'create']
                and super(NotSafeMethodAndAllowAny, self).has_permission(request, view))


class ActivityViewSet(viewsets.ModelViewSet):
    queryset = Activity.objects.all()
    serializer_class = ActivitySerializer
    permission_classes = (NotCreateAndIsAdminUser, CreateAndIsAuthenticated, NotSafeMethodAndAllowAny)

    def create(self, request):
        pass

    def list(self, request):
        pass
    ....

The reference: Allow separate permissions per View in ViewSet

Also, you might want to check out this questions which is very similar to yours: Separate permissions per methods

OR

you can do it like this:

class ActivityViewSet(viewsets.ModelViewSet):
    queryset = Activity.objects.all()
    serializer_class = ActivitySerializer

    def get_permissions(self):
        if self.action in ['update', 'partial_update', 'destroy', 'list']:
            # which is permissions.IsAdminUser 
            return request.user and request.user.is_staff
        elif self.action in ['create']:
            # which is permissions.IsAuthenticated
            return request.user and is_authenticated(request.user)             
        else :
            # which is permissions.AllowAny
            return True
illright
  • 3,991
  • 2
  • 29
  • 54
bluebird_lboro
  • 601
  • 5
  • 17
  • I believe that the `permission_classes` decorator can only be used on functional views. – adamfc Sep 09 '16 at 08:29
  • still wont work. after adding a permission_class : "@permission_classes((IsAdmin,))" for list, the 'create' action wont be avilable for the authenticated users. – idik Sep 09 '16 at 09:26
  • @idik actually, adamfc 's comment is right, sorry for my mistake. Turns out your idea is right, overriding your **def get_permissions()** method. However, you need to implement it in a slight different way. – bluebird_lboro Sep 09 '16 at 13:48