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

使用邮箱为主键的自定义用户模型时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 21:01:55