Django self-recursive foreignkey filter query for all childs

喜你入骨 提交于 2019-11-27 18:49:51

You can always add a recursive function to your model:

EDIT: Corrected according to 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

(Don't use this if you have a lot of recursion or data ...)

Still recommending mptt as suggested by errx.

You should read about Modified preorder tree traversal. Here is django implementation. https://github.com/django-mptt/django-mptt/

sunn0's suggestion is great idea, but get_all_children() returns weird results. It returns something like [Person1, [Person3, Person4], []]. It should be changed to like below.

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

If you know the max depth of your tree, you could try something like this (untested):

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

I know this is old, but someone might just get helped.

     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

and then simply make this a property (OR a cached_property if that works for you) on the model so it can be called on any instance.

Shubhanshu

I had a very similar business problem, wherein given a team member, I was supposed to find out the complete team under under him. But having a large number of employees made the recursive solution very inefficient and also my API was getting time-out errors from server.

The accepted solution takes the node, goes to it's first child and goes deep down till bottom of the hierarchy. Then comes back up again to the second child (if exists), and then again goes down till the bottom. In short, it explores all the nodes one by one and appends all the members in an array. This leads to a lot of db calls and should be avoided if there is a huge number of nodes to be explored. The solution I came up with, fetches the nodes layer-wise. The number of db calls is equal to the number of layers. Have a look at this SO link for the solution.

I'm also going to write in QuerySet since this will allow you to chain them. And I will provide answer for both retrieval of all children and all parents.

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)

Now we need to set PersonQuerySet as the manager.

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

    people = PersonQuerySet.as_manager()

So here's the final query.

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)

Note: The following solutions is as slow as as the rest of the other answers earlier. I have inspected the Database hit everytime it recurse to its child/parent. (If anyone can improve further with some optimization & caching, this would be better, perhaps prefetch relevant data before querying). In the meanwhile, mptt is more practical.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!