Django саморекурсивный запрос фильтра для всех дочерних элементов
У меня есть эта модель с привязкой к внешнему ключу с самостоятельной ссылкой:
class Person(TimeStampedModel):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
Теперь я хочу получить всех многоуровневых детей для человека. Как написать для него запрос Django? Он должен вести себя как рекурсивная функция.
Ответы
Ответ 1
Вы всегда можете добавить рекурсивную функцию к своей модели:
EDIT: Исправлено в соответствии с 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
(Не используйте это, если у вас много рекурсии или данных...)
Продолжая рекомендовать mptt, как предложено errx.
Ответ 2
Вы должны прочитать об измененном обходе дерева предварительного заказа.
Вот реализация django.
https://github.com/django-mptt/django-mptt/
Ответ 3
Предложение sunn0 - отличная идея, но get_all_children() возвращает странные результаты. Он возвращает что-то вроде [Person1, [Person3, Person4], []]. Его следует изменить так, как показано ниже.
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
Ответ 4
Если вы знаете максимальную глубину своего дерева, вы можете попробовать что-то вроде этого (untested):
Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))
Ответ 5
У меня была очень похожая бизнес-проблема , в которой задан член команды, я должен был найти полную команду под ним. Но наличие большого числа сотрудников сделало рекурсивное решение очень неэффективным, а также мой API получал ошибки таймаута с сервера.
Принятое решение принимает node, идет к нему первым ребенком и идет глубоко вниз до нижней части иерархии. Затем снова возвращается к второму ребенку (если существует), а затем снова идет вниз. Короче говоря, он исследует все узлы один за другим и добавляет все члены в массиве. Это приводит к множеству вызовов db, и их следует избегать, если необходимо изучить огромное количество узлов. Решение, с которым я столкнулся, выбирает узлы по-разному. Количество вызовов db равно количеству слоев. Посмотрите на эту ссылку SO. для решения.
Ответ 6
Я знаю, что это старо, но кому-то может помочь.
Учитывая экземпляр модели Person
, скажем Person
, вы можете просто сделать:
children = person.children.all() # children being the related_name of the recursive field