如何在Django REST Framework中返回自引用模型的嵌套层级结构?
如何在Django REST Framework中实现未知层级的树形结构序列化(不修改视图代码)
问题背景
我们正在开发一个Django REST Framework项目,连接存储多层级树形结构数据的PostgreSQL数据库。需要提供一个GET请求端点,无参数传入时返回完整的嵌套树形结构(包含父节点、子节点、孙节点等),且不能修改现有视图代码。
示例数据
数据库中地区数据结构如下(层级可扩展,深度未知):
| id | region | parent_region_id |
|---|---|---|
| 1 | world | NULL |
| 2 | europe | 1 |
| 3 | asia | 1 |
| 4 | africa | 1 |
| 5 | belgium | 2 |
| 6 | germany | 2 |
| 7 | spain | 2 |
| 8 | japan | 3 |
| 9 | indonesia | 3 |
| 10 | vietnam | 3 |
| 11 | tanzania | 4 |
| 12 | egypt | 4 |
| 13 | senegal | 4 |
目标输出
GET请求/region时需返回如下嵌套JSON结构:
{ "id":1, "region":"world", "children":[ { "id":2, "region":"europe", "children":[ { "id":5, "region":"belgium" }, { "id":6, "region":"germany" }, { "id":7, "region":"spain" } ] }, { "id":3, "region":"asia", "children":[ { "id":8, "region":"japan" }, { "id":9, "region":"indonesia" }, { "id":10, "region":"vietnam" } ] }, { "id":4, "region":"africa", "children":[ { "id":11, "region":"tanzania" }, { "id":12, "region":"egypt" }, { "id":13, "region":"senegal" } ] } ] }
已尝试代码及问题
现有代码返回的子节点仅为ID列表,而非嵌套结构,且递归方案存在超时或序列化错误:
# Models.py class HierarchyData(models.Model): region = models.CharField(max_length=100, null=False, default=None) parent = models.ForeignKey("self", models.DO_NOTHING, null=True, blank=True, db_column='parent', related_name="children") # Serializers.py class HeirarchyDataSerializer(serializers.ModelSerializer): class Meta: model = HierarchyData fields = ["id","region", "children"] # Views.py class ListHierarchyData(generics.ListAPIView): queryset = HierarchyData.objects.all() serializer_class = HeirarchyDataSerializer permission_classes = [IsAuthenticated]
错误返回结果:
{ "id": 1, "region": "world", "children": [2,3,4] }
解决方案
1. 修正模型并优化查询(关键)
修改模型,添加自定义Manager实现根节点过滤和递归预取所有子节点,避免N+1查询问题,同时匹配数据库实际列名:
# Models.py from django.db import models from django.db.models import Prefetch def recursive_prefetch(): # 递归预取所有层级的子节点,彻底解决N+1查询 return Prefetch( 'children', queryset=HierarchyData.objects.prefetch_related(recursive_prefetch()) ) class HierarchyDataManager(models.Manager): def get_queryset(self): # 仅返回根节点(parent为NULL的节点),并预取所有子节点 return super().get_queryset()\ .filter(parent__isnull=True)\ .prefetch_related(recursive_prefetch()) class HierarchyData(models.Model): region = models.CharField(max_length=100, null=False) parent = models.ForeignKey( "self", on_delete=models.DO_NOTHING, null=True, blank=True, db_column='parent_region_id', # 匹配数据库实际列名 related_name="children" ) objects = HierarchyDataManager() class Meta: db_table = '你的实际表名' # 替换为数据库中地区表的名称
2. 修改序列化器实现递归嵌套
更新序列化器,使用SerializerMethodField递归序列化子节点,并仅在存在子节点时显示children字段:
# Serializers.py from rest_framework import serializers from .models import HierarchyData class HeirarchyDataSerializer(serializers.ModelSerializer): children = serializers.SerializerMethodField(required=False) class Meta: model = HierarchyData fields = ["id", "region"] def get_children(self, obj): # 仅当存在子节点时递归序列化 if obj.children.exists(): return HeirarchyDataSerializer(obj.children.all(), many=True, context=self.context).data return None def to_representation(self, instance): # 移除空的children字段,匹配目标输出格式 data = super().to_representation(instance) children_data = self.get_children(instance) if children_data: data['children'] = children_data return data
3. 保持视图代码不变
无需修改现有ListHierarchyData视图,因为自定义Manager已经让objects.all()返回过滤后的根节点并预取了所有子节点,序列化器会自动处理嵌套结构。
效果验证
调用/region端点后,会返回目标格式的完整嵌套树形结构,且不会出现超时或序列化错误,同时支持任意深度的层级扩展。
内容的提问来源于stack exchange,提问作者Sher Khan Mari
相关产品推荐
相关产品推荐

