我的这个模型有一个自引用外键关系:
class Person(TimeStampedModel):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
现在我想为一个人获得所有多层次的孩子。我该如何为它编写Django查询?它需要表现得像递归函数。
答案 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更实用。