Python 如何在 Django Rest Framework 中更新用户密码?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38845051/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 21:29:41  来源:igfitidea点击:

How to update user password in Django Rest Framework?

pythondjangodjango-rest-framework

提问by bysucpmeti

I want to ask that following code provides updating password but I want to update password after current password confirmation process. So what should I add for it? Thank you.

我想问以下代码提供更新密码,但我想在当前密码确认过程后更新密码。那么我应该为它添加什么?谢谢你。

class UserPasswordSerializer(ModelSerializer):

    class Meta:
        model = User
        fields = [
            'password'
        ]

        extra_kwargs = {
            "password": {"write_only": True},
        }

    def update(self, instance, validated_data):
        for attr, value in validated_data.items():
            if attr == 'password':
                instance.set_password(value)
            else:
                setattr(instance, attr, value)
        instance.save()
        return instance

回答by Yi?it Güler

I believe that using a modelserializer might be an overkill. This simple serializer & view should work.

我相信使用模型序列化器可能是一种矫枉过正。这个简单的序列化器和视图应该可以工作。

Serializers.py

序列化器.py

from rest_framework import serializers
from django.contrib.auth.models import User

class ChangePasswordSerializer(serializers.Serializer):
    model = User

    """
    Serializer for password change endpoint.
    """
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True)

Views.py

视图.py

from rest_framework import status
from rest_framework import generics
from rest_framework.response import Response
from django.contrib.auth.models import User
from . import serializers
from rest_framework.permissions import IsAuthenticated   

class ChangePasswordView(UpdateAPIView):
        """
        An endpoint for changing password.
        """
        serializer_class = ChangePasswordSerializer
        model = User
        permission_classes = (IsAuthenticated,)

        def get_object(self, queryset=None):
            obj = self.request.user
            return obj

        def update(self, request, *args, **kwargs):
            self.object = self.get_object()
            serializer = self.get_serializer(data=request.data)

            if serializer.is_valid():
                # Check old password
                if not self.object.check_password(serializer.data.get("old_password")):
                    return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
                # set_password also hashes the password that the user will get
                self.object.set_password(serializer.data.get("new_password"))
                self.object.save()
                response = {
                    'status': 'success',
                    'code': status.HTTP_200_OK,
                    'message': 'Password updated successfully',
                    'data': []
                }

                return Response(response)

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

回答by tominardi

@Yi?it Güler give a good answer, thanks, but it could be better in some minor points.

@Yi?it Güler 给出了一个很好的答案,谢谢,但在一些小问题上可能会更好。

As long you don't really works with UpdateModelMixin, but directly with the request user instance, you don't need to use a UpdateAPIView. A simple APIViewis enough.

只要您不真正使用UpdateModelMixin,而是直接使用请求用户实例,您就不需要使用UpdateAPIView。一个简单的APIView就足够了。

Also, when the password is changed, you can return a status.HTTP_204_NO_CONTENTinstead of a 200 with some random content.

此外,当密码更改时,您可以返回 astatus.HTTP_204_NO_CONTENT而不是 200 带有一些随机内容。

By the way, don't forgot to validate your new password before save. It's too bad if you allow "password" at update while you don't at create.

顺便说一句,在保存之前不要忘记验证您的新密码。如果您在不创建时允许在更新时使用“密码”,那就太糟糕了。

So I use the following code in my project:

所以我在我的项目中使用了以下代码:

from django.contrib.auth.password_validation import validate_password

class ChangePasswordSerializer(serializers.Serializer):
    """
    Serializer for password change endpoint.
    """
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True)

    def validate_new_password(self, value):
        validate_password(value)
        return value

And for the view:

对于视图:

class UpdatePassword(APIView):
    """
    An endpoint for changing password.
    """
    permission_classes = (permissions.IsAuthenticated, )

    def get_object(self, queryset=None):
        return self.request.user

    def put(self, request, *args, **kwargs):
        self.object = self.get_object()
        serializer = ChangePasswordSerializer(data=request.data)

        if serializer.is_valid():
            # Check old password
            old_password = serializer.data.get("old_password")
            if not self.object.check_password(old_password):
                return Response({"old_password": ["Wrong password."]}, 
                                status=status.HTTP_400_BAD_REQUEST)
            # set_password also hashes the password that the user will get
            self.object.set_password(serializer.data.get("new_password"))
            self.object.save()
            return Response(status=status.HTTP_204_NO_CONTENT)

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

回答by Michael van de Waeter

After you save the user, you might want to make sure that the user stays logged in (after django==1.7 an user automatically is logged out on password change):

保存用户后,您可能希望确保用户保持登录状态(在 django==1.7 之后,用户会在密码更改时自动注销):

from django.contrib.auth import update_session_auth_hash

# make sure the user stays logged in
update_session_auth_hash(request, self.object)

回答by Pedro

I dont' think the validation should be done by the view as @Yi?it Güler proposes. Here is my solution:

我不认为验证应该由@Yi?it Güler 提议的视图完成。这是我的解决方案:

serializers.py

序列化程序.py

from django.contrib.auth import password_validation
from rest_framework import serializers

