You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 22:34:54