阶段 10:用户扩展档案信息表
合辑 :Django 系统阶段开发手册 | 阶段 :10 / 18 | 定位 :新增用户扩展档案表并接入迁移与后台管理 | 建议学时:4--6 小时
大家好,我是小马不起床。
阶段导语
本阶段在权限链路(用户 → 角色 → 权限)之外,开辟第二条数据主线:用户 → 用户扩展档案。通过新增 sys_user_profile 表并与 sys_user 建立外键(Foreign Key)关系,为后续档案查询与修改接口提供数据基础。
本阶段仅完成数据表与模型层的构建:定义模型、生成并执行迁移(migration)、接入 Django Admin,并在 MySQL 中校验建表结果。档案接口、Vue 页面、头像上传与权限控制均不在本阶段范围内。
学习目标
完成本阶段后,应能够:
- 依据给定 SQL 设计用户扩展档案表,并说明其与
sys_user的一对一关联关系 - 在
rbac/models.py中定义SysUserProfile模型,正确使用外键字段与choices枚举 - 将模型注册到 Django Admin,配置列表展示、搜索与过滤项
- 执行
makemigrations与migrate,并在 MySQL 中确认建表成功 - 通过 Django Shell 与 SQL 联合查询创建并核验档案测试数据
前置知识
本阶段依赖阶段 9(阶段 9:前后端完整联调和问题修复.md)的内容:
| 前置条目 | 具体要求 |
|---|---|
| RBAC 四表 | 理解 sys_user、sys_role、sys_role_permission、sys_permission 的关系 |
| SysUser 模型 | 已存在 SysUser 模型,含 user_id、username、real_name 等字段 |
| 迁移流程 | 掌握 makemigrations 与 migrate 的基本用法 |
| Django Admin | 了解模型的后台注册方式 |
若上述内容尚不熟悉,建议先完成阶段 9 的学习。
目录
- 数据表设计与目标范围
- [定义 SysUserProfile 模型](#定义 SysUserProfile 模型)
- 模型字段说明
- [注册到 Django Admin](#注册到 Django Admin)
- 生成并执行迁移
- [校验 MySQL 表结构](#校验 MySQL 表结构)
- 创建与查看测试档案
- 可选:将档案纳入初始化命令
- 阶段完成标准与系统关系
- 阶段小结
- 常见问题与排查
- 下一阶段衔接
1. 数据表设计与目标范围
1.1 本阶段要实现与暂不实现的内容
| 本阶段实现 | 本阶段暂不实现 |
|---|---|
新增 sys_user_profile 表 |
档案接口 |
让 sys_user_profile 关联 sys_user |
Vue 页面 |
| 在 Django Admin 后台管理用户档案 | 头像上传 |
| 在 MySQL 中确认表创建成功 | 权限控制 |
1.2 建表 SQL
本阶段使用的建表语句如下:
sql
CREATE TABLE `sys_user_profile` (
`profile_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '档案ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`level` INT DEFAULT NULL COMMENT '等级',
`gender` INT DEFAULT NULL COMMENT '性别',
`birthday` DATE DEFAULT NULL COMMENT '生日',
`height_cm` DECIMAL(5,2) DEFAULT NULL COMMENT '身高(cm)',
`weight_kg` DECIMAL(5,2) DEFAULT NULL COMMENT '体重(kg)',
`avatar_url` VARCHAR(255) DEFAULT NULL COMMENT '头像地址',
`intro` TEXT DEFAULT NULL COMMENT '简介',
`create_time` DATETIME DEFAULT NULL COMMENT '账号创建时间',
`join_time` DATETIME DEFAULT NULL COMMENT '入职/开卡时间',
`expire_date` DATE DEFAULT NULL COMMENT '到期/停卡时间',
PRIMARY KEY (`profile_id`),
CONSTRAINT `fk_profile_user` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`)
) ENGINE=InnoDB COMMENT='用户扩展档案信息';
该表与用户表的关系为:
text
sys_user
↓ user_id
sys_user_profile
即一个用户可以有一份扩展档案信息。需要说明的是,该 SQL 未对 user_id 施加唯一约束,数据库层面允许一个用户存在多条档案记录;本阶段严格沿用该 SQL 定义实现。
要点:
- 要点:本阶段只构建数据表与模型层,接口与前端不在范围内。
- 要点 :
sys_user_profile通过user_id外键关联sys_user.user_id。 - 要点 :建表 SQL 未对
user_id设唯一约束,数据库允许一个用户多条档案。
2. 定义 SysUserProfile 模型
打开文件 backend/rbac/models.py,在文件末尾、SysUser 类之后追加以下模型:
python
class SysUserProfile(models.Model):
"""
用户扩展档案信息表
对应 MySQL 表:sys_user_profile
"""
GENDER_CHOICES = (
(0, "未知"),
(1, "男"),
(2, "女"),
)
profile_id = models.BigAutoField(
primary_key=True,
verbose_name="档案ID"
)
user = models.ForeignKey(
SysUser,
on_delete=models.PROTECT,
db_column="user_id",
related_name="profiles",
verbose_name="用户ID"
)
level = models.IntegerField(
null=True,
blank=True,
verbose_name="等级"
)
gender = models.IntegerField(
null=True,
blank=True,
choices=GENDER_CHOICES,
verbose_name="性别"
)
birthday = models.DateField(
null=True,
blank=True,
verbose_name="生日"
)
height_cm = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
verbose_name="身高(cm)"
)
weight_kg = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
verbose_name="体重(kg)"
)
avatar_url = models.CharField(
max_length=255,
null=True,
blank=True,
verbose_name="头像地址"
)
intro = models.TextField(
null=True,
blank=True,
verbose_name="简介"
)
create_time = models.DateTimeField(
null=True,
blank=True,
verbose_name="账号创建时间"
)
join_time = models.DateTimeField(
null=True,
blank=True,
verbose_name="入职/开卡时间"
)
expire_date = models.DateField(
null=True,
blank=True,
verbose_name="到期/停卡时间"
)
class Meta:
db_table = "sys_user_profile"
verbose_name = "用户扩展档案"
verbose_name_plural = "用户扩展档案"
def __str__(self):
return f"{self.user.username} 的档案"
要点:
- 要点 :模型追加在
SysUser类之后,Meta.db_table固定为sys_user_profile,与 SQL 表名一致。 - 要点 :
user字段以db_column="user_id"复用 SQL 中的列名,避免 Django 自动生成user_id_id。 - 要点 :
__str__返回「用户名 + 的档案」,用于 Admin 列表显示。
3. 模型字段说明
3.1 字段一览
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
profile_id |
BigAutoField | 主键、自增 | 档案表主键 |
user |
ForeignKey(SysUser) | db_column="user_id"、on_delete=PROTECT、related_name="profiles" |
当前档案所属的用户 |
level |
IntegerField | null、blank | 等级 |
gender |
IntegerField | null、blank、choices=GENDER_CHOICES | 性别,存数字 |
birthday |
DateField | null、blank | 生日 |
height_cm |
DecimalField(5,2) | null、blank | 身高(cm) |
weight_kg |
DecimalField(5,2) | null、blank | 体重(kg) |
avatar_url |
CharField(255) | null、blank | 头像地址 |
intro |
TextField | null、blank | 简介 |
create_time |
DateTimeField | null、blank | 账号创建时间 |
join_time |
DateTimeField | null、blank | 入职/开卡时间 |
expire_date |
DateField | null、blank | 到期/停卡时间 |
3.2 关键字段解释
profile_id 对应 SQL 的 profile_id BIGINT NOT NULL AUTO_INCREMENT,Django 写法为 models.BigAutoField(primary_key=True),作为档案表自增主键。
user 对应 SQL 的外键约束 CONSTRAINT fk_profile_user FOREIGN KEY (user_id) REFERENCES sys_user (user_id),Django 写法为:
python
user = models.ForeignKey(
SysUser,
on_delete=models.PROTECT,
db_column="user_id"
)
其作用为标记当前档案属于哪个用户,例如 admin 用户对应 admin 的档案、zhangsan 用户对应 zhangsan 的档案。此处使用 on_delete=models.PROTECT,含义是当用户仍有档案时不允许直接删除该用户,属于较安全的删除策略。
gender 字段通过 GENDER_CHOICES 定义枚举,数据库存储数字,Admin 后台显示中文:
| 存储值 | 显示文本 |
|---|---|
| 0 | 未知 |
| 1 | 男 |
| 2 | 女 |
height_cm 与 weight_kg 对应 SQL 的 DECIMAL(5,2),Django 写法为 models.DecimalField(max_digits=5, decimal_places=2),表示最多 5 位数字、其中 2 位小数,例如 180.50、65.20。
要点:
- 要点 :
on_delete=PROTECT阻止删除仍持有档案的用户。 - 要点 :
db_column="user_id"使外键列名与既有 SQL 保持一致。 - 要点 :
gender存数字、经choices在后台映射中文。 - 要点 :
DECIMAL(5,2)对应max_digits=5, decimal_places=2。
4. 注册到 Django Admin
打开文件 backend/rbac/admin.py,先修改导入。原语句可能为:
python
from .models import SysPermission, SysRole, SysRolePermission, SysUser
改为:
python
from .models import (
SysPermission,
SysRole,
SysRolePermission,
SysUser,
SysUserProfile,
)
在文件末尾追加注册代码:
python
@admin.register(SysUserProfile)
class SysUserProfileAdmin(admin.ModelAdmin):
list_display = (
"profile_id",
"user",
"level",
"gender",
"birthday",
"height_cm",
"weight_kg",
"join_time",
"expire_date",
)
search_fields = (
"user__username",
"user__real_name",
"intro",
)
list_filter = (
"gender",
"level",
)
要点:
- 要点 :导入需补入
SysUserProfile,否则注册时报错。 - 要点 :
search_fields使用user__username等跨关系字段,支持按用户名与真实姓名检索。 - 要点 :
list_filter提供性别与等级的过滤侧栏。
5. 生成并执行迁移
确认位于后端目录 rbac_project/backend,生成迁移文件:
bash
python3 manage.py makemigrations rbac
正常输出类似:
text
Migrations for 'rbac':
rbac/migrations/0002_sysuserprofile.py
+ Create model SysUserProfile
随后执行迁移建表:
bash
python3 manage.py migrate
正常输出类似:
text
Applying rbac.0002_sysuserprofile... OK
这一步完成后,MySQL 中新增一张 sys_user_profile 表。
要点:
- 要点 :
makemigrations生成0002_sysuserprofile.py迁移文件,migrate将其落到数据库。 - 要点:两条命令需在后端目录执行,工作目录错误会导致找不到 app。
- 要点 :迁移成功的关键标志是输出
Applying rbac.0002_sysuserprofile... OK。
6. 校验 MySQL 表结构
进入 MySQL 并选择数据库:
bash
mysql -u root -p
sql
USE rbac_db;
查看全部表:
sql
SHOW TABLES;
结果中应出现 sys_user_profile。查看表结构:
sql
DESC sys_user_profile;
应看到如下字段:profile_id、user_id、level、gender、birthday、height_cm、weight_kg、avatar_url、intro、create_time、join_time、expire_date。校验完成后退出 MySQL:
sql
exit;
要点:
- 要点 :
SHOW TABLES确认表存在,DESC确认列齐全。 - 要点 :
DESC结果中的列名为user_id(由db_column指定),而非user_id_id。
7. 创建与查看测试档案
7.1 在 Django Shell 创建测试档案
进入 shell:
bash
python manage.py shell
输入:
python
from datetime import date, datetime
from rbac.models import SysUser, SysUserProfile
admin = SysUser.objects.get(username="admin")
profile = SysUserProfile.objects.create(
user=admin,
level=1,
gender=1,
birthday=date(1998, 1, 1),
height_cm=175.50,
weight_kg=70.25,
avatar_url="https://example.com/avatar.png",
intro="这是管理员的扩展档案",
create_time=datetime.now(),
join_time=datetime.now(),
expire_date=date(2030, 12, 31),
)
profile
若返回类似 <SysUserProfile: admin 的档案>,说明创建成功。退出 shell:
python
exit()
7.2 在 MySQL 查看档案数据
进入 MySQL 并选择数据库:
bash
mysql -u root -p
sql
USE rbac_db;
执行联合查询:
sql
SELECT
p.profile_id,
u.username,
u.real_name,
p.level,
p.gender,
p.birthday,
p.height_cm,
p.weight_kg,
p.avatar_url,
p.intro,
p.create_time,
p.join_time,
p.expire_date
FROM sys_user_profile p
JOIN sys_user u ON p.user_id = u.user_id;
结果中可看到刚创建的 admin 档案。
要点:
- 要点 :
objects.create(user=admin, ...)直接传入SysUser实例建立关联。 - 要点 :Shell 返回
<SysUserProfile: admin 的档案>即代表写入成功。 - 要点 :联合查询通过
JOIN sys_user展示档案所属用户名与真实姓名。
8. 可选:将档案纳入初始化命令
若希望执行初始化命令时自动为 admin 与 zhangsan 创建档案:
bash
python manage.py init_rbac_data
可修改文件 backend/rbac/management/commands/init_rbac_data.py。先修改导入:
python
from rbac.models import SysPermission, SysRole, SysUser, SysUserProfile
然后在文件末尾(即创建 admin 与 zhangsan 用户之后)追加:
python
from datetime import date, datetime
admin_user = SysUser.objects.filter(username="admin").first()
zhangsan_user = SysUser.objects.filter(username="zhangsan").first()
if admin_user and not SysUserProfile.objects.filter(user=admin_user).exists():
SysUserProfile.objects.create(
user=admin_user,
level=10,
gender=1,
birthday=date(1998, 1, 1),
height_cm=175.00,
weight_kg=70.00,
avatar_url="",
intro="管理员账号",
create_time=datetime.now(),
join_time=datetime.now(),
expire_date=date(2030, 12, 31),
)
self.stdout.write(
self.style.SUCCESS("创建 admin 用户档案")
)
if zhangsan_user and not SysUserProfile.objects.filter(user=zhangsan_user).exists():
SysUserProfile.objects.create(
user=zhangsan_user,
level=1,
gender=1,
birthday=date(2000, 5, 1),
height_cm=172.00,
weight_kg=65.00,
avatar_url="",
intro="普通用户账号",
create_time=datetime.now(),
join_time=datetime.now(),
expire_date=date(2030, 12, 31),
)
self.stdout.write(
self.style.SUCCESS("创建 zhangsan 用户档案")
)
追加后重新执行:
bash
python manage.py init_rbac_data
要点:
- 要点 :以
filter(user=...).exists()做幂等判断,避免重复初始化时产生多条档案。 - 要点 :档案创建代码需放在用户创建之后,确保
admin、zhangsan已存在。 - 要点:本步骤为可选项,不影响阶段主流程。
9. 阶段完成标准与系统关系
9.1 完成标准
需逐项确认:
models.py中新增SysUserProfile模型admin.py中注册SysUserProfilemakemigrations成功migrate成功- MySQL 中出现
sys_user_profile表 - Django Admin 中能看到「用户扩展档案」
- 可以给 admin 或 zhangsan 创建档案数据
9.2 当前系统关系
权限主线保持不变:
text
sys_user
↓ role_id
sys_role
↓
sys_role_permission
↓
sys_permission
本阶段新增档案主线:
text
sys_user
↓ user_id
sys_user_profile
系统内由此形成两条主线:
| 主线 | 链路 |
|---|---|
| 权限主线 | 用户 → 角色 → 权限 |
| 档案主线 | 用户 → 用户扩展档案 |
要点:
- 要点:完成标准覆盖模型、注册、迁移、建表、后台与测试数据六类校验。
- 要点 :权限主线与档案主线均以
sys_user为起点,互不干扰。
阶段小结
| 环节 | 核心操作 | 结果 |
|---|---|---|
| 表设计 | 依据给定 SQL 定义 sys_user_profile |
与 sys_user 建立 user_id 外键关系 |
| 模型定义 | 在 rbac/models.py 追加 SysUserProfile |
db_table 与 SQL 表名一致 |
| 字段映射 | 外键、choices、DecimalField 等 |
数字存库、后台显示中文 |
| Admin 注册 | @admin.register 配置展示/搜索/过滤 |
后台可管理用户档案 |
| 迁移 | makemigrations + migrate |
MySQL 新增 sys_user_profile |
| 建表校验 | SHOW TABLES / DESC |
确认表与列存在 |
| 测试数据 | Shell create + SQL 联合查询 |
生成并核验 admin 档案 |
| 初始化集成(可选) | 修改 init_rbac_data 命令 |
初始化时自动生成档案 |
常见问题与排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 迁移报找不到模型 | SysUserProfile 未写入 rbac/models.py 或写错缩进 |
确认模型类位于 models.py 且继承 models.Model |
| Admin 页面无「用户扩展档案」 | admin.py 未导入或未注册 SysUserProfile |
补充导入并添加 @admin.register(SysUserProfile) |
MySQL 无 sys_user_profile 表 |
只执行了 makemigrations,未执行 migrate |
在后端目录执行 python3 manage.py migrate |
| 外键列名与 SQL 不符 | 未设置 db_column="user_id" |
在 user 字段显式指定 db_column |
| 创建档案报用户不存在 | SysUser.objects.get(username="admin") 未匹配到用户 |
先确认 admin 用户已创建,或改用初始化命令生成 |
| 删除用户被拒绝 | on_delete=PROTECT 阻止删除持有档案的用户 |
先删除或转移该用户的档案记录后再删除用户 |
下一阶段衔接
阶段 11(阶段 11:用户档案查询和修改接口.md)将基于本阶段建成的 sys_user_profile 表,实现档案的查询与修改接口:
text
GET /api/profile/me/ 查看当前用户档案
PUT /api/profile/me/ 修改当前用户档案
本阶段的模型与数据是阶段 11 接口层的直接依赖,需确保建表与测试数据均已就绪。