AbstractUser 是 Django 提供的完整用户模型基类,直接继承它扩展字段最稳妥,但必须在首次迁移前定好并配置 AUTH_USER_MODEL;已有项目只能用代理模型或外键关联;支持邮箱登录需自定义认证后端、确保 email 字段 unique=True 且非空,并验证 request.user 类型为自定义模型。

AbstractUser 是 Django 提供的完整用户模型基类,含用户名、邮箱、密码、权限字段和常用方法。直接继承它来扩展字段(比如加手机号、头像、性别)是最稳妥的路径,但必须在项目首次迁移前就定好,否则会引发迁移冲突。
必须在 AUTH_USER_MODEL 中提前声明新模型
Django 不允许在已有数据库表后切换用户模型。哪怕只跑过一次 python manage.py migrate,再改 AUTH_USER_MODEL 就会报错:
ValueError: The AUTH_USER_MODEL setting must be in the format 'app_label.ModelName'
解决办法只有两个:
- 全新项目:立刻在
settings.py里写死AUTH_USER_MODEL = 'users.UserProfile'(对应你的 app 名和模型名) - 已有项目:不能硬切,只能用代理模型(
proxy=True)或外键关联方式补字段,无法把登录凭据逻辑迁移到邮箱上
CustomBackend.authenticate() 必须处理 email 和 username 双字段查询
默认 ModelBackend 只认 username。要支持邮箱登录,你得自己写后端,并注意几个关键点:
常见错误是只查 email=username,却忘了用户仍可能用用户名登录——实际场景中两者都得兼容。
正确做法是用 Q(username=username) | Q(email=username),且必须加异常捕获,否则空邮箱或重复邮箱会导致 500:
from django.contrib.auth.backends import ModelBackend from django.db.models import Q from .models import UserProfile <p>class CustomBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): if username is None: return None try: user = UserProfile.objects.get(Q(username=username) | Q(email=username)) if user.check_password(password): return user except UserProfile.DoesNotExist: return None except UserProfile.MultipleObjectsReturned:</p><h1>邮箱不唯一?赶紧加 unique=True 约束,否则这里必崩</h1><pre class="brush:python;toolbar:false;"> return None
别漏掉 AUTHENTICATION_BACKENDS 配置:
AUTHENTICATION_BACKENDS = ['apps.users.backends.CustomBackend', 'django.contrib.auth.backends.ModelBackend']
顺序很重要:CustomBackend 放前面,否则不会进你的逻辑。
邮箱字段必须设为 unique=True,且不能允许空值用于登录
继承 AbstractUser 时,原生 email 字段默认是 null=True, blank=True,这会导致两个问题:
① 用户注册时没填邮箱,后续用邮箱登录就查不到;
② 多个用户共用一个邮箱,get() 抛 MultipleObjectsReturned 异常,直接 500。
所以务必重定义 email 字段:
email = models.EmailField(verbose_name='邮箱', max_length=255, unique=True)
同时在表单或序列化器里强制校验非空——Django Admin 或 DRF 默认不会拦空邮箱提交。
登录成功后,request.user 的类型是你的自定义模型,不是 User
这是最容易被忽略的隐性坑。如果你在视图、模板或中间件里写了类似 if request.user.is_authenticated and request.user.username == 'admin' 这种判断,它依然能跑,但一旦你调用了自定义字段(比如 request.user.phone),而没确认模型类型,就可能抛 AttributeError。
更危险的是,在未刷新会话的情况下切换了用户模型——旧 session 里存的还是老 User 对象,request.user.phone 会直接报错。
验证方式很简单:
print(type(request.user)) # 应该输出
如果输出 User,说明要么 AUTH_USER_MODEL 没生效,要么用户是旧 session 登录的,需清空 django_session 表或重启开发服务器。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











