首页 > 解决方案 > ViewSet 的动作装饰器忽略了它的 permission_classes

问题描述

为什么我的动作装饰器忽略了它的权限类?

我有一个具有“IsAuthenticated”的视图集,用于通用发布/创建和发布到自定义操作装饰器。

但是,当我为未登录的用户使用自定义动作装饰器时,动作装饰器的代码仍然运行(并导致错误)。

为什么是这样?未登录的用户在发布到操作时不应该收到 401_Unauthorized 吗?

未登录的用户在进行一般帖子时确实会收到 401_Unauthorized。

这是视图集:

class ItemViewSet(viewsets.ModelViewSet):
    queryset = Item.objects.all()
    lookup_field = "pk"
    serializer_class = ItemSerializer

    def get_permissions(self):
        if self.action == "create":
            permission_classes = [IsAuthenticated]
        else:
            permission_classes = [AllowAny]
        return [permission() for permission in permission_classes]

    @action(
        methods=["post"],
        detail=True,
        permission_classes=[IsAuthenticated],
        url_name="actioned",
    )
    def actioned(self, request, pk=None):
        try:
            item = Item.objects.get(user=request.user)
            item.status = "actioned"
            item.save()
            return Response(status=status.HTTP_200_OK)
        except Item.DoesNotExist:
            Item.objects.create(user=request.user, status="actioned")
            return Response(status=status.HTTP_201_CREATED)

我正在为我的网址使用 DefaultRouter():

router = DefaultRouter
router.register(r"items", ItemViewSet, basename="item")
urlpatterns = [ path("", include(router.urls)), ]

以下是测试:


# GENERIC POST BY ANONYMOUSUSER DOESN'T RUN AND GIVES 401
def test_public_generic_post(self):
    payload = {"status": None}
    response = APIClient.post(
        reverse("app:item-list"),
        payload,
        format="json"
    )
    self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) # True

# ACTION POST BY ANONYMOUSUSER RUNS EVEN THOUGH IT HAS THE SAME IsAuthenticated permission
def test_public_action_post(self):
    item = Item.objects.create()
    response = APIClient.post(
        reverse("app:item-actioned", args=[item.pk]),
        format="json"
    )
    self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) # False

对于操作帖子,我收到以下错误:

TypeError: Cannot cast AnonymousUser to int. Are you trying to use it in place of User?

因为它正在运行我的try: item = Item.objects.get(user=request.user)代码并且没有request.user.

但是,如果动作装饰器明确具有,为什么它会首先运行动作装饰器permission_classes=[IsAuthenticated]呢?

我可以确认request.user.is_authenticatedFalse

标签: djangodjango-rest-framework

解决方案


根据@KutayAslan 的评论,删除 get_permissions 方法可以解决此错误。

看起来在我的原始代码中,动作装饰器正在分配 [AllowAny],因为它的“self.action”属于“else”子句。

将 permission_classes 设置为显式等于操作装饰器可解决此问题:

    def get_permissions(self):
        if self.action == "create":
            permission_classes = [IsAuthenticated]
        elif self.action == "actioned":
            permission_classes = [IsAuthenticated]
        else:
            permission_classes = [AllowAny]
        return [permission() for permission in permission_classes]

推荐阅读