我想问下面的代码提供更新密码但我想在当前密码确认过程后更新密码。那么我应该为它添加什么呢?谢谢。
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
答案 0 :(得分:47)
我认为使用模型序列化器可能是一种过度杀伤力。这个简单的序列化器&观点应该有效。
Serializers.py
from rest_framework import serializers
class ChangePasswordSerializer(serializers.Serializer):
"""
Serializer for password change endpoint.
"""
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
Views.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()
return Response("Success.", status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
答案 1 :(得分:13)
我不认为验证应按照@YiğitGüler的建议进行。这是我的解决方案:
serializers.py
from django.contrib.auth import password_validation
from django.utils.translation import gettext_lazy as _
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
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)
答案 2 :(得分:11)
@YiğitGüler给出了一个很好的答案,谢谢,但在一些小问题上可能会更好。
只要您不能使用 UpdateModelMixin ,而是直接使用请求用户实例,您就不需要使用 UpdateAPIView 。一个简单的 APIView 就足够了。
此外,更改密码后,您可以使用随机内容返回status.HTTP_204_NO_CONTENT
而不是200。
顺便说一下,在保存之前不要忘记验证新密码。如果您在创建时不允许更新“密码”,那就太糟糕了。
所以我在我的项目中使用以下代码:
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
对于观点:
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)
答案 3 :(得分:9)
保存用户后,您可能希望确保用户保持登录状态(在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)
答案 4 :(得分:2)
因此,我决定在ModelSerializer中重写更新功能。然后获取用户实例的密码。然后运行必要的比较,以通过check_password函数确保旧密码与当前在用户实例上的密码相同,并确保新密码和确认密码插槽值相同,然后继续设置新密码(如果为true)并保存。实例并返回它。
serializers.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
class ChangePasswordView(RetrieveUpdateAPIView):
queryset= User.objects.all()
serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated]
答案 5 :(得分:1)
我认为最简单(当我说最简单时,我指的是最短和最清洁的)解决方案是:
查看课程
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
序列化器类
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}
答案 6 :(得分:0)
serializer.py
class UserSer(serializers.ModelSerializers):
class meta:
model=UserModel
fields = '__all__'
views.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})
答案 7 :(得分:0)
如果您有ModelViewSet
,我想添加另一个选项。这样,您可能希望使用@action
进行密码更新,这样您仍然可以使用ModelViewSet
处理用户模型的各个方面,并且仍然可以自定义此操作所使用的行为和序列化程序,并且我还将添加一个自定义权限,以验证用户是否尝试更新自己的信息。
permissions.py:
from rest_framework import exceptions
from rest_framework.permissions import BasePermission, SAFE_METHODS
from django.utils.translation import gettext_lazy as _
from users.models import GeneralUser
class IsSelf(BasePermission):
def has_object_permission(self, request, view, obj):
if isinstance(obj, GeneralUser):
return request.user == obj
raise exceptions.PermissionDenied(detail=_("Received object of wrong instance"), code=403)
*我正在使用我的自定义用户模型类GeneralUser
views.py:
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from rest_framework.response import Response
from rest_framework import viewsets
from django.utils.translation import gettext_lazy as _
from users.api.serializers import UserSerializer, UserPwdChangeSerializer
from users.api.permissions import IsSelf
class UserViewSet(viewsets.ModelViewSet):
__doc__ = _(
"""
<Your Doc string>
"""
)
permission_classes = (IsAuthenticated, IsSelf)
serializer_class = UserSerializer
def get_queryset(self):
return GeneralUser.objects.filter(pk=self.request.user.pk)
def get_permissions(self):
if self.action == 'create':
permission_classes = [AllowAny]
else:
permission_classes = [IsAuthenticated]
return [permission() for permission in permission_classes]
# ....
# Your other actions or configurations
# ....
@action(detail=True, methods=["put"])
def upassword(self, request, pk=None):
user = GeneralUser.objects.get(pk=pk)
self.check_object_permissions(request, user)
ser = UserPwdChangeSerializer(user, data=request.data, many=False, context={
"user":request.user
})
ser.is_valid(raise_exception=True)
user = ser.save()
return Response(ser.data, status=status.HTTP_200_OK)
serializers.py:
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.hashers import make_password
from django.core import exceptions
from django.contrib.auth.password_validation import validate_password as v_passwords
from rest_framework import serializers
from users.models import GeneralUser
class UserSerializer(serializers.ModelSerializer):
__doc__ = _(
"""
Serializer for User model
"""
)
class Meta:
model = GeneralUser
fields = '__all__'
read_only_fields = ["last_login", "date_joined"]
extra_kwargs = {'password': {'write_only': True}}
def validate_password(self, value: str) -> str:
try:
v_passwords(value, GeneralUser)
return make_password(value)
except exceptions.ValidationError as e:
raise serializers.ValidationError(e.messages)
class UserPwdChangeSerializer(serializers.Serializer):
__doc__ = _(
"""
Serializer for user model password change
"""
)
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['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.")})
v_passwords(data['new_password1'], self.context['user'])
return data
def save(self, **kwargs):
password = self.validated_data['new_password1']
user = self.context['user']
user.set_password(password)
user.save()
return user
我使用@Pedro的答案来配置UserPwdChangeSerializer
通过此实现,您将拥有一个用于所有字段更新和用户创建的功能ModelViewSet
,以及用于密码更新的操作,其中您将能够使用旧密码并验证新密码密码输入正确两次。
自定义密码更改将在您为用户使用的url路径内创建,可能类似于:
api/users/<user_pk>/upassword
答案 8 :(得分:0)
编辑:使用 capcha 或类似的东西来逃避蛮力......
我用我自己的hacky方式做到了! 可能不是最好的方法,但我发现它更好理解,,,
** 随意询问是否有任何东西似乎是保镖,我总是鼓励问题和反馈...... **
我为它创建了一个模型。
class PasswordReset(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
key = models.CharField(max_length=100)
timestamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
添加了这样的网址...
urlpatterns = [
path("request/", password_reset_request),
path("confirm/", password_reset_confirm),
]
这里有我们的观点...
@api_view(["POST"])
@permission_classes([AllowAny])
def password_reset_request(request):
# checking username
queryset = User.objects.filter(username=request.POST.get("username"))
if queryset.exists():
user = queryset.first()
else:
return Response({"error": "User does not exists!"})
# Checking for password reset model
queryset = PasswordReset.objects.filter(user=user)
if queryset.exists():
password_reset = PasswordReset.first()
# checking for last password reset
if password_reset.timestamp < timezone.now() - timedelta(days=1):
# password is not recently updated
password_reset.delete()
password_reset = PasswordReset(
user=user,
key="".join(
[choice("!@$_-qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890") for i in range(99)]
),
)
password_reset.save()
# send email here
subject = "Password reset request"
message = """To reset your password, go to localhost:8000/password_reset/{}""".format(password_reset.key)
from_email = "notes@frozenmatrix.com"
recipient_list = [user.email]
auth_user = "digital.mechanics.00@gmail.com"
auth_password = "mechanicsareawesomeagain"
send_mail(subject, message, from_email, recipient_list, auth_user=auth_user, auth_password=auth_password)
else:
# recent password updated
return Response({"error": "Your password was updated recently, wait before updating it again."})
@api_view(["POST"])
@permission_classes([AllowAny])
def password_reset_confirm(request):
# checking key
queryset = PasswordReset.objects.filter(key=request.POST.get("key"))
if queryset.exists():
password_reset = queryset.first()
if password_reset.timestamp < timezone.now() - timedelta(minutes=30):
# expired
return Response({"error": "Password reset key is expired! Try fresh after some hours."})
else:
# valid
password = request.POST.get("password", "")
if password == "":
# valid key and waiting for password
return Response({"success": "Set a new password"})
else:
# seting up the password
user = password_reset.user
user.set_password(password)
user.save()
return Response({"success": "Password updated successfully."})
else:
# invalid key
return Response({"error": "Invalid key"})