【问题标题】:Django rest framework permission_classes of ViewSet methodViewSet方法的Django rest框架permission_classes
【发布时间】:2016-06-28 12:19:15
【问题描述】:

我正在使用 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 方法。有没有办法只将它们用于某些端点?

【问题讨论】:

标签: python django rest django-rest-framework


【解决方案1】:

我认为没有内置的解决方案。但是您可以通过覆盖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 是个好主意。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    我可能迟到了,但正如其中一位评论者指出的那样,我使用了 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):
            ...
    

    【讨论】:

      【解决方案4】:

      我认为所有其他答案都很棒,但我们不应该直接取消在装饰器中定义的默认操作 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-16
        • 1970-01-01
        • 2016-05-27
        • 1970-01-01
        • 2019-07-09
        • 1970-01-01
        • 2015-10-31
        • 2017-12-23
        相关资源
        最近更新 更多