Получение всех дочерних элементов самоссылающейся модели Django во вложенной иерархии

Введение
В настоящее время мы работаем над проектом Django REST Framework. Он подключается к базе данных Postgres, которая хранит некоторые иерархические данные (древовидная структура), уходящие на несколько уровней вглубь. Мы должны предложить конечную точку для GET-запросов, которая возвращает всю вложенную структуру дерева (родитель, дети, внуки и т.д.), когда не предлагается никаких параметров.

ВНИМАНИЕ: Пожалуйста, не отмечайте это как дубликат сразу, не прочитав полностью вопрос. Здесь уже есть очень похожие вопросы и я уже попробовал на них ответить. Я упоминаю об этом в своем сообщении и о том, с какими проблемами я столкнулся при попытке ответить на эти вопросы.

Образцы данных
В таблице ниже показаны примерные данные регионов, где каждый регион может иметь родителя, что указывает на иерархию регионов. В этом примере иерархия имеет три уровня (world>continent>country). Но в реальности дерево может быть гораздо глубже, иметь неизвестное количество уровней (world>continent>country>province>city>neighborhood>etc.).

id region parent_region_id
1 world NULL
2 europe 1
3 asia 1
4 africa 1
5 belgium 2
6 germany 2
7 spain 2
8 japan 3
9 indonesia 3
10 vietnam 3
11 tanzania 4
12 egypt 4
13 senegal 4

Наша цель
Вывод JSON, показанный ниже, - это то, чего мы пытаемся достичь. Это цель для тела ответа GET-запроса для ресурса /region.

{
   "id":1,
   "region":"world",
   "children":[
      {
         "id":2,
         "region":"europe",
         "children":[
            {
               "id":5,
               "region":"belgium"
            },
            {
               "id":6,
               "region":"germany"
            },
            {
               "id":7,
               "region":"spain"
            }
         ]
      },
      {
         "id":3,
         "region":"asia",
         "children":[
            {
               "id":8,
               "region":"japan"
            },
            {
               "id":9,
               "region":"indonesia"
            },
            {
               "id":10,
               "region":"vietnam"
            }
         ]
      },
      {
         "id":4,
         "region":"africa",
         "children":[
            {
               "id":11,
               "region":"tanzania"
            },
            {
               "id":12,
               "region":"egypt"
            },
            {
               "id":13,
               "region":"senegal"
            }
         ]
      }
   ]
}

Что мы пытались сделать и чего достигли на данный момент
Вот как мы пытались достичь нашей цели. Смотрите код ниже для моделей, сериализаторов и представлений:

Models.py
________
class HierarchyData:
                region = models.CharField(max_length=100, null=False, default=None)
                parent = models.ForeignKey("self", models.DO_NOTHING, null=True, blank=True, db_column='parent', related_name="children")
 
 
Serializers.py
__________
class HeirarchyDataSerializer(serialisers.ModelSerializer):
                class Meta:
                                model = HierarchyData
                                fields = [“id”,”region”, “children”]
               
Views.py
__________
Class ListHierarchyData(generics.ListAPIView):
                queryset = HierarchyData.objects.all()
                serializer_class = HeirarchyDataSerializer
                permission_classes = [isAuthenticated]

Когда я вызываю конечную точку для заданного сценария, я получаю ответ JSON в следующем формате:

       {
                                “id”: 1,
                                “region”: “world”,
                                “children”: [ 2,3,4]
                }

Связанные вопросы stack overflow, которые, похоже, не ответили на мой вопрос

  1. Как рекурсивно запросить в django эффективно?
  2. Django - Модели - Рекурсивное получение родителей узла листа
  3. Django саморекурсивный запрос фильтра по иностранному ключу для всех дочерних узлов

Вышеупомянутый вопрос частично решает мою проблему, но я все еще не могу получить желаемый результат. Смотрите подробности ниже:

1: Я не могу касаться базы данных напрямую, я должен взаимодействовать с базой данных только через ORM.

2: Рекурсивный тайм-аут и не может сериализоваться, говоря, что объект типа "Model" не является сериализуемым.

3: Этот вариант частично сработал для меня: Основываясь на этом сообщении, я попытался добавить в модель следующее:

def get_children(self):
          children = list()
          children.append(self)
          for child in self.children.all():
              children.extend(children.get_children())
          return children

Затем я получаю всех вложенных детей, но все вложенные значения находятся на одном уровне. Например, у world есть дети [2,3,4], а у них самих есть (внучатые) дети. Затем он перечисляет их в одной строке, например, children = [2,3,4,5,6,7,8,9, 10, 11,12,13]. Это не отражает уровни в данных выборки.
Затем я попробовал следующее решение для модели:

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

Этот вариант работает; он находит вложенные дочерние элементы, но это создает две проблемы: a. Он выдает мне ошибки сериализатора, когда я использую код как он есть, но если я добавляю 'get_all_children' в сериализатор и добавляю другой сериализатор для этого атрибута, то он сериализует объекты, что меня устраивает. b. Он не может добавить их вложенным способом, он просто вставляет список в другой список без дочерних объектов. Он показывает данные следующим образом (ограничено Европой, чтобы не показывать огромный пример):

{
   "id":1,
   "region":"world",
   "get_all_children":[
      [
         {
            "id":2,
            "region":"europe"
         }
      ],
      [
         [
            {
               "id":5,
               "region":"belgium"
            }
         ],
         [
            {
               "id":6,
               "region":"germany"
            }
         ],
         [
            {
               "id":7,
               "region":"spain"
            }
         ]
      ]
   ]
}
 

Теперь данные в порядке, за исключением того, что после Europe он не начинает вкладывать дочерние элементы в тот же массив, он просто начинает новый массив для дочерних элементов и добавляет их к внешнему списку. Это, по сути, добавляет вложенную структуру, не вкладывая ее в родительскую.

Наш вопрос Как мы можем вернуть вывод, упомянутый в "нашей цели", для данных региона, который содержит древовидную структуру, уходящую на неизвестное количество уровней вглубь? Конечно, это конечная глубина.

Единственное ограничение, которое я должен соблюдать, это то, что я не могу редактировать часть представления!

Заранее спасибо всему сообществу и извините, если я допустил ошибку при объяснении, так как это мой первый опыт публикации stack overflow

Вы можете использовать атрибут depth в вашем сериализаторе, т.е.

class Meta:
    model = Model
    fields = ['id', 'region', 'children', 'parent']
    depth = 2

Или используйте метод to_representation в вашем сериализаторе:

def to_representation(self, instance):
        self.fields['parent'] = SerializerClass(many=False, read_only=True)
        self.fields['children'] = SerializerClass(many=True, read_only=True)
        return super(SerializerClass, self).to_representation(instance)

Это позволит вам запрашивать children с related_name, установленными на модели, а также parent

Вернуться на верх