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

Django为College、Employee自定义模型实现密码重置功能的问题

自定义模型密码重置功能问题修复方案

你当前代码存在以下几类核心问题,对应修改方案如下:

一、核心问题梳理

  • Token验证不兼容:Django内置的default_token_generator仅适配继承了AbstractBaseUser的用户模型,你的自定义College、Employee模型未继承对应基类,缺少Token校验所需的get_session_auth_hash方法,生成的Token无法正常通过校验。
  • 路由配置错误:你注释了自定义的密码确认视图,使用了内置的auth_views.PasswordResetConfirmView,该视图默认查询Django内置User表,不会访问你的自定义模型表,导致验证始终失败。
  • 自定义视图逻辑错误:你写的password_reset_confirm是函数视图,但内部套了类视图才会用的get方法,实际请求进来时内部逻辑完全不会执行,直接返回空白模板。
  • 密码存储不安全:直接明文存储用户密码,且未使用Django的密码哈希机制,后续登录校验也会出现匹配失败问题。
  • 模型覆盖不全:当前逻辑仅处理了College模型的邮箱查询,Employee模型的密码重置逻辑完全缺失。
  • 异常捕获错误:查询自定义模型时捕获了内置User.DoesNotExist异常,无法捕获自定义模型的查询异常。

二、修改步骤

1. 调整自定义模型继承关系

修改models.py,让两个自定义模型继承AbstractBaseUser,自动获得密码哈希、Token校验所需的内置方法,同时删除重复的password、last_login字段:

from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser

class Employee(AbstractBaseUser):
    emp_id = models.CharField(primary_key=True, max_length=50)
    name = models.CharField(max_length=50)
    gender = models.CharField(max_length=50, default="None")
    email = models.EmailField(max_length=50)
    dept = models.CharField(max_length=50)
    phone = models.CharField(max_length=20)
    is_active = models.BooleanField(default=False)
    # 配置用户名字段
    USERNAME_FIELD = 'emp_id'
    REQUIRED_FIELDS = ['email', 'name']
   
class College(AbstractBaseUser):
    emp_id = models.CharField(primary_key=True, max_length=50)
    name = models.CharField(max_length=50)
    gender = models.CharField(max_length=50, default="None")
    email = models.EmailField(max_length=50)
    college = models.CharField(max_length=50)
    phone = models.CharField(max_length=20)
    is_active = models.BooleanField(default=False)
    USERNAME_FIELD = 'emp_id'
    REQUIRED_FIELDS = ['email', 'name']

2. 修改密码重置请求视图

调整password_reset_request逻辑,同时查询两个自定义模型,新增用户类型标记用于后续校验时区分模型:

from django.db.models import Q
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.core.mail import send_mail, BadHeaderError
from django.shortcuts import render, redirect
from django.contrib.auth.forms import PasswordResetForm
from .models import College, Employee

def password_reset_request(request):
    if request.method == "POST":
        password_reset_form = PasswordResetForm(request.POST)
        if password_reset_form.is_valid():
            data = password_reset_form.cleaned_data['email']
            # 同时查询两个模型的匹配邮箱
            associated_college = College.objects.filter(email=data)
            associated_employee = Employee.objects.filter(email=data)
            associated_users = list(associated_college) + list(associated_employee)
            
            if associated_users:
                for user in associated_users:
                    subject = "密码重置申请"
                    email_template_name = "password_reset_email.txt"
                    # 标记用户所属模型类型
                    user_type = "college" if isinstance(user, College) else "employee"
                    c = {
                        "email": user.email,
                        'domain': '127.0.0.1:8000',
                        'site_name': 'Two Factor',
                        "uid": urlsafe_base64_encode(force_bytes(user.pk)),
                        "user_type": user_type,
                        'token': default_token_generator.make_token(user),
                        'protocol': 'http',
                    }
                    email = render_to_string(email_template_name, c)
                    try:
                        send_mail(subject, email, 'myemailaddress' , [user.email], fail_silently=False)
                    except BadHeaderError:
                        return HttpResponse('无效的请求头')
                return redirect("password_reset_done")
    password_reset_form = PasswordResetForm()
    return render(request, template_name="password_reset.html", context={"password_reset_form": password_reset_form})

注意:需要同步修改password_reset_email.txt中的重置链接,新增user_type参数,格式为:{{ protocol }}://{{ domain }}/reset/{{ user_type }}/{{ uid }}/{{ token }}/

3. 调整路由配置

放开自定义密码确认视图的路由,删除内置视图的配置:

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views as core_views

urlpatterns = [
    path("password_reset_done", auth_views.PasswordResetDoneView.as_view(template_name='password_reset_done.html'), name='password_reset_done'),
    # 新增带user_type参数的自定义确认视图路由
    path('reset/<str:user_type>/<uidb64>/<token>/', core_views.password_reset_confirm, name='password_reset_confirm'),
    path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name="password_reset_complete.html"), name='password_reset_complete'), 
    path("password_reset", core_views.password_reset_request, name="password_reset"),
]

4. 重写密码确认视图

删除函数内部嵌套的类视图方法,按照函数视图逻辑重写:

from django.utils.http import urlsafe_base64_decode
from django.utils.encoding import force_str
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.hashers import make_password
from django.shortcuts import render, redirect

def password_reset_confirm(request, user_type, uidb64, token):
    user = None
    try:
        uid = force_str(urlsafe_base64_decode(uidb64))
        # 根据用户类型查询对应模型
        if user_type == 'college':
            user = College.objects.get(pk=uid)
        elif user_type == 'employee':
            user = Employee.objects.get(pk=uid)
    except (TypeError, ValueError, OverflowError, College.DoesNotExist, Employee.DoesNotExist):
        user = None

    # 校验Token有效性
    if user is not None and default_token_generator.check_token(user, token):
        if request.method == "POST":
            new_password = request.POST.get('password')
            # 哈希加密后存储密码
            user.password = make_password(new_password)
            user.save()
            return redirect('logi')
        return render(request, 'password_reset_confirm.html', {'validlink': True})      
    else:
        return render(request, 'password_reset_confirm.html', {'validlink': False})

三、额外注意事项

  • 建议在密码确认页面新增重复密码校验字段,避免用户输入错误
  • 旧的明文密码需要先做哈希迁移,否则老用户登录会失败
  • 如果不想继承AbstractBaseUser,可以使用itsdangerous库自定义带过期时间的Token生成与校验逻辑,适配任意自定义模型

内容的提问来源于stack exchange,提问作者Shree Har

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 14:06:05