
本文介绍如何通过 Django REST Framework 的嵌套序列化器,将一对多关系的数据(如项目与合同)结构化为分组嵌套的 JSON 格式,并配合 prefetch_related 优化查询性能。
本文介绍如何通过 django rest framework 的嵌套序列化器,将一对多关系的数据(如项目与合同)结构化为分组嵌套的 json 格式,并配合 `prefetch_related` 优化查询性能。
要实现项目(Project)与其关联合同(Contract)的嵌套输出(即每个项目对象下包含一个 contracts 数组),关键在于利用 DRF 的嵌套序列化器 + 正向外键关系反查 + 查询优化,而非手动拼装数据或修改原始 queryset。
✅ 正确做法:使用 ModelSerializer + 关系字段
首先,确保模型间关系定义清晰(已满足):
# models.py
class Project(models.Model):
guid = models.UUIDField(default=uuid4, editable=False)
name = models.CharField(max_length=200)
class Contract(models.Model):
guid = models.UUIDField(default=uuid4, editable=False)
number = models.CharField(max_length=100)
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='contracts')
注意:related_name='contracts' 是关键——它为 Project 模型动态添加了反向关系属性 project.contracts.all(),使嵌套序列化成为可能。
接着,定义两个层级的序列化器:
# serializers.py
from rest_framework import serializers
class ContractSerializer(serializers.ModelSerializer):
contract_guid = serializers.UUIDField(source='guid', read_only=True)
contract_number = serializers.CharField(source='number', read_only=True)
class Meta:
model = Contract
fields = ['contract_guid', 'contract_number']
class ProjectSerializer(serializers.ModelSerializer):
project_guid = serializers.UUIDField(source='guid', read_only=True)
project_name = serializers.CharField(source='name', read_only=True)
contracts = ContractSerializer(many=True, read_only=True) # ← 嵌套序列化,依赖 related_name
class Meta:
model = Project
fields = ['project_guid', 'project_name', 'contracts']
? 视图中正确使用并优化查询
在视图中,必须使用 prefetch_related('contracts'),否则每序列化一个 Project 就会触发一次额外的 SQL 查询(N+1 问题):
提供使用 django-unfold (v0.56+) 构建高级 Django 管理界面的专业指导。当用户提及 django-unfold、unfold admin 等内容时使用此技能。
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
class ProjectContractListView(APIView):
def get(self, request):
# ✅ 预先获取所有项目及其关联合同,仅 2 次查询
projects = Project.objects.prefetch_related('contracts').all()
serializer = ProjectSerializer(projects, many=True)
return Response(serializer.data)
? 提示:若需按条件过滤(如仅返回有合同的项目),可进一步链式调用 .filter(contracts__isnull=False).distinct()。
⚠️ 注意事项
- 不要尝试“修改 queryset”本身来模拟嵌套结构(如 annotate() 或 values() 拼接),这违背 ORM 设计初衷,且难以维护;
- source='guid' 和 source='number' 等字段映射确保序列化字段名与模型属性解耦,提升接口可读性;
- 若 Contract 模型中 guid/number 字段非必填,请添加 allow_null=True 或设置默认值;
- 如需支持写入(POST/PUT),需在 ContractSerializer 中显式声明 read_only=False 并重写 create()/update() 方法。
✅ 最终效果
响应将严格符合目标格式:
[
{
"project_guid": "a1b2c3d4-...",
"project_name": "项目A",
"contracts": [
{ "contract_guid": "x1y2z3-...", "contract_number": "CT-001" },
{ "contract_guid": "x4y5z6-...", "contract_number": "CT-002" }
]
},
{
"project_guid": "e5f6g7h8-...",
"project_name": "项目B",
"contracts": [
{ "contract_guid": "m9n0o1-...", "contract_number": "CT-003" }
]
}
]
这种方案结构清晰、性能可控、符合 DRF 最佳实践,是处理一对多嵌套序列化的标准解法。










