我有一个模型,其中包含自引用的外键关系:
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-tree-queries !
你应该了解MPTT(Modified Preorder Tree Traversal)算法。这里有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()
,直到找不到更多的数据/子项。
我也会使用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)
我知道这已经过时了,但是可能会有人受益。
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
)添加到模型中,以便在任何实例上调用。
这是我编写的代码,用于获取所有通过它们的“child”字段连接的帖子的所有步骤
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)
include_self=True
。否则,在每次递归时,我们只是下降了一个更深的层次,从未向r
添加任何内容。只有在进行这种更改后,它才能正确地工作。 - andy