Django - Эффективная выборка рекурсивной структуры categoryi

У меня есть модель, которая выглядит следующим образом:

class Category(models.Model):
    name = models.CharField(max_length=50)
    parent = models.ForeignKey(
        'categories.Category',
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='categories'
    )

В основном, в поле parent он ссылается на самого себя. Если родитель имеет значение None, то это корневая категория.

Я использую его для построения иерархии категорий.

Какой способ был бы наиболее эффективным:

  1. получить все объекты через иерархию
  2. отобразить их в шаблоне?

По какой-то причине select_related не приводит к улучшению производительности здесь.

Я также нашел следующее: Как рекурсивно делать запросы в django эффективно?

Но мне было очень трудно применить его к моему примеру. Буду признателен за любую помощь.

Спасибо!

Вот несколько потенциальных способов оптимизации запроса и отображения иерархии категорий:

Используйте метод select_related() для предварительной выборки родительской категории для каждой дочерней категории. Это уменьшит количество запросов, необходимых для получения родительских категорий, и может повысить производительность, если вам нужно получить доступ к родительской категории для каждой дочерней категории в шаблоне:

Получение всех категорий и предварительное получение их родительских категорий

categories = Category.objects.select_related('parent')

Используйте метод prefetch_related() для предварительной выборки всех дочерних категорий для каждой родительской категории. Это уменьшит количество запросов, необходимых для получения дочерних категорий, и может повысить производительность, если вам нужно получить доступ к дочерним категориям для каждой родительской категории в шаблоне:

Получить все родительские категории и предварительно получить их дочерние категории

parent_categories = Category.objects.filter(parent__isnull=True).prefetch_related('categories')

Используйте метод Model.objects.raw() для создания необработанного SQL-запроса, который извлекает иерархию категорий в одном запросе. Это может повысить производительность, если вам нужно получить и отобразить большое количество категорий, поскольку это позволяет избежать накладных расходов на создание и инициализацию нескольких объектов модели. Вот пример необработанного SQL-запроса, который извлекает иерархию категорий:

Создайте необработанный SQL-запрос, который извлекает иерархию категорий

raw_query = """
    SELECT c.id, c.name, c.parent_id
    FROM categories_category c
    WHERE c.parent_id IS NULL
    UNION ALL
    SELECT c.id, c.name, c.parent_id
    FROM categories_category c
    INNER JOIN (
        SELECT c.id, c.name, c.parent_id
        FROM categories_category c
        WHERE c.parent_id IS NOT NULL
    ) child_categories ON child_categories.parent_id = c.id
"""

Выполните необработанный SQL-запрос и получите иерархию категорий

categories = Category.objects.raw(raw_query)

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

{% load recursive_tags %}

<ul>
    {% recursetree categories %}
        <li>
            {{ node.name }}
            {% if not node.is_leaf_node %}
                <ul>
                    {{ children }}
                </ul>
            {% endif %}
        </li>
    {% endrecursetree %}
</ul>

Обратите внимание, что библиотека django-mptt должна быть установлена и загружена, чтобы использовать тег шаблона recursetree. Обратитесь к документации для получения дополнительной информации о том, как использовать рекурсивные шаблонные теги.

Одним из способов эффективного получения и отображения иерархической структуры категорий в Django с помощью PostgreSQL является использование пакета django-mptt. Этот пакет предоставляет реализацию Modified Preorder Tree Traversal, который является эффективным алгоритмом для представления иерархических структур в базе данных.

Используя django-mptt, вы определите вашу модель Category следующим образом:

from mptt.models import MPTTModel, TreeForeignKey

class Category(MPTTModel):
    name = models.CharField(max_length=50)
    slug = models.SlugField()
    parent = TreeForeignKey(
        'self',
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='categories'
    )

Это добавит в вашу модель несколько полей, таких как left, right, tree_id и level, которые используются алгоритмом django-mptt для представления иерархии.

Чтобы получить все объекты в иерархии, вы можете использовать метод get_descendants, предоставляемый django-mptt:

root_category = Category.objects.get(parent=None)
all_categories = root_category.get_descendants(include_self=True)

Это вернет набор запросов, содержащий все категории в иерархии, начиная с корневой категории.

Для отображения категорий в шаблоне можно использовать теги шаблона mptt, предоставляемые django-mptt:

{% load mptt_tags %}
<ul>
    {% recursetree all_categories %}
        <li>
            {{ node.name }}
            {% if not node.is_leaf_node %}
                <ul class="children">
                    {{ children }}
                </ul>
            {% endif %}
        </li>
    {% endrecursetree %}
</ul>

Это отобразит иерархическую структуру категорий в формате вложенного списка, с отступом от каждого уровня и стилями в соответствии со стилями mptt по умолчанию.

В целом, использование django-mptt является более эффективным и удобным способом представления и получения иерархических структур в Django по сравнению с использованием рекурсивных SQL-запросов.

Одним из возможных решений может быть использование https://django-mptt.readthedocs.io/en/latest/overview.html#what-is-django-mptt

MPTT - это техника хранения иерархических данных в базе данных. Цель цель - сделать операции поиска очень эффективными. Компромиссом за эту эффективность является то, что выполнение вставок и перемещение элементов по дереву более трудоемко, поскольку требуется дополнительная работа для поддержания структуры дерева в хорошем состоянии в любое время.

from django.db import models
from mptt.models import MPTTModel, TreeForeignKey

class Category(MPTTModel):
    name = models.CharField(max_length=50)
    slug = models.SlugField()
    parent = TreeForeignKey(
        'self',
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='children'
    )

    class MPTTMeta:
        order_insertion_by = ['name']

Вы можете использовать тег шаблона django-mptt следующим образом:

{% load mptt_tags %}
<ul>
    {% recursetree categories %}
        <li>
            {{ node.name }}
            {% if not node.is_leaf_node %}
                <ul class="children">
                    {{ children }}
                </ul>
            {% endif %}
        </li>
    {% endrecursetree %}
</ul>

Имеется учебник и дополнительная информация в библиотеке docs.

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