Django self-recursive foreignkey filter query for all childs

Mam ten model z relacją z kluczem obcym:

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

Teraz chcę mieć wszystkie wielopoziomowe dzieci dla osoby. Jak napisać dla niego zapytanie Django? Musi zachowywać się jak funkcja rekurencyjna.

Author: ghickman, 2011-01-18

7 answers

Zawsze możesz dodać funkcję rekurencyjną do swojego modelu:

EDIT: poprawione według 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

(nie używaj tego, jeśli masz dużo rekurencji lub danych ...)

Nadal zalecam mptt zgodnie z sugestią errx.

 24
Author: sunn0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-14 11:23:54

Powinieneś przeczytać o zmodyfikowanym pre-order Tree traversal. Oto implementacja django. https://github.com/django-mptt/django-mptt/

 13
Author: errx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-18 14:56:47

Sugestia Sunn0 jest świetnym pomysłem, ale get_all_children () zwraca dziwne wyniki. Zwraca coś w rodzaju [Person1, [Person3, Person4], []]. Należy go zmienić na jak poniżej.

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
 7
Author: SeomGi Han,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-19 12:23:27

Jeśli znasz maksymalną głębokość drzewa, możesz spróbować czegoś takiego (nieprzetestowanego):

Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))
 5
Author: Tomasz Zielinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-18 21:50:56

Wiem, że to stare, ale ktoś może dostać pomoc.

     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 następnie po prostu zrób to property (lub cached_property jeśli to działa) na modelu, aby można było go wywołać na dowolnej instancji.

 1
Author: yusuf.oguntola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-03 12:12:03

Miałem bardzo podobny problem biznesowy , w którym biorąc pod uwagę członka zespołu, miałem dowiedzieć się, że cały zespół pod nim. Ale duża liczba pracowników sprawiła, że rekurencyjne rozwiązanie było bardzo nieefektywne, a także moje API otrzymywało błędy czasowe z serwera.

Przyjęte rozwiązanie pobiera węzeł, przechodzi do jego pierwszego dziecka i idzie głęboko w dół aż do dołu hierarchii. Następnie wraca ponownie do drugiego dziecka (jeśli istnieje), a następnie ponownie idzie w dół aż do dna. Krótko mówiąc, bada wszystkie węzły jeden po drugim i dołącza wszystkie członkowie w tablicy. Prowadzi to do wielu wywołań db i należy ich unikać, jeśli do zbadania jest ogromna liczba węzłów. Rozwiązanie, które wymyśliłem, pobiera węzły warstwowo. Liczba wywołań db jest równa liczbie warstw. Spójrz na to więc link dla rozwiązania.

 0
Author: Shubhanshu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:27

Zamierzam również napisać w QuerySet, ponieważ to pozwoli Ci je połączyć. I udzielę odpowiedzi zarówno za odzyskanie wszystkich dzieci, jak i wszystkich rodziców.

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)

Teraz musimy ustawić PersonQuerySet jako menedżera.

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

    people = PersonQuerySet.as_manager()

Oto ostatnie pytanie.
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)

Uwaga: Poniższe rozwiązania są równie powolne, jak pozostałe odpowiedzi wcześniej. Sprawdzałem trafienie bazy danych za każdym razem, gdy rekurencyjnie zwraca się do jego dziecka / rodzica. (Jeśli ktoś może jeszcze poprawić z trochę optymalizacji i buforowania, byłoby lepiej, być może prefetch odpowiednie dane przed zapytaniem). Tymczasem mptt jest bardziej praktyczny.

 0
Author: Yeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-24 19:05:58