12.点了「保存」,到底发生了什么?
我第一次做到「点击保存 → 数据进数据库」的时候,并没有想象中那么顺。
因为我心里一直有一个挥之不去的问题:
“这个保存,到底是表单在提交,还是 JS 在提交?”
如果你现在也有点迷糊,别慌,这太正常了。 这一节,我们就把**“保存这一步”彻底走一遍路径**。
1.为什么不再用默认的表单提交?
先回到一个最基础的事实:
<form>默认就是 POST 提交- 提交后,页面会 整体刷新
- 后端返回的是 HTML 页面
这套机制 没有错,但问题是—— 它不适合弹窗场景。
想象一下:
- 你在 SweetAlert 弹窗里填学生信息
- 一点保存
- 页面整个刷新
- 弹窗消失
体验直接崩掉。
所以我在这里做了一个选择:
用 Fetch 接管提交过程,让页面“看起来没动”,但数据已经保存。
2.怎么“拦住”表单?
很多人第一反应是:
“那我直接写 fetch 不就行了吗?”
但你会发现——表单还是会自己提交。
所以第一步不是提交,而是:
阻止它默认提交。
我们可以这样做:
1️⃣ 等页面加载完成
2️⃣ 找到 form
3️⃣ 监听 submit
4️⃣ preventDefault()
逻辑非常清楚:
你别自己跑,我让你跑,你再跑。
3.fetch 提交表单
用户填写完学生信息,点击“保存”按钮,需要提交到后端。
在 JavaScript 中使用 fetch 提交表单是最常见的现代前端发送数据的做法之一。代码如下:
# 代码位置:templates/students/student_form.html
<script src="{% static 'js/sweetalert2.js' %}" ></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
const url = actionUrl;
form.addEventListener('submit', async function (e) {
e.preventDefault(); // 阻止默认提交
const formData = new FormData(form);
const response = await fetch(url, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': formData.get('csrfmiddlewaretoken'),
},
credentials: 'same-origin' // 重要:携带 cookie
});
</script>
fetch是JavaScript的语法,很多同学一看到它就慌,其实它就做了三件事:
✅ 第一件:告诉它往哪提交
# 代码位置:templates/students/student_form.html
{% if student.pk %}
<script>
// 更新学生信息时,提交的URL
var actionUrl = "{% url 'student_update' student.pk %}";
</script>
{% else %}
<script>
// 创建学生信息时,提交的URL
var actionUrl = "{% url 'student_create' %}";
</script>
{% endif %}
actionUrl就是fetch提交的URL,就是你后端那个 CreateView。
✅ 第二件:把表单数据带过去
这里我用的是 FormData,原因只有一个:
省事,而且不容易错。
const formData = new FormData(form)
它会自动帮你拿到:
- 学生姓名
- 学号
- 班级
- CSRF Token
✅ 第三件:把 CSRF 带上
这是 Django 新手必踩的坑。
我当时也是一脸问号,后来才意识到:
CSRF 就藏在表单里,你只要把它带过去就行。
headers: {
'X-CSRFToken': formData.get('csrfmiddlewaretoken'),
'X-Requested-With': 'XMLHttpRequest'
}
到这里,请求就能顺利到后台了。
4.后台返回JSON
这是第二个新手必卡点。
你会发现:
- fetch 明明请求成功了
- 但拿到的却是一整个 HTML 页面
原因只有一个:
CreateView 默认返回的是 render,不是 Json。
所以这一步,我们必须动后端。
在前端页面,点击“保存”按钮时,form表单数据提交到后端。
Django 就会按照正常的流程执行:
flowchart TB
A[POST 请求到达后端]
A --> B[实例化 Form<br/>get_form]
B --> C{表单是否通过校验}
C -->|通过| D[form_valid]
C -->|未通过| E[form_invalid]
那么我们可以重写form_valid()和form_invalid()方法。这时不再返回HTML页面,而是返回JSON格式数据给前端。
在StudentCreateView类中重新这个2个方法。代码如下:
# 代码位置:student/views.py
class StudentCreateView(CreateView):
model = Student
form_class = StudentForm
template_name = 'students/student_form.html'
def form_valid(self, form):
# 验证成后,返回json数据
return JsonResponse({
'status': 'success',
'messages': '操作成功'
}, status=200)
def form_invalid(self, form):
# 验证失败,返回json数据
errors = form.errors.as_json()
return JsonResponse({
'status': 'error',
'messages': errors
}, status=400)
此时,从前端-->后端-->前端的完整流程如下:
flowchart TD
A[前端 fetch 提交] --> B[后端收到 POST 请求]
B --> C[StudentCreateView.dispatch]
C --> D[StudentCreateView.post]
D --> E[StudentCreateView.get_form]
E --> F[form = StudentForm<br/>data=request.POST,<br/>files=request.FILES]
F --> G{form.is_valid ?}
G -->|Yes| H[form_valid]
G -->|No| I[form_invalid]
H --> J[返回 JsonResponse]
I --> J
5.前端接住结果,只做一件事
现在 Fetch 拿到的是JSON 了。
那前端就非常简单了:
- 如果
status === success - 用 SweetAlert 弹个提示
Swal.fire({
icon: 'success',
title: data.messages
})
student_form.html完整代码如下:
# 代码位置:templates/students/student_form.html
{% load static %}
<link rel="stylesheet" href="{% static 'css/form.css' %}">
<link rel="stylesheet" href="{% static 'css/sweetalert2.css' %}" >
<div class="container">
{% if student.pk %}
<h2>编辑学生信息</h2>
{% else %}
<h2>添加学生信息</h2>
{% endif %}
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}:</label>
{{ field }}
{% if field.help_text %}
<small class="form-text text-muted"> {{ field.help_text}} </small>
{% endif %}
</div>
{% endfor %}
<div class="handleButton">
<button type="submit" id="saveButton">保存</button>
<button type="button" id="cancelButton" onclick="window.parent.Swal.close();">取消</button>
</div>
</form>
</div>
{% if student.pk %}
<script>
var actionUrl = "{% url 'student_update' student.pk %}";
</script>
{% else %}
<script>
var actionUrl = "{% url 'student_create' %}";
</script>
{% endif %}
<script src="{% static 'js/sweetalert2.js' %}" ></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
const url = actionUrl;
form.addEventListener('submit', async function (e) {
e.preventDefault(); // 阻止默认提交
const formData = new FormData(form);
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': formData.get('csrfmiddlewaretoken'),
},
credentials: 'same-origin' // 重要:携带 cookie
});
const data = await response.json();
if (data.status === 'success') {
Swal.fire({
icon: 'success',
title: data.message || '提交成功',
text: '学生信息已成功保存',
timer: 2000,
showConfirmButton: false
});
} else {
Swal.fire({
icon: 'error',
title: '提交失败',
text: data.message || '请检查输入内容'
});
}
console.log('服务器返回:', data);
} catch (error) {
console.error('请求出错:', error);
Swal.fire({
icon: 'error',
title: '网络错误',
text: '请检查网络连接或稍后重试'
});
}
});
});
</script>
6.验证结果
访问学生列表页面,打开浏览器的调试模式,选择console, 填写一份正确字段格式的学生信息,点击“保存”,运行效果如下:

