Skip to main content

15.将学生信息写入到数据库

表单验证都过了,数据到底是怎么真正落库的?

数据提交成功 ≠ 业务逻辑完成

这一节,才是真正的分水岭。

这一节开始之前,我们必须先把逻辑想清楚,否则代码一定会写乱。

我们现在是在做什么?

👉 新增学生

那学生的数据要存在哪?

  • 显然:student

但事情并没有这么简单。

1.为什么一定要操作 auth_user 表?

如果你打开 Student 模型,会发现一个非常关键的字段:

Student 是关联 User 的

也就是说:

  • 学生 ≠ 只能存在于 student 表
  • 学生 本质上也是一个“系统用户”

为什么要这么设计呢?

因为在系统中,一共有3个用户角色:管理员,老师,学生。如果分别设计用户管理,会非常繁琐且冗余。所以我们可以借助Django自身的auth_user表,此外还可以利用Django登录、授权等方法,而不再需要自己去重复造轮子,将更多的时间和精力聚焦于业务逻辑上。

2.新手最容易犯的一个错误

很多人第一反应是:

“那我直接用学生姓名当用户名不就行了?”

听起来很合理,但这是auth_user.username 必须唯一

现实世界里:

梓轩、梓涵、梓晴不止一个。

所以我们不能使用username, 但是我们可以student_number,因为一个班级里,甚至是学校里,学籍号是唯一的。

3.一个简单又稳妥的设计方案

我当时的设计是这样的(非常推荐你用):

  • username = 学生姓名 + "_" + 学籍号
  • password = 学籍号后 6 位

这样做有三个好处:

  1. username 永远唯一
  2. 管理员好记
  3. 学生第一次登录成本极低

而且你完全不用担心密码加密问题:

Django 的 create_user 会自动帮你加密

你只管传明文。

4.真正开始写代码之前,先理一遍执行顺序

当表单提交后,流程是这样的:

flowchart TB
Start[Form 提交流程]

Start --> A[Form 表单校验]

A -->|失败| B[返回校验错误<br/>JSON]
A -->|通过| C[form_valid]

C --> D[处理 user 表]
D --> E[处理 student 表]

E --> F[返回成功 JSON]

注意一句非常关键的话:

能走到 form_valid,说明数据已经是“干净的”。

5.在 form_valid 中做三件事

第一步:拿到已经验证过的数据

这里必须用:

form.cleaned_data.get(...)

因为:

  • 它只包含校验通过的数据
  • 绝不会是脏数据

我们需要的只有两个字段:

# 代码位置:students/views.py
def form_valid(self, form):
# 接收字段
student_name = form.cleaned_data.get('student_name')
student_number = form.cleaned_data.get('student_number')

2️⃣处理 auth_user 表(重点)

逻辑非常清晰:

  1. 拼接 username
  2. 生成 password(学号后六位)
  3. 查询 user 是否已存在
  4. 存在 → 直接用
  5. 不存在 → 创建

这里一定要用:

# 代码位置:students/views.py
from django.contrib.auth.models import User

def form_valid(self, form):
# 接收字段
student_name = form.cleaned_data.get('student_name')
student_number = form.cleaned_data.get('student_number')
# 拼接用户名和密码
username = student_name + '_' + student_number
password = student_number[-6:]
# 为auth_user表创建一个新记录
user = User.objects.create_user(username=username, password=password)

【代码解析】

  • User 是 Django 内置的认证模型(django.contrib.auth.models.User)。
  • create_user() 是 Django 专门为创建用户提供的便捷方法,它会:
    • 自动对密码进行哈希加密(不能直接用 User.objects.create(),因为密码不会加密)。
    • 设置 is_active=True(默认激活)。
    • 其他字段(如 email、first_name 等)如果没传则为空。
  • 结果:数据库的 auth_user 表里多了一条记录,这个 user 对象就是新创建的用户实例。

3️⃣把 user 绑定到 student 上

这一步非常优雅:

# 代码位置:students/views.py
from django.contrib.auth.models import User

def form_valid(self, form):
# 接收字段
student_name = form.cleaned_data.get('student_name')
student_number = form.cleaned_data.get('student_number')
# 拼接用户名和密码
username = student_name + '_' + student_number
password = student_number[-6:]
# 为auth_user表创建一个新记录
user = User.objects.create_user(username=username, password=password)
# user对象赋值给form
form.instance.user = user
form.save()

