modelserializer 默认不支持嵌套写入,因其对反向关系字段仅做只读序列化,不参与反序列化验证与保存;需手动重写create()/update()或使用drf-writable-nested实现可写嵌套。

ModelSerializer 默认支持嵌套只读输出,但不处理写入;想让 POST/PUT 带嵌套数据并保存,必须手动接管 create() 和 update(),或引入 drf-writable-nested。
为什么嵌套字段默认不接收 POST 数据?
DRF 的 ModelSerializer 对外键、OneToOneField 或反向关系(如 related_name='contracts')默认只做序列化输出,不参与反序列化验证与保存。常见表现包括:
- 前端传
{"contracts": [{"number": "C-001"}]},后端validated_data里根本没这个 key - 即使去掉
read_only=True,DRF 也不会调用子序列化器的is_valid()和save(),而是试图把字典直接塞进模型字段,报错TypeError: Object of type dict is not JSON serializable - 多级嵌套(如
Project → Contract → Author)漏掉任意一层的显式处理,就会断链或静默丢弃
手动实现可写嵌套:以 Project + Contract 为例
不依赖第三方库时,关键不是“怎么定义字段”,而是“怎么在 create() 里拆解和重建关联”。以下步骤缺一不可:
-
ProjectSerializer的Meta.fields中**不要声明contracts字段**——否则 DRF 会把它当普通字段处理,导致类型错误 - 在
create()方法里先创建主对象:project = Project.objects.create(**validated_data) - 用
validated_data.pop('contracts', [])取出原始嵌套数据,再逐个用ContractSerializer(data=item).is_valid(raise_exception=True)验证并.save(project=project) - 注意外键赋值方式:必须是
Contract.objects.create(..., project=project),而不是project.contracts.set([...])—— 后者绕过ContractSerializer的字段验证和自定义逻辑
用 drf-writable-nested 省掉重复劳动
它本质是把上面的手动流程封装成通用逻辑,但有硬约束,踩坑点很集中:
- 子序列化器必须继承
WritableNestedModelSerializer,不是ModelSerializer - 主序列化器字段名必须严格匹配模型反向关系名,比如
contracts = ContractSerializer(...)要求Project.contracts是有效的related_name - POST 数据中嵌套字段名必须和序列化器字段名一致,且结构扁平(不能套多层
data或payload包裹) - 对
ManyToManyField(非through模型)和OneToOneField正向/反向都支持,但泛型关系仅支持反向
查询性能不优化,嵌套再对也慢
嵌套输出本身不触发 N+1 查询,但默认不会预加载关联数据。不加 prefetch_related('contracts'),每序列化一个 Project 就查一次数据库:
- 视图中必须显式使用:
Project.objects.prefetch_related('contracts') - 如果嵌套里还有更深层关系(如
Contract.author),要写成prefetch_related('contracts__author') - 别用
select_related替代prefetch_related处理many=True关系,前者会生成 LEFT JOIN,数据量大时易爆内存
read_only=False 了就万事大吉,其实 DRF 根本不认这个字段——它只在 create() 里才真正开始干活。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











