Django Rest API密码重置问题:无法处理重置确认的GET/POST请求
问题描述
基于Django Rest API实现密码重置功能,已完成密码重置请求环节:可接收用户邮箱,通过Postman成功发送重置邮件。但点击邮件链接进入密码重置确认页面后,无法处理GET和POST请求——GET请求应展示新密码输入字段,POST请求用于提交新密码并更新CustomUser自定义用户模型。
相关代码
serializers.py
from rest_framework import serializers from django.contrib.auth.hashers import make_password from .models import CustomUser class SignUpUserSerialzer(serializers.ModelSerializer): password = serializers.CharField(max_length=100, write_only=True) def validate_password(self, password): return make_password(password) class Meta: model = CustomUser fields = '__all__' class ChangePasswordSerializer(serializers.Serializer): model = CustomUser """ password = serializers.CharField(max_length=100, write_only=True) def validate_password(self, password): return make_password(password) class Meta: model = CustomUser fields = ['password'] """
urls.py
from django.urls import path,include from . import views # from .views import ChangePasswordView urlpatterns = [ path('register/', views.register, name='register'), path('login/', views.custom_login, name='login'), path('logout/', views.custom_logout, name='logout'), path('user/', views.user_profile, name='user_profile'), path('activate/<uidb64>/<token>', views.activate, name='activate'), path("passwordreset/", views.password_reset_request, name="password_reset"), path('reset/<uidb64>/<token>/', views.passwordResetConfirm, name='password_reset_confirm'), ]
models.py
class CustomUser(AbstractUser): email = models.EmailField(unique=True) def __str__(self): return self.username
views.py
from django.contrib.auth import authenticate, login, logout from django.contrib.auth.tokens import account_activation_token from django.contrib.auth.models import AbstractUser from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.encoding import force_bytes, force_str from django.core.mail import EmailMessage from django.template.loader import render_to_string from django.contrib.sites.shortcuts import get_current_site from django.contrib import messages from django.db.models import Q from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status from .models import CustomUser, Posts from .serializers import SignUpUserSerialzer, PostsSerializer, ChangePasswordSerializer @api_view(['POST']) def register(request): userser = SignUpUserSerialzer(data=request.data) if userser.is_valid(): user = userser.save(is_active = False) activateEmail(request, user, userser.validated_data['email']) return Response(userser.data) else: return Response(status=status.HTTP_404_NOT_FOUND) @api_view(['POST']) def custom_login(request): username=request.data['username'] password=request.data['password'] user = authenticate(username=username, password=password) if user is not None: login(request, user) return Response({"user": user.id}) else: return Response("error") @api_view(['POST']) def custom_logout(request): logout(request) return Response("logged out") def activateEmail(request, user, to_email): mail_subject = 'Activate your user account.' message = render_to_string('template_activate_account.html', { 'user': user.username, 'domain': get_current_site(request).domain, 'uid': urlsafe_base64_encode(force_bytes(user.pk)), 'token': account_activation_token.make_token(user), 'protocol': 'https' if request.is_secure() else 'http' }) email = EmailMessage(mail_subject, message, to=[to_email]) if email.send(): messages.success(request, f'Dear <b>{user}</b>, please go to you email <b>{to_email}</b> inbox and click on \ received activation link to confirm and complete the registration. <b>Note:</b> Check your spam folder.') else: messages.error(request, f'Problem sending confirmation email to {to_email}, check if you typed it correctly.') @api_view(['GET']) def activate(request, uidb64, token): User = get_user_model() try: uid = force_str(urlsafe_base64_decode(uidb64)) user = User.objects.get(pk=uid) except(TypeError, ValueError, OverflowError, User.DoesNotExist): user = None if user is not None and account_activation_token.check_token(user, token): user.is_active = True user.save() return Response('account activated') else: return Response('activation failed') @api_view(['GET']) def user_profile(request): user = request.user posts = Posts.objects.filter(user=user) serial = PostsSerializer(posts,many=True) return Response(serial.data) @api_view(['POST']) def password_reset_request(request): if request.method == 'POST': user_email = request.data['email'] associated_user = get_user_model().objects.filter(Q(email=user_email)).first() if associated_user: subject = "Password Reset request" message = render_to_string("template_reset_password.html", { 'user': associated_user, 'domain': get_current_site(request).domain, 'uid': urlsafe_base64_encode(force_bytes(associated_user.pk)), 'token': account_activation_token.make_token(associated_user), "protocol": 'https' if request.is_secure() else 'http' }) email = EmailMessage(subject, message, to=[associated_user.email]) if email.send(): messages.success(request, """ <h2>Password reset sent</h2><hr> <p> We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.<br>If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder. </p> """ ) return Response("Password reset sent") else: return Response("reset sent") @api_view(['GET','POST']) def passwordResetConfirm(request, uidb64, token): User = get_user_model() try: uid = force_str(urlsafe_base64_decode(uidb64)) user = User.objects.get(pk=uid) print("confr0") except: user = None print("confr1") print(user) if user is not None and account_activation_token.check_token(user, token): print("enter1") if request.method == 'POST': passser = ChangePasswordSerializer(data=request.data) print("enter2") if passser.is_valid(): print('passre1') passw = passser.save() print('passre2') return Response("pass succefuly changed") else: return Response("error") else: passser = ChangePasswordSerializer() return Response(passser.data) else: return Response(" major error")
解决方案
1. 修复ChangePasswordSerializer
解开注释并修改序列化器,添加密码字段,重写save方法关联目标用户:
class ChangePasswordSerializer(serializers.Serializer): password = serializers.CharField(max_length=100, write_only=True) def validate_password(self, password): # 对密码进行加密处理 return make_password(password) def save(self, **kwargs): # 获取传入的用户对象并更新密码 user = kwargs.get('user') if user: user.password = self.validated_data['password'] user.save() return user
2. 修复passwordResetConfirm视图
优化异常捕获范围,在POST请求时传递用户给序列化器,返回标准化响应:
@api_view(['GET','POST']) def passwordResetConfirm(request, uidb64, token): User = get_user_model() try: uid = force_str(urlsafe_base64_decode(uidb64)) user = User.objects.get(pk=uid) except (TypeError, ValueError, OverflowError, User.DoesNotExist): user = None if user is not None and account_activation_token.check_token(user, token): if request.method == 'POST': passser = ChangePasswordSerializer(data=request.data) if passser.is_valid(): # 将验证通过的用户传递给序列化器完成密码更新 passser.save(user=user) return Response({"message": "密码修改成功"}, status=status.HTTP_200_OK) else: # 返回具体的验证错误信息 return Response(passser.errors, status=status.HTTP_400_BAD_REQUEST) else: # GET请求返回密码输入字段的结构,供前端渲染输入框 passser = ChangePasswordSerializer() return Response(passser.data, status=status.HTTP_200_OK) else: return Response({"error": "链接无效或已过期"}, status=status.HTTP_400_BAD_REQUEST)
3. 额外优化建议
- 区分Token生成器:密码重置建议使用
django.contrib.auth.tokens.PasswordResetTokenGenerator,不要和账户激活的token混用,避免逻辑冲突。 - 完善错误反馈:返回具体的错误详情(如序列化器的验证错误),方便前端调试。
- 统一状态码:使用DRF的
status模块返回标准化HTTP状态码,而非自定义字符串。
内容的提问来源于stack exchange,提问作者Mohamed Stohy
相关产品推荐
相关产品推荐