填写一份错误字段类型格式的学生信息,点击“保存”,运行效果如下:

走到这里,你已经完成了很多人卡很久的一件事:
- ✅ 用 Fetch 接管表单提交
- ✅ Django 后端返回 Json
- ✅ SweetAlert 做用户反馈
这已经不是“写个教学 Demo”的水平了,而是真实项目在用的交互方式。
下一步你可以立刻做的事
别停,马上选一个做:
1️⃣ 故意不填字段,看看后台会发生什么
2️⃣ 打印 data,观察 Django 返回了什么
3️⃣ 想一个问题:
👉 如果保存失败,我怎么把错误也弹出来?
下一节,我们就专门解决这个问题: 字段校验失败,怎么优雅地告诉用户。
我们继续。
【大熊课堂精品课程】
Python零基础入门动画课: https://www.bilibili.com/cheese/play/ss7988
Django+Vue:全栈开发: https://www.bilibili.com/cheese/play/ss8134
PyQT6开发桌面软件: https://www.bilibili.com/cheese/play/ss12314
Python办公自动化: https://www.bilibili.com/cheese/play/ss14990
Cursor AI编程+MCP:零基础实战项目课: https://www.bilibili.com/cheese/play/ss105194189
Pandas数据分析实战: https://www.bilibili.com/cheese/play/ss734522035
AI大模型+Python小白应用实战: https://www.bilibili.com/cheese/play/ss3844