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
相关产品推荐
相关产品推荐