【代码解析】

  1. form 是 StudentForm(继承自 ModelForm),它的 instance 属性就是即将要保存的 Student 模型实例。
  2. 这是一个一对一外键,表示每个学生对应一个唯一的 Django 用户账号。这里把刚刚创建的 user 赋值给 Student 的 user 字段,相当于告诉 Django:“这个学生要关联这个新用户”。
  3. 把表单中填写的所有字段(student_name、student_number、grade 等)保存到数据库的 student 表。因为 form.instance.user 已经被赋值,所以 user_id 字段会自动保存刚刚创建的 user 的 ID。

一句话总结:

能让 Django 帮你干的事,别自己写。

完整代码如下:

# 代码位置:students/views.py
class StudentCreateView(BasestudentView, CreateView):

def form_valid(self, form):
# 接收字段
student_name = form.cleaned_data.get('student_name')
student_number = form.cleaned_data.get('student_number')
# 写入到auth_user表
username = student_name + '_' + student_number
password = student_number[-6:]
# 为auth_user表创建一个新记录
user, created = User.objects.get_or_create(
username=username
)
//
if created:
user.set_password(password)
user.save()
form.instance.user = user
form.save()

# 返回json响应
return JsonResponse({
'status': 'success',
'messages': '操作成功'
}, status=200)

【代码解析】

  1. 只有当 created == True(也就是这次真的新建了用户)时,才会去设置密码

  2. set_password():这是 Django 官方推荐的设置密码方法,它会自动对明文密码进行哈希加密(使用 PBKDF2 + salt 等)

  3. 最后 save() 把修改后的用户保存回数据库。

上面的代码进行了优化,替换了原来的create_user。为什么不直接用 create_user()?

因为 get_or_create() 本身是查找或创建,而 create_user() 是强制创建(如果用户名已存在会抛 IntegrityError)。

这段代码想要达到的效果是:

“如果用户已存在,就什么都不改(尤其是不改密码);只有新建时才设置密码”

6.查看提交结果

点击添加按钮,填写正常的学生信息,点击保存按钮,效果如下图所示。

image-20260106152730092

使用navicat查看数据库中的student表,发现新增了一条记录,如下图:

image-20260106153209192

student表中新增记录的字段的值就是我们表单中填写的学生信息。特别注意user_id字段,这是一个外键,它的2。

继续查看auth_user表,发现新增了一条记录,它的id就是2,如下图:

image-20260106153006602

恭喜你,你已经成功地创建了一条学生记录,并且在auth_user表中,同步创建了一条用户 记录。它们是一对一关系:

student.user_id  <---> auth_user.id

7.刷新父页面

虽然我们已经成功地将数据存入到了数据库,但是你也会发现一个问题:弹唱一直在显示,而没有关闭。

这是因为:

  • 当前页面是在 iframe 里
  • 它是学生列表页的“子页面”

所以:

你刷新的是 iframe,不是主页面。

这一步很多人第一次看到都会“恍然大悟”。

解决方案只有一句 JS:

window.parent.location.reload()

意思是:

  • 回到父窗口
  • 重新加载列表页
  • 新数据自然就出来了

在student_form.html代码中,找到提交成功的代码进行修改,代码如下:

if (data.status === 'success') {
Swal.fire({
icon: 'success',
title: data.message || '提交成功',
text: '学生信息已成功保存',
timer: 2000,
showConfirmButton: false
});
// 新增代码:加载父页面
window.parent.location.reload()
}

但是上面代码有一个问题,添加成功后,没有等待timer设置的2000毫秒,而是直接执行了加载父页面,可以这样修改:

if (data.status === 'success') {
Swal.fire({
icon: 'success',
title: data.message || '提交成功',
text: '学生信息已成功保存',
timer: 2000,
showConfirmButton: false
}).then((result) => {
// timer结束后,再执行刷新父页面
window.parent.location.reload();
});

再添加一个学生信息,查看添加成功后,是否会自动刷新父页面,显示加载后的列表页信息。

image-20260106154542706

image-20260106155056812

本章小结

到这里为止,你已经真正做完了:

  • ✔ 表单校验
  • ✔ 业务处理
  • ✔ user + student 双表写入
  • ✔ 前端友好提示
  • ✔ 页面自动刷新

这已经是一个完整的“真实系统新增流程”了。

下一步你现在就可以做的事情(强烈建议)

不要急着往下看课程,我建议你现在立刻:

  1. 再新增 2 个学生
  2. 故意制造一次错误
  3. 看 user 表和 student 表是否一一对应

下一节,我们会顺势做一件非常重要的事情:

把“新增”这套逻辑,原封不动地复用到“编辑学生”中

到那一步,你会真正理解:

Django 为什么这么适合做管理系统。

我们下节见 👋