Django rest framework中ViewSet方法的permission_classes

34

我正在使用Django REST框架编写REST API,并希望通过权限保护某些端点。权限类似乎提供了一种优雅的方法来实现此目的。我的问题是,我希望为不同的重写ViewSet方法使用不同的权限类。

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def create(self, request, *args, **kwargs):
        return super(UserViewSet, self).create(request, *args, **kwargs)

    @decorators.permission_classes(permissions.IsAdminUser)
    def list(self, request, *args, **kwargs):
        return super(UserViewSet, self).list(request, *args, **kwargs)
在上面的代码中,我想为未经身份验证的用户允许注册(创建用户),但我不希望将用户列表公开给任何人,只想对员工开放。
文档中,我看到了保护API视图(而不是ViewSet方法)的示例,使用permission_classes装饰器设置整个ViewSet的权限类。但似乎无法在重写的ViewSet方法上运行。是否有办法仅在某些端点上使用它们?

最终,我使用了https://github.com/Tivix/django-rest-auth,但你提供的解决方案也很好。 - fodma1
4个回答

62

我认为没有内置的解决方法。但是您可以通过覆盖get_permissions方法来实现此目的:

我认为没有内置的解决方法。但是您可以通过覆盖get_permissions方法来实现此目的:

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]

我认为将“get_permissions”放入Mixin是一个好主意。 - Phoenix

14

我创建了一个超类,它是从@ilse2005的答案派生而来的。在所有后续的Django视图中,您可以继承这个类以实现对操作级别权限的控制。


我创建了一个超类,它派生自@ilse2005的答案。在所有后续的Django视图中,您可以继承此类以实现操作级别的权限控制。
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]

11

我可能回答晚了,但我使用了一个mixin,就像其中一位评论者指出的那样。参考@Itachi的答案,这是我的mixin实现:

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
                )
            ]

下面是如何使用mixin:

class MyViewSet(ViewSetActionPermissionMixin, ModelViewSet):
    ...
    permission_action_classes = {
        "list": [AllowAny],
        "create": [IsAdminUser],
        "custom_action": [MyCustomPermission],
    }

    @action(...)
    def custom_action(self, request, *args, **kwargs):
        ...

10

我认为其他回答都很好,但我们不应该直接压制在其装饰器中定义的默认操作的permission_classes权限类。因此,

from rest_framework import viewsets
from rest_framework import permissions

class BaseModelViewSet(viewsets.ModelViewSet):
    queryset = ''
    serializer_class = ''
    permission_classes = (permissions.AllowAny,)

    # Refer to https://dev59.com/b1sV5IYBdhLWcg3wpgND#35987077
    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)]

现在我们可以用这两种方式来定义permission_classes。由于我们在超类中定义了默认的全局permission_classes_by_action,所以我们可以在选项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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接