django-mptt 获取节点列表的后代

15 投票
3 回答
11688 浏览
提问于 2025-04-16 16:04

我想要获取所有的 descendants(include_self=True),不是针对一个节点,而是针对一组节点(一个查询集)。我希望这能通过一条 SQL 查询来实现。

举个例子(其实这个例子现在是不能用的):

some_nodes = Node.objects.filter( ...some_condition... ) 
some_nodes.get_descendants(include_self=True) #hopefully I would like 
to have all possible Nodes starting from every node of "some_nodes" 

我现在唯一想到的办法是遍历这些节点,然后对每个节点运行 get_descendants(),但这真是个糟糕的解决方案(会产生很多 SQL 查询)。

如果通过 Django ORM 没有简单的方法来做到这一点,你能给我提供一个自定义的 SQL 查询吗?你可以假设我有一个节点的主键列表。

补充说明:如果这有帮助的话,我所有的“某些节点”都在同一个父目录下,并且在树中处于同一级别。

3 个回答

1

Django mptt使用了一种叫做“修改过的先序遍历”的方法,这种方法的详细介绍可以在MySQL管理层次数据的文档中找到。

它有一个查询,用来返回某个节点下面的所有节点:

SELECT node.name
FROM nested_category AS node, nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
    AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;

这里的关键在于parent.lft和parent.rgt这两个数字,所有的子节点的node.lft值都会在这两个值之间。

显然,这个例子假设每个节点只有一个父节点,并且你需要用父节点的名字来找到它。因为你已经有了父节点的数据,所以你可以做类似下面的事情:

SELECT node.id
FROM node_table
WHERE node.lft BETWEEN parent[0].lft AND parent[0].rgt
    OR node.lft BETWEEN parent[1].lft AND parent[1].rgt

我就不告诉你怎么为每个父节点生成一个单独的BETWEEN条件了(提示一下,可以用“ AND ".join)。

另外,你也可以对每个父节点使用范围生成器,这样可以获取每个父节点的lft和rgt值之间的所有值,包括这两个值。这样你就可以用一个大的IN语句,而不是很多个BETWEEN条件。

将以上任意一种方法与RawQueryset结合使用,你就能拿到模型数据。

11

后来的 mptt 版本已经在对象管理器中内置了这个功能。也就是说,解决这个问题的方法很简单,直接使用下面的代码就可以了:

Node.objects.get_queryset_descendants(my_queryset, include_self=False)
10

非常感谢Craig de Stigter在django-mptt-dev小组中回答了我的问题,如果有人需要的话,我在这里友好地重新分享他的解决方案,链接在这里:http://groups.google.com/group/django-mptt-dev/browse_thread/thread/637c8b2fe816304d

   from django.db.models import Q 
   import operator 
   def get_queryset_descendants(nodes, include_self=False): 
       if not nodes: 
           return Node.tree.none() 
       filters = [] 
       for n in nodes: 
           lft, rght = n.lft, n.rght 
           if include_self: 
               lft -=1 
               rght += 1 
           filters.append(Q(tree_id=n.tree_id, lft__gt=lft, rght__lt=rght)) 
       q = reduce(operator.or_, filters) 
       return Node.tree.filter(q) 

示例节点树:

T1 
---T1.1 
---T1.2 
T2 
T3 
---T3.3 
------T3.3.3 

示例用法:

   >> some_nodes = [<Node: T1>, <Node: T2>, <Node: T3>]  # QureySet
   >> print get_queryset_descendants(some_nodes)
   [<Node: T1.1>, <Node: T1.2>, <Node: T3.3>, <Node: T3.3.3>] 
   >> print get_queryset_descendants(some_nodes, include_self=True)
   [<Node: T1>, <Node: T1.1>, <Node: T1.2>, <Node: T2>, <Node: T3>, <Node: T3.3>, <Node: T3.3.3>] 

撰写回答