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