Django自我递归外键过滤查询所有孩子

时间:2011-01-18 14:41:08

标签: django model recursive-query

我的这个模型有一个自引用外键关系:

class Person(TimeStampedModel):
    name = models.CharField(max_length=32)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

现在我想为一个人获得所有多层次的孩子。我该如何为它编写Django查询?它需要表现得像递归函数。

8 个答案:

答案 0 :(得分:27)

您始终可以向模型添加递归函数:

编辑:根据SeomGi Han修正

def get_all_children(self, include_self=True):
    r = []
    if include_self:
        r.append(self)
    for c in Person.objects.filter(parent=self):
        _r = c.get_all_children(include_self=True)
        if 0 < len(_r):
            r.extend(_r)
    return r

(如果你有很多递归或数据,请不要使用它。)

仍然按照errx的建议推荐mptt。

答案 1 :(得分:14)

您应该阅读有关修改的预订树遍历的信息。 这是django的实现。 https://github.com/django-mptt/django-mptt/

答案 2 :(得分:8)

sunn0的建议很棒,但是get_all_children()会返回奇怪的结果。它返回类似[Person1,[Person3,Person4],[]]的内容。它应该改为如下所示。

def get_all_children(self, include_self=True):
    r = []
    if include_self:
        r.append(self)
    for c in Person.objects.filter(parent=self):
        _r = c.get_all_children(include_self=True)
        if 0 < len(_r):
            r.extend(_r)
    return r

答案 3 :(得分:5)

如果您知道树的最大深度,可以尝试这样的(未经测试):

Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))

答案 4 :(得分:2)

Person类(TimeStampedModel): 名称= models.CharField(max_length = 32) parent = models.ForeignKey('self',null = True,blank = True,related_name ='children')

def get_children(self):
        children = list()
        children.append(self)
        for child in self.children.all():
            children.extend(children.get_children())
        return children

get_children()将使用相关名称获取实例的所有子项,然后如果递归地找到该子项,则将对该子项调用get_children(),直到找不到更多数据/子项为止。

答案 5 :(得分:1)

我知道这已经过时了,但有人可能会得到帮助。

     def get_all_children(self, container=None):
         if container is None:
             container = []
         result = container
         for child in self.children.all():
             result.append(child)
             if child.children.count() > 0:
                 child.get_all_children(result)
         return result

然后在模型上简单地将它设为property(或cached_property,如果它适用于你),以便可以在任何实例上调用它。

答案 6 :(得分:0)

我有一个非常相似的business problem,其中给了一个团队成员,我应该找到他下面的完整团队。但是,拥有大量员工使得递归解决方案效率非常低,而且我的API也会从服务器中获得超时错误。

接受的解决方案接受节点,转到它的第一个孩子并深入到层次结构的底部。然后再次回到第二个孩子(如果存在),然后再次下降到底部。简而言之,它逐个探索所有节点并将所有成员附加到数组中。这导致了大量的db调用,如果要探索大量节点,应该避免这种情况。我提出的解决方案是逐层获取节点。 db调用的数量等于层数。请查看此SO link解决方案。

答案 7 :(得分:0)

我还要编写QuerySet,因为这将允许您链接它们。 我将为所有孩子和所有父母的检索提供答案。

class PersonQuerySet(QuerySet):
    def descendants(self, person):
        q = Q(pk=person.pk)
        for child in person.children.all():
            q |= Q(pk__in=self.descendants(child))
        return self.filter(q)

    def ancestors(self, person):
        q = Q(pk=person.pk)
        if person.parent:
            q |= Q(pk__in=self.ancestors(person.parent))
        return self.filter(q)

现在,我们需要将PersonQuerySet设置为管理员。

class Person(TimeStampedModel):
    name = models.CharField(max_length=32)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

    people = PersonQuerySet.as_manager()

这是最后的查询。

albert_einstein = Person.people.get(name='Albert Einstein')
bernhard_einstein = Person.peole.get(name='Bernhard Caesar Einstein')
einstein_folks = Person.people.descendants(albert_einstein).ancestors(bernhard_einstein)

注意: 以下解决方案与前面其他答案一样缓慢。每当数据库命中子级/父级时,我都会检查它。 (如果任何人都可以通过一些优化和缓存来进一步改善,那么最好在查询之前 prefetch 相关数据)。同时,mptt更实用。