соответствующие данные, прежде чем запрашивать). Между тем, mptt более практичен.

я есть эта модель с собственной ссылкой внешнего ключа:

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

Теперь я хочу получить всех многоуровневых детей для человека. Как мне написать запрос Django для него? Он должен вести себя как рекурсивная функция.

Ответы на вопрос(7)

вы можете попробовать что-то вроде этого (не проверено):

Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))
Решение Вопроса

РЕДАКТИРОВАТЬ: Исправлено в соответствии с SeomGi Хан

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.

 alan08 июн. 2012 г., 12:43
Должно быть get_all_children () в качестве вызова функции, и вам нужно использовать r = r + c.get_all_children (), иначе вы получите вложенные списки. Я не имею права редактировать это сам
 sunn009 июн. 2012 г., 10:57
Обновлен код согласно комментарию. Если бы всем было разрешено редактировать все сообщения, у нас была бы большая проблема :)
 bobleujr12 июн. 2016 г., 18:51
Ваше решение дает хорошее представление, но не проверено. В конце r является вложенным пустым списком. Пожалуйста, подумайте об исправлении ..
 andy07 июн. 2014 г., 18:54
Я думаю, что это должно иметьinclude_self=True во внутреннем вызове функции. В противном случае, при каждой рекурсии мы просто опускаемся на один уровень, фактически никогда ничего не добавляя кr, Это правильно сработало только после внесения этих изменений.

но 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

что это старый, но кто-то может просто помочь.

     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

а затем просто сделать этоproperty (ИЛИcached_property если это работает для вас) на модели, так что он может быть вызван в любом случае.

 yusuf.oguntola03 июл. 2018 г., 14:13
Спасибо @ Да, я, наверное, неправильно понял вопрос. Я только что обновил свой ответ похожим фрагментом, который у меня есть в одном из моих проектов.
 Yeo24 июн. 2018 г., 18:46
Это не отвечает на вопрос "получить всемногоуровневый дети для человека«... Это на самом деле только прямые дети (т.е.одноуровневый) для человека.

бизнес проблема, при этом учитывая члена команды, я должен был выяснить всю команду под ним. Но большое количество сотрудников делало рекурсивное решение очень неэффективным, а мой API получал ошибки тайм-аута с сервера.

Принятое решение берет узел, переходит к его первому дочернему элементу и углубляется вглубь иерархии. Затем снова возвращается ко второму ребенку (если существует), а затем снова опускается до самого дна. Короче говоря, он исследует все узлы один за другим и добавляет все элементы в массив. Это приводит к большому количеству вызовов БД, и его следует избегать, если нужно исследовать огромное количество узлов. Решение, которое я придумал, извлекает узлы послойно. Количество вызовов в БД равно количеству слоев. Посмотри на этоТАК ссылка для решения.

Вот реализация Django.https://github.com/django-mptt/django-mptt/

 Ahsan18 янв. 2011 г., 15:59
нужно ли использовать стороннее решение? Есть ли другой способ??
 Tomasz Zielinski18 янв. 2011 г., 22:49
@Ashan: Вы всегда можете прочитать об этом, например. Вот:dev.mysql.com/tech-resources/articles/hierarchical-data.html и напиши код сам

так как это позволит вам связать их в цепочку. И я предоставлю ответ как для поиска всех детей и всех родителей.

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)

Теперь нам нужно установитьPersonQuerySet как менеджер.

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

    people = PersonQuerySet.as_manager()

Итак, вот последний запрос.

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)

Примечание. Следующие решения работают так же медленно, как и остальные ответы ранее. Я проверял попадание в базу данных каждый раз, когда она рекурсирует своему ребенку / родителю. (Если кто-то может улучшить дальнейшее с некоторой оптимизацией и кэшированием, это было бы лучше, возможно,упреждающей соответствующие данные, прежде чем запрашивать). Между тем, mptt более практичен.

Ваш ответ на вопрос