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