Django-rest-framework 中 viewset 的创建权限

16 投票
3 回答
13670 浏览
提问于 2025-04-18 00:39

我正在尝试创建一个REST API,但在用户注册这一步遇到了困难:简单来说,我需要在注册之前获取访问令牌。

这是视图的代码:

class UserViewSet(viewsets.ModelViewSet):

    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def metadata(self, request):
        """
        Don't include the view description in OPTIONS responses.
        """
        data = super(UserViewSet, self).metadata(request)
        return data

    def create(self, request):
        serializer = self.get_serializer(data=request.DATA, files=request.FILES)

        if serializer.is_valid():
            self.pre_save(serializer.object)
            self.object = serializer.save(force_insert=True)
            self.post_save(self.object, created=True)
            self.object.set_password(self.object.password)
            self.object.save()
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

这是一个解决方法:

@api_view(['POST'])
@permission_classes((AllowAny,))
@csrf_exempt
def create_auth(request, format=None):
    data = JSONParser().parse(request)
    serialized = UserSerializer(data=data)

    if serialized.is_valid():
        user = User.objects.create_user(
            serialized.init_data['email'],
            serialized.init_data['username'],
            serialized.init_data['password'],
        )
        user.groups = serialized.init_data['groups']

        user.save()

        serialized_user = UserSerializer(user)
        return Response(serialized_user.data, status=status.HTTP_201_CREATED, headers={"Access-Control-Allow-Origin": "http://127.0.0.1:8000/"})
    else:
        return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST, headers={"Access-Control-Allow-Origin": "http://127.0.0.1:8000/"})

我的问题是:我该如何在UserViewSet中指定,在create这个操作中不需要凭证?或者说我想用一个自定义的认证方法?我不想改变整个视图集的认证或权限设置。

谢谢,
Adi

编辑:为了更清楚一点,未注册的用户应该可以提交注册数据,但不应该允许他们做其他事情。已认证的用户可以获取用户列表并更新自己的资料……这是默认的行为。这就是为什么AllowAny不是一个选项。在我看来,处理这个问题的合适地方是在create函数里,但我不明白我应该重写什么。

3 个回答

2

这段内容是基于@argaen的回答,我试过了,确实有效:

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    permission_classes = (AllowAny,)
    authentication_classes = (NoAuthentication,)
    filter_backends = (filters.DjangoFilterBackend,)
    filter_fields = ('id', 'email', 'name')

    def get_queryset(self):
        user = TokenAuthentication().authenticate(self.request)
        if user is not None:
            user = user[0]
            if user.is_superuser:
                return get_user_model().objects.all()
            else:
                return get_user_model().objects.filter(id=user.id)

        return get_user_model().objects.none() 
6

你可以利用Django REST框架的功能来定义自定义权限。你可以在一个自定义类里指定两个权限检查,一个是has_permission,另一个是has_object_permission。这样做可以让匿名用户在访问时遇到403错误,除了在创建数据的接口上可以发帖。看起来可能是这样的:

class IsAnonCreate(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.method == "POST" and not request.user.is_authenticated():
            return True
        elif not request.user.is_authenticated() and request.method != "POST":
            return False
        elif request.method in permissions.SAFE_METHODS:
            return True

        return False

    def has_object_permission(self, request, view, obj):
        if not request.user.is_authenticated():
            return False
        if request.method in permissions.SAFE_METHODS:
            return True

        return obj.username == request.user.username

如果你想的话,还可以为已认证的用户添加一些自定义处理。

接下来,你只需要把这个权限类添加到你的ModelViewSet里就可以了:

class UserViewSet(viewsets.ModelViewSet):

    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (IsAnonCreate, )
29

自定义 get_queryset 方法:

def get_queryset(self):
    if self.request.user.is_superuser:
        return User.objects.all()
    else:
        return User.objects.filter(id=self.request.user.id)

这样一来,已经登录的用户只能获取、修改或删除自己的对象。

指定 permission_classes = (AllowAny,) 这样可以让已经登录的用户创建一个新的对象。

编辑:根据评论进一步解释

这样自定义 get_queryset 方法意味着:

  1. 是的,未登录的用户可以发送 GET 请求来获取用户列表,但返回的列表会是空的,因为 User.objects.filter(id=self.request.user.id) 确保只返回已登录用户的信息。

  2. 其他方法也是如此,如果一个已登录的用户尝试删除另一个用户的对象,会返回一个详细信息:未找到(因为他试图访问的用户不在查询集中)。

  3. 已登录的用户可以对自己的用户对象随心所欲。

撰写回答