在首次迁移前设计用户模型
学习目标
使用 AbstractUser 保留 Django 的成熟认证能力,同时拥有将来可扩展的用户模型;在数据库初始化前正确设置 AUTH_USER_MODEL。
为什么要在这个时间点决定
Django 默认用户模型能满足基本需求,但真实项目常会增加邮箱、头像或个人简介。更换已使用的用户表会影响外键、迁移和第三方应用,成本远大于起步时建立一个很小的自定义模型。本站使用 user.Userinfo,练习项目使用 accounts.User,两者不能互换数据库。
AbstractUser 已包含用户名、哈希密码、登录时间、管理员标记等字段和认证接口。我们只把邮箱设置为必填且唯一,仍用用户名登录。模型的 unique=True 是数据库约束,不等于完整的邮件格式、大小写和业务校验;注册表单会在第 16 章规范化邮箱。
具体怎么做
- 替换
accounts/models.py,增加 User 类。 - 在配置中加入
AUTH_USER_MODEL = 'accounts.User',注册对应的 UserAdmin。 - 后面的模型用
settings.AUTH_USER_MODEL建立外键,运行时用get_user_model()获取类,避免硬编码默认 User。 - 仍先不迁移,下一章设计文章模型后,第 10 章一起生成初始迁移。
本章新增或替换的完整文件
以下路径均相对于练习项目根目录。替换同名文件的全部内容;未列出的文件保持上一章版本。
accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
@admin.register(User)
class AccountAdmin(UserAdmin):
search_fields = ("username", "email")
ordering = ("date_joined", "pk")
accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email = models.EmailField("邮箱", unique=True)
config/settings.py
import os
from pathlib import Path
from django.core.exceptions import ImproperlyConfigured
BASE_DIR = Path(__file__).resolve().parent.parent
DEBUG = os.environ.get("DJANGO_DEBUG", "1") == "1"
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "development-only-never-use-this-key-in-production")
if not DEBUG and (SECRET_KEY.startswith("development-") or len(SECRET_KEY) < 50):
raise ImproperlyConfigured("生产环境必须设置至少 50 字符的独立 DJANGO_SECRET_KEY")
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1,[::1]").split(",")
INSTALLED_APPS = [
"django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles",
"accounts.apps.AccountsConfig", "blog.apps.BlogConfig",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
TEMPLATES = [{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"], "APP_DIRS": True,
"OPTIONS": {"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
]},
}]
WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3"}}
AUTH_PASSWORD_VALIDATORS = [
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/Shanghai"
USE_I18N = True
USE_TZ = True
STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
MAILERS = {"default": {"BACKEND": "django.core.mail.backends.console.EmailBackend"}}
AUTH_USER_MODEL = "accounts.User"
如何确认成功
python manage.py check
python manage.py shell -c "from django.contrib.auth import get_user_model; print(get_user_model()._meta.label)"
应输出 accounts.User。现在只检查模型注册,不执行需要用户表的查询。
练习:在 shell 中查看 get_user_model().USERNAME_FIELD,预期为 username。解释邮箱唯一为什么不代表自动支持邮箱登录;认证后端和登录表单仍有各自职责。
常见问题
已经运行过 migrate 怎么办? 如果只是本教程空白实验库,可以新建练习目录重新开始,保留旧目录供核对;有真实数据时绝不能直接删库或改 AUTH_USER_MODEL,需要专门的数据迁移方案。能保存明文密码吗? 不能。使用 create_user()、set_password() 或 UserCreationForm,它们会调用密码哈希机制。管理员就是超级用户吗? is_staff 允许进入 Admin,具体模型操作还需要权限;is_superuser 拥有全部权限,日常编辑应使用权限更小的账号。
官方参考
登录后可保存阅读位置、标记章节完成,并在其他设备继续学习。
登录并保存学习进度 →联系站长
微信:VIPdjango。技术问题建议先在 社区讨论 中交流,方便更多人参与和查阅。
章节讨论 0