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/
- [Django]-How to configure where to redirect after a log out in Django?
- [Django]-Open the file in universal-newline mode using the CSV Django module
- [Django]-Jquery template tags conflict with Django template!
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
- [Django]-Bulk create model objects in django
- [Django]-Temporarily Disabling Django Caching
- [Django]-Change the width of form elements created with ModelForm in Django
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))
- [Django]-Django template convert to string
- [Django]-Get list of Cache Keys in Django
- [Django]-Remove duplicates in a Django query
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.
- [Django]-Can I use Socket.IO with Django?
- [Django]-Django development IDE
- [Django]-What's the purpose of Django setting ‘SECRET_KEY’?
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.
- [Django]-Django unique=True not working
- [Django]-How to do a HTTP DELETE request with Requests library
- [Django]-Django Order By Date, but have "None" at end?
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.
- [Django]-Serializing list to JSON
- [Django]-Django release 1.5: 'url' requires a non-empty first argument. The syntax changed in Django 1.5
- [Django]-Why django urls end with a slash?
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.
- [Django]-Drawbacks to running Django under PyPy?
- [Django]-Django-storages with multiple S3 Buckets
- [Django]-Django related_name for field clashes
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)
- [Django]-H14 error in heroku – "no web processes running"
- [Django]-Django REST Framework pagination links do not use HTTPS
- [Django]-How does the order of mixins affect the derived class?