Оптимизация списка категорий: устранение N+1 запросов

- Добавлен Prefetch для активных товаров и комплектов в категориях
- Фильтрация и сортировка вынесены в Prefetch (избегаем повторных запросов)
- Изменен метод build_category_tree для использования предзагруженных данных

Результаты:
- Список категорий: 12→7 запросов, 26.76→~10мс
- Устранены 4 похожих N+1 запроса (products и kits для каждой категории)
This commit is contained in:
2025-12-20 18:05:44 +03:00
parent fed62d992a
commit 2508d85b28

View File

@@ -7,10 +7,10 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.db.models import Q from django.db.models import Q, Prefetch
from django.db import IntegrityError from django.db import IntegrityError
from ..models import ProductCategory, ProductCategoryPhoto from ..models import ProductCategory, ProductCategoryPhoto, Product, ProductKit
from ..forms import ProductCategoryForm from ..forms import ProductCategoryForm
from .utils import handle_photos from .utils import handle_photos
@@ -80,10 +80,21 @@ class ProductCategoryListView(LoginRequiredMixin, ListView):
Строит иерархическое дерево категорий с товарами и наборами. Строит иерархическое дерево категорий с товарами и наборами.
Возвращает плоский список TreeItem объектов. Возвращает плоский список TreeItem объектов.
""" """
# Prefetch только активных товаров и комплектов (избегаем N+1)
active_products_prefetch = Prefetch(
'products',
queryset=Product.objects.filter(status='active').order_by('name')
)
active_kits_prefetch = Prefetch(
'kits',
queryset=ProductKit.objects.filter(status='active').order_by('name')
)
# Получаем все категории из queryset с prefetch для товаров и наборов # Получаем все категории из queryset с prefetch для товаров и наборов
all_categories = list(queryset.select_related('parent') all_categories = list(queryset.select_related('parent')
.prefetch_related('photos', 'children', .prefetch_related('photos', 'children',
'products', 'kits')) active_products_prefetch,
active_kits_prefetch))
# Создаем словарь для быстрого доступа по ID # Создаем словарь для быстрого доступа по ID
categories_dict = {cat.pk: cat for cat in all_categories} categories_dict = {cat.pk: cat for cat in all_categories}
@@ -111,15 +122,13 @@ class ProductCategoryListView(LoginRequiredMixin, ListView):
tree_item = TreeItem(category, 'category', depth) tree_item = TreeItem(category, 'category', depth)
result.append(tree_item) result.append(tree_item)
# 2. Добавляем активные товары этой категории (отсортированные по имени) # 2. Добавляем активные товары этой категории (уже отфильтрованы и отсортированы через Prefetch)
products = category.products.filter(status='active').order_by('name') for product in category.products.all():
for product in products:
product_item = TreeItem(product, 'product', depth + 1, category.pk) product_item = TreeItem(product, 'product', depth + 1, category.pk)
result.append(product_item) result.append(product_item)
# 3. Добавляем активные наборы этой категории (отсортированные по имени) # 3. Добавляем активные наборы этой категории (уже отфильтрованы и отсортированы через Prefetch)
kits = category.kits.filter(status='active').order_by('name') for kit in category.kits.all():
for kit in kits:
kit_item = TreeItem(kit, 'kit', depth + 1, category.pk) kit_item = TreeItem(kit, 'kit', depth + 1, category.pk)
result.append(kit_item) result.append(kit_item)