Skip to main content

14.优化表单验证失败错误提示信息

现在我们已经做到:

  • ModelForm 写了表单
  • clean_xxx 做了字段验证
  • fetch 提交表单
  • 后端能返回 JSON

我可以明确告诉你一句话:

你已经超过 80% 的 Django 初学者了。

现在剩下的,不是“技术难度”,而是体验打磨

1. 问题出现在哪里?

目前的流程是这样的:

  1. 用户点击「保存」
  2. 前端用 fetch 提交
  3. 后端验证失败
  4. 后端返回 JSON(包含错误信息)

但问题在于:

  • 后端返回的是一大坨错误信息
  • 前端只知道“失败了”
  • 用户不知道哪个字段错了、错在哪里

如下图所示。

image-20260106094817726

本节,我们必须把“开发者能看懂的错误”,翻译成“普通用户能看懂的提示”。

2. 把错误“翻译成人话”

前面章节中,我们已经在students/views.py定义了form_invalid()方法, 它会将错误信息以JSON形式返回到前端。所以,接下来,我们只需要处理前端即可。

做法是三步:

1️⃣ 拿到 errors

上一节我们看到,后端返回的错误信息如下:

{
"status": "error",
"messages": "{\"student_name\": [{\"message\": \"\\u8bf7\\u586b\\u5199\\u6b63\\u786e\\u7684\\u5b66\\u751f\\u59d3\\u540d\", \"code\": \"\"}], \"student_number\": [{\"message\": \"\\u5b66\\u53f7\\u957f\\u5ea6\\u5e94\\u4e3a19\\u4f4d\\u3002\", \"code\": \"\"}], \"birthday\": [{\"message\": \"\\u8f93\\u5165\\u4e00\\u4e2a\\u6709\\u6548\\u7684\\u65e5\\u671f\\u3002\", \"code\": \"invalid\"}], \"contact_number\": [{\"message\": \"\\u8054\\u7cfb\\u7535\\u8bdd\\u5e94\\u4e3a11\\u4f4d\\u3002\", \"code\": \"\"}]}"
}

所以我们需要把messages正确解析出来,代码如下:

// 解析嵌套的 JSON 字符串
const errors = JSON.parse(data.messages);

2️⃣ 拼成一个 HTML 列表

解析出来的messages包含了所有错误信息,我们让每个错误显示一行,所以可以使用HTML中的 <li> 标签。代码如下:

// 构造错误信息的文本
let errorMessage = '';

// 遍历 errors 对象的所有属性(字段名)
for (const field in errors) {
// hasOwnProperty 检查:确保这个属性是对象自身的(不是从原型链继承的)
// 这是 for...in 循环的经典防御性写法(现在不常用,但仍然安全)
if (errors.hasOwnProperty(field)) {
// errors[field] 通常是一个数组,包含该字段的所有错误信息
// 例如:errors.student_name = [{message: "姓名不能为空"}, {message: "姓名格式错误"}]
errors[field].forEach(error => {
// 每次循环都往 errorMessage 里追加一条 <li> 错误提示
errorMessage += `<li style="color:red;text-align:left;margin-left: 100px;">
${error.message}
</li>`;
});
}
}

这段代码是一个典型的错误消息收集与 HTML 拼接片段, 主要目的是把后端返回的字段级错误信息,转换成可直接插入页面的 HTML 列表。

3️⃣ 用 SweetAlert2 一次性展示

Swal.fire({
icon: 'error',
title: '提交失败',
html: errorMessage,
confirmButtonText: '关闭'
});

**注意:**前面我们使用的Swal.fire中的text, 现在

效果你已经在视频里看到了:

  • 所有错误一次性列出来
  • 清清楚楚
  • 用户知道下一步该怎么改

image-20260106135456526

 ### 4️⃣ 完整代码

student_form.htlm完整代码如下:

# 代码位置: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 {
// 解析嵌套的 JSON 字符串
const errors = JSON.parse(data.messages);
// 构造错误信息的文本
let errorMessage = '';
for (const field in errors) {
if (errors.hasOwnProperty(field)) {
errors[field].forEach(error => {
errorMessage += `<li style="color:red;text-align:left;margin-left: 100px;">
${error.message}
</li>`;
});
}
};
Swal.fire({
icon: 'error',
title: '提交失败',
html: errorMessage,
confirmButtonText: '关闭'
});
}

console.log('服务器返回:', data);

} catch (error) {
console.error('请求出错:', error);
Swal.fire({
icon: 'error',
title: '网络错误',
text: '请检查网络连接或稍后重试'
});
}
});


});


</script>

3.为什么我强烈推荐这样做?

说句实话,这是我踩过无数坑以后才总结出来的经验。

如果你只做“单字段提示”:

  • 用户要改一次、提交一次
  • 特别容易烦
  • 真实项目中非常容易被投诉

如果你一次性展示全部错误:

  • 用户效率高

  • 页面专业感直接上一个台阶

到这里,我们完整走通了一条“专业级表单路径”

我们回顾一下你现在已经掌握了什么:

  • ✅ ModelForm 表单验证
  • ✅ clean_xxx 精细校验
  • ✅ fetch 异步提交
  • ✅ form_invalid 接住错误
  • ✅ SweetAlert2 友好提示

这一套流程,就是很多真实后台系统的标准做法

下一步

别急着往下看课程,我建议你现在立刻做三件事:

  1. 故意填错 2~3 个字段
  2. 看 SweetAlert 弹窗里的错误是否清晰
  3. 自己改一条错误提示文案,让它更“像人说的话”

你会明显感觉到:

你写的已经不是“教学 Demo”,而是“真实系统”。

下一节,我们会继续做一件非常关键的事情

👉 将验证通过的学生信息保存到数据库。

我们下节见 👋