is_valid()按顺序执行字段级声明式验证、字段级自定义validate_字段名、对象级validate;字段级失败则中断后续,错误存于serializer.errors,默认不抛异常。

DRF序列化器里 is_valid() 到底验证了什么
is_valid() 不是只跑字段类型检查,它按固定顺序执行三类验证:字段级(required、max_length 等声明式规则)、字段级自定义(validate_字段名)、对象级(validate 方法)。顺序错了就可能漏掉逻辑——比如你在 validate_email 里依赖 self.initial_data,但此时 email 字段还没被转换成 Python 类型(比如还是字符串而非 None 或 str),容易判空出错。
- 字段级验证失败会直接中断后续流程,
validate_字段名和validate都不会执行 -
is_valid()默认不抛异常,只把错误塞进serializer.errors;加raise_exception=True才触发ValidationError - 调用
is_valid()前必须确保传入数据已通过data=...正确赋值,否则errors永远为空
validate_字段名 方法怎么写才不踩坑
这个方法名必须严格匹配字段名,比如字段叫 phone,方法就得叫 validate_phone,少个下划线或大小写不对都无效。它接收的是**已转换类型后的值**(比如 IntegerField 字段传进来是 int,不是字符串),所以别在里头再做 int(value) 这种转换。
- 返回值必须是该字段的最终值(可以修改,比如统一转小写),不能 return None 或丢弃
- 如果校验失败,必须显式 raise
serializers.ValidationError('xxx'),return 错误字符串会被当成合法值 - 不要在
validate_phone里查数据库——它可能被反复调用(比如批量创建时),应移到validate或视图层 - 注意时区:
DatetimeField传入的值默认带settings.TIME_ZONE信息,别用datetime.now()直接比
validate 方法和 validate_字段名 的分工边界
validate 是对象级入口,适合做跨字段逻辑(比如“结束时间不能早于开始时间”)或需要访问多个字段值的判断。它接收整个 attrs 字典,里面是所有已通过字段级验证的值。
-
validate在所有validate_字段名之后执行,所以能拿到清洗后的字段值,但拿不到原始输入(self.initial_data是原始的,但字段可能已被转换) - 如果某个字段没通过字段级验证,它根本不会出现在
attrs里,所以别假设所有字段都存在——先用attrs.get('xxx') - 想复用校验逻辑?别把
validate当工具函数调,它只在is_valid()流程中被自动调用 - 性能敏感场景(如导入万条数据),避免在
validate里做 IO,比如每次调用都查一次用户权限
调试时 is_valid() 返回 False 却看不到错误?
最常见原因是没打印 serializer.errors,或者只看了 print(serializer.errors) 但没展开嵌套结构。DRF 的错误是嵌套字典,比如字段错误是 {'email': ['Enter a valid email address.'] },非字段错误(validate 抛的)在 non_field_errors 键下。
- 用
print(dict(serializer.errors))强制转成普通 dict,避免 Django 的ErrorDict魔术方法干扰调试 - 如果
errors是空字典但is_valid()仍返回False,大概率是序列化器初始化时 data 没传对,比如写了data=request.POST却忘了request.FILES - 自定义
to_internal_value出错时,错误不会进errors,而是直接 raise,得用 try/except 包住is_valid()










