如何在Django视图中用django-mptt单查询获取带children属性的完整树
解决Django-MPTT单次查询构建带children属性的树形结构问题
你说得对,get_children()确实会触发额外的数据库查询,哪怕你已经用all()拉取了整张表的数据。要实现单次查询就构建出每个节点带children属性的结构,我们可以在内存中手动构建父子关系——既然已经把所有数据拿到了,就没必要再跑数据库了。
下面分两种场景给你代码示例:
场景1:使用模型实例(推荐,保留模型方法和属性)
假设你的SongGroup模型继承了MPTTModel,结构大概是这样:
from mptt.models import MPTTModel, TreeForeignKey class SongGroup(MPTTModel): name = models.CharField(max_length=100) parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') class MPTTMeta: order_insertion_by = ['name']
我们可以这样构建树形结构:
# 单次查询获取所有分组 all_groups = SongGroup.objects.all() # 用字典存所有分组,方便快速查找父节点 group_map = {group.id: group for group in all_groups} # 给每个节点初始化一个内存中的children列表(避免用默认的related_name触发查询) for group in all_groups: group.children_list = [] # 遍历所有节点,把它们添加到父节点的children列表中 for group in all_groups: if group.parent_id: parent_group = group_map[group.parent_id] parent_group.children_list.append(group) # 最后收集所有根节点(没有父节点的节点) root_groups = [group for group in all_groups if group.parent_id is None]
现在root_groups里的每个节点都有children_list属性,包含它的所有子节点,子节点也会递归带有自己的children_list,全程只触发了一次数据库查询。
场景2:使用values()返回字典(符合你给出的示例需求)
如果必须用字典格式,我们可以对values()返回的结果做同样的内存处理:
# 单次查询获取所有分组的字典数据,记得包含parent_id(用来关联父子) groups = list(SongGroup.objects.all().values('id', 'name', 'parent_id')) # 按需添加其他字段 # 构建id到字典的映射 group_map = {group['id']: group for group in groups} # 给每个字典初始化children空列表 for group in groups: group['children'] = [] # 关联父子节点 for group in groups: parent_id = group['parent_id'] if parent_id: parent_group = group_map[parent_id] parent_group['children'].append(group) # 收集根节点 root_groups = [group for group in groups if group['parent_id'] is None]
现在你可以像示例里那样遍历root_groups(或所有groups,根节点是更合理的入口),每个字典都有children字段,包含它的子节点列表,完全不需要额外查询。
为什么不用get_children()?
get_children()是django-mptt提供的查询方法,它本质上会生成一个新的QuerySet去数据库筛选当前节点的子节点——哪怕你已经把所有数据拿到了内存里,它还是会发起新的数据库请求,所以这种场景下我们手动在内存中构建关系是更高效的选择。
内容的提问来源于stack exchange,提问作者user3599803
相关产品推荐
相关产品推荐

