我有一个具有自引用外键关系的模型:
class Person(TimeStampedModel):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
现在我想为一个人获取所有多层次的孩子。如何为它编写 Django 查询?它需要表现得像递归函数。
我有一个具有自引用外键关系的模型:
class Person(TimeStampedModel):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
现在我想为一个人获取所有多层次的孩子。如何为它编写 Django 查询?它需要表现得像递归函数。
您始终可以在模型中添加递归函数:
编辑:根据 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。
编辑:2021 年,因为这个答案仍然受到关注:/
您应该阅读有关修改前序树遍历的内容。这是django的实现。 https://github.com/django-mptt/django-mptt/
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
如果你知道你的树的最大深度,你可以尝试这样的事情(未经测试):
Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))
class Person(TimeStampedModel): name = 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() ,直到找不到更多数据/子代。
我知道这是旧的,但有人可能会得到帮助。
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
然后只需在模型上将其设为 a property
(或 acached_property
如果对您有用),以便可以在任何实例上调用它。
我还将在 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)
注意:以下解决方案与之前的其他答案一样慢。每次递归到其子/父时,我都检查了数据库命中。(如果有人可以通过一些优化和缓存进一步改进,这会更好,也许在查询之前预取相关数据)。同时,mptt更实用。
这是我为获取通过其“子”字段连接的帖子的所有步骤而写的内容
steps = Steps.objects.filter(post = post)
steps_ids = steps.values_list('id', flat=True)
递归获取所有“步骤”的孩子:
objects = Steps.objects.filter(child__in=steps_ids)
while objects:
steps = steps | objects
steps_ids = objects.values_list('id', flat=True)
objects = Steps.objects.filter(child__in=steps_ids)