django-mptt 获取节点列表的后代
我想要获取所有的 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 个回答
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结合使用,你就能拿到模型数据。
后来的 mptt 版本已经在对象管理器中内置了这个功能。也就是说,解决这个问题的方法很简单,直接使用下面的代码就可以了:
Node.objects.get_queryset_descendants(my_queryset, include_self=False)
非常感谢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>]