Получение всех дочерних элементов самоссылающейся модели 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, которые, похоже, не ответили на мой вопрос
- Как рекурсивно запросить в django эффективно?
- Django - Модели - Рекурсивное получение родителей узла листа
- 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