Django rest framework permission_classes of ViewSet method

前端 未结 4 2057
攒了一身酷
攒了一身酷 2020-12-08 10:43

I\'m writing a rest API with the Django REST framework, and I\'d like to protect certain endpoints with permissions. The permission classes look like they provide an elegant

相关标签:
4条回答
  • 2020-12-08 11:08

    I think all of the other answers are great but we shouldn't suppress the default actions' permission_classes defined in their decorators directly. So,

    from rest_framework import viewsets
    from rest_framework import permissions
    
    class BaseModelViewSet(viewsets.ModelViewSet):
        queryset = ''
        serializer_class = ''
        permission_classes = (permissions.AllowAny,)
    
        # Refer to https://stackoverflow.com/a/35987077/1677041
        permission_classes_by_action = {
            'create': permission_classes,
            'list': permission_classes,
            'retrieve': permission_classes,
            'update': permission_classes,
            'destroy': permission_classes,
        }
    
        def get_permissions(self):
            try:
                return [permission() for permission in self.permission_classes_by_action[self.action]]
            except KeyError:
                if self.action:
                    action_func = getattr(self, self.action, {})
                    action_func_kwargs = getattr(action_func, 'kwargs', {})
                    permission_classes = action_func_kwargs.get('permission_classes')
                else:
                    permission_classes = None
    
                return [permission() for permission in (permission_classes or self.permission_classes)]
    

    Now we could define the permission_classes in these two ways. Since we defined the default global permission_classes_by_action in the superclass, we could drop that definition for all the actions in option 2.

    class EntityViewSet(BaseModelViewSet):
        """EntityViewSet"""
        queryset = Entity.objects.all()
        serializer_class = EntitySerializer
        permission_classes_by_action = {
            'create': (permissions.IsAdminUser,),
            'list': (permissions.IsAuthenticatedOrReadOnly,),
            'retrieve': (permissions.AllowAny,),
            'update': (permissions.AllowAny,),
            'destroy': (permissions.IsAdminUser,),
            'search': (permissions.IsAuthenticated,)  # <--- Option 1
        }
    
        @action(detail=False, methods=['post'], permission_classes=(permissions.IsAuthenticated,))  # <--- Option 2
        def search(self, request, format=None):
            pass
    
    0 讨论(0)
  • 2020-12-08 11:25

    I'm probably late to answer this, but I used a mixin, as one of the commenters pointed out. Taking the answer from @Itachi, this is my mixin implementation:

    class ViewSetActionPermissionMixin:
        def get_permissions(self):
            """Return the permission classes based on action.
    
            Look for permission classes in a dict mapping action to
            permission classes array, ie.:
    
            class MyViewSet(ViewSetActionPermissionMixin, ViewSet):
                ...
                permission_classes = [AllowAny]
                permission_action_classes = {
                    'list': [IsAuthenticated]
                    'create': [IsAdminUser]
                    'my_action': [MyCustomPermission]
                }
    
                @action(...)
                def my_action:
                    ...
    
            If there is no action in the dict mapping, then the default
            permission_classes is returned. If a custom action has its
            permission_classes defined in the action decorator, then that
            supercedes the value defined in the dict mapping.
            """
            try:
                return [
                    permission()
                    for permission in self.permission_action_classes[self.action]
                ]
            except KeyError:
                if self.action:
                    action_func = getattr(self, self.action, {})
                    action_func_kwargs = getattr(action_func, "kwargs", {})
                    permission_classes = action_func_kwargs.get(
                        "permission_classes"
                    )
                else:
                    permission_classes = None
    
                return [
                    permission()
                    for permission in (
                        permission_classes or self.permission_classes
                    )
                ]
    

    And here's how to use the mixin:

    class MyViewSet(ViewSetActionPermissionMixin, ModelViewSet):
        ...
        permission_action_classes = {
            "list": [AllowAny],
            "create": [IsAdminUser],
            "custom_action": [MyCustomPermission],
        }
    
        @action(...)
        def custom_action(self, request, *args, **kwargs):
            ...
    
    0 讨论(0)
  • 2020-12-08 11:26

    I created a superclass that is derived from @ilse2005's answer. In all subsequent django views you can inherit this to achieve action level permission control.

    class MixedPermissionModelViewSet(viewsets.ModelViewSet):
       '''
       Mixed permission base model allowing for action level
       permission control. Subclasses may define their permissions
       by creating a 'permission_classes_by_action' variable.
    
       Example:
       permission_classes_by_action = {'list': [AllowAny],
                                       'create': [IsAdminUser]}
       '''
    
       permission_classes_by_action = {}
    
       def get_permissions(self):
          try:
            # return permission_classes depending on `action`
            return [permission() for permission in self.permission_classes_by_action[self.action]]
          except KeyError:
            # action is not set return default permission_classes
            return [permission() for permission in self.permission_classes]
    
    0 讨论(0)
  • 2020-12-08 11:33

    I think there is no inbuilt solution for that. But you can achieve this by overriding the get_permissions method:

    from rest_framework.permissions import AllowAny, IsAdminUser
    
    class UserViewSet(viewsets.ModelViewSet):
        queryset = User.objects.all()
        serializer_class = UserSerializer
    
        permission_classes_by_action = {'create': [AllowAny],
                                        'list': [IsAdminUser]}
    
        def create(self, request, *args, **kwargs):
            return super(UserViewSet, self).create(request, *args, **kwargs)
    
        def list(self, request, *args, **kwargs):
            return super(UserViewSet, self).list(request, *args, **kwargs)
    
        def get_permissions(self):
            try:
                # return permission_classes depending on `action` 
                return [permission() for permission in self.permission_classes_by_action[self.action]]
            except KeyError: 
                # action is not set return default permission_classes
                return [permission() for permission in self.permission_classes]
    
    0 讨论(0)
提交回复
热议问题