Django Rest框架中修改用户密码的视图

7

我将使用Django Restful创建一个简单的API。我需要创建一个视图,让用户可以更改他/她的密码。我正在使用默认的Django用户模型和一个简单的UserSerializer。有一个名为set_password的方法,但我无法找到正确使用它与用户序列化器的方法。我在任何地方都找不到解决方案。

UserSelializer:

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ('id', "username", 'email', 'first_name', 'last_name', 'password')

视图(基于类):这是一个例子(我不知道我在这里做什么):

    class UserChangePassword(APIView):

        def patch(self, request):
            user = self.request.user
            serialized = UserSerializer(data=request.DATA)
            if serialized.is_valid():
                user.set_password(serialized.data['password'])
                user.save()
                return Response(status=status.HTTP_205_RESET_CONTENT)
            else:
            return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)

请注意,我想发布一个JSON脚本来更改密码。类似这样的内容:
 {
    "old_password": "123", 
    "new_password": "12345"
}

你为什么要调用 set_password() 两次? - Rod Xavier
不,UserSerializer 中的那个是用来将密码编码的。它与我想要的无关。我会把它删除,因为这会让一些人感到困惑。 - user3418042
你必须将它作为单独的视图吗?我在ModelViewSet内用另一种方式实现了它,像这样http://dpaste.com/0EBYX24。 - dado_eyad
4个回答

1
密码重置使用Viewset

在视图中

from rest_framework.decorators import detail_route, list_route, permission_classes
from rest_framework import viewsets
class UserProfileViewSet(viewsets.ViewSet):

    permission_classes = (AllowAny,)
    serializer_class = UserProfileSerializer

    def list(self, request):
        queryset = UserProfile.objects.all()
        serializer = self.serializer_class(queryset, many=True)
        return Response(serializer.data)

    def create(self, request):
        serializer = self.serializer_class(data=request.data)
        # check email address is exists or not.
        user_type = request.data['user_type']
        user_token = register_by_social(request.data['email'], request.data['username'], user_type)

        if not user_token or user_token == True:
            if not User.objects.filter(Q(email=request.data['email']) 
                | Q(username=request.data['username'])).exists():

                if serializer.is_valid():
                    userprofile = serializer.save()

                    return Response({
                        'status': status.HTTP_201_CREATED,
                        'message': 'Successfully signup new user.',
                        'token': userprofile.user.auth_token.key })

                return Response({
                    'status': status.HTTP_400_BAD_REQUEST,
                    'message': 'Please provided required fields.',
                    'error' : serializer.errors })

            return Response({
                'status': status.HTTP_409_CONFLICT,
                'message': 'Email address or username is already exists.'})

        return Response({
            'status': status.HTTP_200_OK,
            'message': 'Social user is already registered.',
            'token': user_token })

    @list_route(permission_classes=[IsAuthenticated], authentication_classes = (BasicAuthentication, TokenAuthentication), 
            methods=['post'], url_path='reset-user-password')
    def reset_user_password(self, request, pk=None):

        reset_password_serializer = UserResetPasswordSerializer(request.user, data=request.data)

        if reset_password_serializer.is_valid():

            if not request.user.check_password(request.data.get('password')):
                return Response({"password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)

            request.user.set_password(request.data.get('new_password'))
            request.user.save()
            return Response({"Message": ["Password reset successfully"]}, status=status.HTTP_200_OK)

您可以在serializer.py中仅为密码创建一个序列化器。
import django.contrib.auth.password_validation as validators
class UserResetPasswordSerializer(serializers.ModelSerializer):
    password = serializers.CharField(source='user.password', style={'input_type': 'password'},
        max_length=20, min_length=8)
    new_password = serializers.CharField(style={'input_type': 'password'},
        max_length=20, min_length=8)
    class Meta:
        model = User
        fields =("password", 'new_password')

1
一种方法是在序列化器中重写restore_object方法。代码如下:
class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ('id', "username", 'email', 'first_name', 'last_name', 'password')

    # turn text to hashed password
    def restore_object(self, attrs, instance=None):
        attrs['password'] = make_password(attrs['password'])
        return super(UserSerializer, self).restore_object(attrs, instance=None)

现在,当这个反序列化成一个对象实例时,你将会拥有一个有效的哈希密码。然后,通过稍微修改你当前的视图,你应该能够实现你想要的功能。
   class UserChangePassword(APIView):

       def patch(self, request):
           serialized = UserSerializer(data=request.DATA)
           if serialized.is_valid():
               serialized.save()
               return Response(status=status.HTTP_205_RESET_CONTENT)
           else:
               return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)

我认为您的PATCH请求中的JSON格式(取决于查找类型,默认为id)可能如下所示:

{
  "id": "83",
  "password": "12345"
}

我希望这可以帮到你!

编辑:

请注意,正如Symmetric在评论中指出的那样,restore_object已经在DRF 3.0中被弃用。


请注意,在DRF 3.0中,restore_object已被弃用,并已被create()和update()方法所取代;请参阅http://www.django-rest-framework.org/topics/3.0-announcement/#the-create-and-update-methods。 - Symmetric
完全正确,我将进行编辑以使其更加明确。 - jnishiyama

1
当我尝试在APIView中使用make_password时,出现了这个错误。
raise AttributeError("This QueryDict instance is immutable")
AttributeError: This QueryDict instance is immutable

class UserSerializer(serializers.ModelSerializer):
    resume = serializers.CharField(source='userprofile.resume')
    password = serializers.CharField(write_only=True)
    class Meta:
        model = User
        fields =  ('first_name', 'last_name', 'email', 'username', 'resume', 'password')

class UpdateUser(APIView):

    permission_classes = [IsAuthenticated]

    def patch(self, request):
        user = User.objects.get(username=request.user)
        if 'password' in request.data and request.data['password'] != '':
            request.data['password'] = make_password(request.data['password'])
        serializer = UserSerializer(user, data=request.data, many=False, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response({'message': "Error updated"}, status=status.HTTP_400_BAD_REQUEST)

我通过mutable_data = request.data.copy()来解决这个问题,我的问题是,这样做可以吗?还是我在做一些错误或不良的做法?
class UpdateUser(APIView):

    permission_classes = [IsAuthenticated]

    def patch(self, request):
        user = User.objects.get(username=request.user)
        mutable_data = request.data.copy()
        if 'password' in mutable_data and mutable_data['password'] != '':
            mutable_data['password'] = make_password(mutable_data['password'])
        serializer = UserSerializer(user, data=mutable_data, many=False, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response({'message': "Error updated"}, status=status.HTTP_400_BAD_REQUEST)

0

您的问题已在以下链接中得到解答:https://dev59.com/qF4c5IYBdhLWcg3wvsbB#27586192

不要在视图类中处理密码。在UserSerializer类的重写createupdate方法中添加set_password调用。

您可以使用ModelViewSet创建patch(部分更新)视图,例如:

class UserViewSet(viewsets.ModelViewSet):
    lookup_field = 'username'
    queryset = User.objects.all()
    serializer_class = serializers.UserSerializer

根据引用答案中的@DRC所述,在UserSerializer类上添加createupdate方法:
   def create(self, validated_data):
        user = get_user_model(**validated_data)
        user.set_password(validated_data['password'])
        user.save()
        return user

    def update(self, instance, validated_data):
        for f in UserSerializer.Meta.fields + UserSerializer.Meta.write_only_fields:
            set_attr(instance, f, validated_data[f])
        instance.set_password(validated_data['password'])
        instance.save()
        return instance

此外,您传入的JSON应该更像:

{
    "username": "mariodev", 
    "password": "12345"
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接