bigywater.tech
Django · 62

Django Form 完整校验流程

form.is_valid() 到底做了什么、按什么顺序?一个字段失败了,后面的钩子还执行吗?这篇给每个环节打点,把完整的校验流程按调用顺序排出来。

惰性
构造 Form 不校验;is_valid() / errors 才开始
顺序
每个字段:to_python → validate → run_validators → clean_<字段>;然后 clean() → _post_clean()
缺键
字段失败后 cleaned_data 里没有它,表单级 clean() 要用 .get()

校验的触发

实测 · 本机执行输出
构造之后 LOG: []
f.is_bound = True  hasattr(f, "cleaned_data") = False
调用 f.errors 之前: errors 是否已计算 = False
is_valid() = True  之后 LOG:
  a.to_python('3')
  a.validate(3)
  a.run_validators(3)
  clean_a()
  b.to_python('5')
  b.validate(5)
  b.run_validators(5)
  clean_b()
  clean()(表单级,cleaned_data 里现有的键:['a', 'b'])
  _post_clean()
cleaned_data = {'a': 3, 'b': 5}

is_valid() 就是「is_bound 且 errors 为空」;errors 第一次访问时调用 full_clean()。

字段失败之后

实测 · 本机执行输出
is_valid() = False
  a.to_python('0')
  a.validate(0)
  a.run_validators(0)
  b.to_python('5')
  b.validate(5)
  b.run_validators(5)
  clean_b()
  clean()(表单级,cleaned_data 里现有的键:['b'])
  _post_clean()
errors: min_value / {'a': ['Ensure this value is greater than or equal to 1.']}
cleaned_data = {'b': 5}

字段自己的三个阶段任一失败就不再执行它的 clean_<field>。注意 Django 仍会继续处理其他字段,并且仍会调用表单级 clean()——所以 clean() 必须容忍字段缺失(本站另一篇《Forms 校验流水线》用真实的 KeyError 演示过这一点)。

表单级错误与顺序

实测 · 本机执行输出
a > b 时 non_field_errors: ['a 不能大于 b']  code = order
a="x", b="" : {'a': ['invalid'], 'b': ['required']}  顺序 = ['a', 'b']
错误对象: {"a": [{"message": "Enter a whole number.", "code": "invalid"}], "b": [{"message": "This field is required.", "code": "r

ValidationError 的 code 是给程序判断用的,message 是给人看的;需要给具体字段加错误用 self.add_error('a', ...)(本文没有测)。

几个细节

实测 · 本机执行输出
未绑定表单 is_valid() = False  errors = {}
多余字段 zzz 进入 cleaned_data 吗: False
disabled 字段:提交 99,cleaned_data = {'x': 1} (用 initial,忽略提交的值)
has_changed() = False  changed_data = []

未绑定表单(没有数据)is_valid() 永远是 False,errors 为空。请求里多余的字段被忽略。disabled=True 的字段用 initial,忽略用户提交的值,是防止篡改只读字段的正确做法(比只在前端 readonly 可靠)。

分步演示:校验流程

Form 校验流程
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。字段与表单的方法里加了日志打点
    • 没有验证:add_error 与 clean() 的组合、field 的 error_messages 覆盖、FileField / ChoiceField 的特殊路径、Form 的 field_order 对校验顺序的影响、异步视图里的表单使用
    京ICP备2026067502号-1京公网安备11010502063487号