class ChangePasswordSerializer(serializers.Serializer):
    old_password = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password1 = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password2 = serializers.CharField(max_length=128, write_only=True, required=True)

    def validate_old_password(self, value):
        user = self.context['request'].user
        if not user.check_password(value):
            raise serializers.ValidationError(
                _('Your old password was entered incorrectly. Please enter it again.')
            )
        return value

    def validate(self, data):
        if data['new_password1'] != data['new_password2']:
            raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")})
        password_validation.validate_password(data['new_password1'], self.context['request'].user)
        return data

    def save(self, **kwargs):
        password = self.validated_data['new_password1']
        user = self.context['request'].user
        user.set_password(password)
        user.save()
        return user

view.py

查看.py

from rest_framework.generics import UpdateAPIView
from rest_framework.authtoken.models import Token

class ChangePasswordView(UpdateAPIView):
    serializer_class = ChangePasswordSerializer

    def update(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        # if using drf authtoken, create a new token 
        if hasattr(user, 'auth_token'):
            user.auth_token.delete()
        token, created = Token.objects.get_or_create(user=user)
        # return new token
        return Response({'token': token.key}, status=status.HTTP_200_OK)

回答by danielrosheuvel

So I decided to override the update function within ModelSerializer. Then get the password of the User instance. Afterwards run the necessary comparisons of making sure old password is the same as the one currently on the user instance via the check_password function and making sure new password and confirm password slot values are the same then proceed to set the new password if true and save the instance and return it.

所以我决定覆盖 ModelSerializer 中的更新功能。然后获取 User 实例的密码。然后通过 check_password 函数运行必要的比较以确保旧密码与当前用户实例上的相同,并确保新密码和确认密码槽值相同,然后继续设置新密码(如果为真)并保存实例并返回它。

serializers.py

序列化程序.py

   class ChangePasswordSerializer(ModelSerializer):
    confirm_password = CharField(write_only=True)
    new_password = CharField(write_only=True)
    old_password = CharField(write_only=True)

    class Meta:
        model = User
        fields = ['id', 'username', 'password', 'old_password', 'new_password','confirm_password']



    def update(self, instance, validated_data):

        instance.password = validated_data.get('password', instance.password)

        if not validated_data['new_password']:
              raise serializers.ValidationError({'new_password': 'not found'})

        if not validated_data['old_password']:
              raise serializers.ValidationError({'old_password': 'not found'})

        if not instance.check_password(validated_data['old_password']):
              raise serializers.ValidationError({'old_password': 'wrong password'})

        if validated_data['new_password'] != validated_data['confirm_password']:
            raise serializers.ValidationError({'passwords': 'passwords do not match'})

        if validated_data['new_password'] == validated_data['confirm_password'] and instance.check_password(validated_data['old_password']):
            # instance.password = validated_data['new_password'] 
            print(instance.password)
            instance.set_password(validated_data['new_password'])
            print(instance.password)
            instance.save()
            return instance
        return instance

views.py

视图.py

    class ChangePasswordView(RetrieveUpdateAPIView):
        queryset= User.objects.all()
        serializer_class = ChangePasswordSerializer
        permission_classes = [IsAuthenticated]

回答by Igor

I think the easiest (when I say easiest, I mean shortest possible and cleaner) solution would be something like:

我认为最简单的(当我说最简单时,我的意思是尽可能短和更干净)的解决方案是这样的:

View class

查看班级

class APIChangePasswordView(UpdateAPIView):
    serializer_class = UserPasswordChangeSerializer
    model = get_user_model() # your user model
    permission_classes = (IsAuthenticated,)

    def get_object(self, queryset=None):
        return self.request.user

Serializer class

序列化器类

from rest_framework import serializers
from rest_framework.serializers import Serializer


class UserPasswordChangeSerializer(Serializer):
    old_password = serializers.CharField(required=True, max_length=30)
    password = serializers.CharField(required=True, max_length=30)
    confirmed_password = serializers.CharField(required=True, max_length=30)

    def validate(self, data):
        # add here additional check for password strength if needed
        if not self.context['request'].user.check_password(data.get('old_password')):
            raise serializers.ValidationError({'old_password': 'Wrong password.'})

        if data.get('confirmed_password') != data.get('password'):
            raise serializers.ValidationError({'password': 'Password must be confirmed correctly.'})

        return data

    def update(self, instance, validated_data):
        instance.set_password(validated_data['password'])
        instance.save()
        return instance

    def create(self, validated_data):
        pass

    @property
    def data(self):
        # just return success dictionary. you can change this to your need, but i dont think output should be user data after password change
        return {'Success': True}

回答by Anar Ali

serializer.py

序列化程序.py

class UserSer(serializers.ModelSerializers):
      class meta:
          model=UserModel
          fields = '__all__'


views.py

视图.py

class UserView(UpdateAPIView):
    serializer_class = serializers.UserSer
    queryset = models.User.objects.all()

    def get_object(self,pk):
        try:
            return models.User.objects.get(pk=pk)
        except Exception as e:
            return Response({'message':str(e)})

    def put(self,request,pk,format=None):
        user = self.get_object(pk) 
        serializer = self.serializer_class(user,data=request.data)

        if serializer.is_valid():            
            serializer.save()
            user.set_password(serializer.data.get('password'))
            user.save()
            return Response(serializer.data)    
        return Response({'message':True})