使用邮箱为主键的自定义用户模型时Django allauth密码重置反向匹配失败
解决Django allauth使用邮箱作为自定义用户模型主键时的密码重置故障
当自定义用户模型以邮箱作为主键时,测试密码修改功能会出现URL反向解析失败的错误,具体错误信息如下:
Reverse for 'account_reset_password_from_key' with keyword arguments '{'uidb36': 'mgodhrawala402@gmail.com', 'key': 'bbz25w-9c6941d5cb69a49883f15bc8e076f504'}' not found. 1 pattern(s) tried: ['accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$']
问题根源是allauth默认的uidb36参数正则仅允许字母数字字符,而邮箱包含@、.等特殊字符,导致URL匹配失败。以下是几种可行的解决方案:
方案1:改用数字ID作为主键(推荐)
此方案符合Django和allauth的默认设计逻辑,后续维护成本最低,适合开发阶段调整。
修改自定义用户模型
将主键改回Django默认的自增数字ID,邮箱设为唯一字段并作为登录标识:
from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): username = None # 禁用用户名字段 email = models.EmailField(unique=True) # 移除primary_key=True,使用默认id字段 USERNAME_FIELD = 'email' # 指定邮箱为登录验证字段 REQUIRED_FIELDS = []
配置settings.py
AUTH_USER_MODEL = 'your_app.CustomUser' ACCOUNT_AUTHENTICATION_METHOD = 'email' ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_UNIQUE_EMAIL = True ACCOUNT_USERNAME_REQUIRED = False
方案2:重写allauth的URL和视图适配邮箱主键
若业务必须保留邮箱作为主键,可修改URL正则和视图逻辑,直接接受邮箱作为uid参数。
覆盖allauth的密码重置URL
在项目urls.py中添加以下配置,覆盖allauth默认URL:
from django.urls import path from your_app.views import CustomPasswordResetFromKeyView urlpatterns = [ # 其他URL配置... path('accounts/password/reset/key/<uid>-<key>/', CustomPasswordResetFromKeyView.as_view(), name='account_reset_password_from_key'), ]
自定义密码重置视图
重写PasswordResetFromKeyView,修改用户查询逻辑:
from allauth.account.views import PasswordResetFromKeyView from django.contrib.auth import get_user_model User = get_user_model() class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): def get_user(self, uid): # 直接通过邮箱查询用户 try: return User.objects.get(email=uid) except User.DoesNotExist: return None
重写密码重置表单生成链接逻辑
修改重置邮件中的链接生成逻辑,直接传入邮箱作为uid:
from allauth.account.forms import PasswordResetForm from django.urls import reverse class CustomPasswordResetForm(PasswordResetForm): def save(self, request, **kwargs): email = self.cleaned_data["email"] for user in self.users_to_send(email): reset_url = reverse( "account_reset_password_from_key", kwargs={"uid": user.email, "key": self.generate_key(user)} ) # 发送重置邮件(复用原邮件模板即可) self.send_mail( 'account/email/password_reset_key', reset_url, user, request=request, ) return super().save(request, **kwargs)
在settings中配置自定义表单
ACCOUNT_FORMS = { 'reset_password': 'your_app.forms.CustomPasswordResetForm', }
方案3:编码邮箱为纯字母数字字符串
将邮箱编码为符合uidb36正则的纯字母数字字符串,无需修改URL结构。
自定义视图解码邮箱
from allauth.account.views import PasswordResetFromKeyView from django.contrib.auth import get_user_model from django.utils.http import urlsafe_base64_decode User = get_user_model() class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): def get_user(self, uidb36): try: # 解码获取原邮箱 email = urlsafe_base64_decode(uidb36).decode('utf-8') return User.objects.get(email=email) except (User.DoesNotExist, ValueError, TypeError): return None
重写表单生成编码后的uid
from allauth.account.forms import PasswordResetForm from django.urls import reverse from django.utils.http import urlsafe_base64_encode class CustomPasswordResetForm(PasswordResetForm): def save(self, request, **kwargs): email = self.cleaned_data["email"] for user in self.users_to_send(email): # 编码邮箱为纯字母数字字符串 encoded_uid = urlsafe_base64_encode(user.email.encode('utf-8')) reset_url = reverse( "account_reset_password_from_key", kwargs={"uidb36": encoded_uid, "key": self.generate_key(user)} ) self.send_mail( 'account/email/password_reset_key', reset_url, user, request=request, ) return super().save(request, **kwargs)
建议
如果应用处于开发初期,优先选择方案1,它完全遵循Django和allauth的设计规范,后续兼容性问题最少。若业务强制要求使用邮箱作为主键,可根据代码复杂度选择方案2或3,其中方案2逻辑更直观,方案3无需改动URL结构。
内容的提问来源于stack exchange,提问作者Mustansir Godhrawala
相关产品推荐
相关产品推荐

