Django休息框架用于ViewSet方法的permission_classes

时间:2016-03-13 13:42:32

标签: python django rest django-rest-framework

我正在使用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)

在上面的代码中,我也希望允许未经身份验证的用户注册(用户创建),但我不想让列表用户只为员工使用。

docs中,我看到了使用permission_classes装饰器保护API视图(而不是ViewSet方法)的示例,我看到为整个ViewSet设置了权限类。但它似乎不适用于重写的ViewSet方法。有没有办法只将它们用于某些端点?

3 个答案:

答案 0 :(得分:21)

我认为没有内置的解决方案。但您可以通过覆盖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]

答案 1 :(得分:4)

我创建了一个派生自@ 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]

答案 2 :(得分:0)

我认为所有其他答案都不错,但我们不应该直接禁止在其修饰器中定义的默认操作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://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)]

现在我们可以通过这两种方式定义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