web-dev-qa-db-ja.com

Django ViewSetメソッドのRESTフレームワークpermission_classes

Django RESTフレームワークを使用してRESTAPIを作成しており、特定のエンドポイントをパーミッションで保護したい。パーミッションクラスはこれを実現するためのエレガントな方法です。私の問題は、オーバーライドされた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デコレータを使用して(ViewSetメソッドではなく)APIビューを保護する例を確認し、ViewSet全体のアクセス許可クラスを設定する方法を確認しました。ただし、オーバーライドされたViewSetメソッドでは機能していないようです。特定のエンドポイントにのみ使用する方法はありますか?

14
fodma1

他のすべての答えは素晴らしいと思いますが、デコレータで直接定義されているデフォルトのアクション '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)]

これで、これら2つの方法で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
1
Itachi