[Django]-Django self-recursive foreignkey filter query for all childs

45👍

✅

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.

EDIT: 2021 since this answer is still getting attention :/

Use django-tree-queries instead!

20👍

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

8👍

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

6👍

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))

3👍

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() is going to fetch all the children of an instance using the related name and then it’s going to call get_children() on the child if found recursively until no further data/children are found.

1👍

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.

1👍

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.

0👍

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.

0👍

Here is what I wrote to get all the steps of a post that are connected through their ‘child’ field

steps = Steps.objects.filter(post = post) 
steps_ids = steps.values_list('id', flat=True) 

Get all the children of ‘steps’ recursively:

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) 

Leave a comment