Всем привет!
Ситуация такая. Есть проект интернет-магазина на Django.
models.py
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey
class ProductCategory(MPTTModel):
title = models.CharField(verbose_name=u'Название категории', max_length=100)
parent = TreeForeignKey('self', verbose_name=u'Родительская категория', on_delete=models.CASCADE, null=True,
blank=True,
related_name='product_category_children')
class Product(models.Model):
title = models.CharField(verbose_name=u'Название товара', max_length=100, )
description = models.TextField(verbose_name=u'Подробное описание товара', null=True, blank=True)
price = models.DecimalField(verbose_name=u'Цена товара', max_digits=10, decimal_places=5)
category = models.ForeignKey(ProductCategory, verbose_name=u'Категория товара', related_name='product_category', on_delete=models.CASCADE, default=None)
class Attribute(models.Model):
label = models.CharField(verbose_name=u'Название атрибута', max_length=50)
def __str__(self):
return self.label
class AttributeValue(models.Model):
attribute = models.ForeignKey(Attribute, verbose_name=u'Название атрибута', on_delete=models.CASCADE, related_name='attribute_values')
value = models.CharField(verbose_name=u'Значение атрибута', max_length=255)
def __str__(self):
return '{}: {}'.format(self.attribute.label, self.value)
class CategoryAttributeValue(models.Model):
category = models.ForeignKey(ProductCategory, verbose_name=u'Категория', on_delete=models.PROTECT, null=True, blank=True)
attribute_value = models.ForeignKey(AttributeValue, verbose_name=u'Атрибут', on_delete=models.PROTECT, null=True, blank=True)
def __str__(self):
return '{}: {}'.format(self.attribute_value.attribute.label, self.attribute_value.value)
class ProductAttributeValue(models.Model):
product_type = models.ForeignKey(Product, verbose_name=u'Товар', on_delete=models.PROTECT, null=True, blank=True, related_name='product_attributes')
attribute_value = models.ForeignKey(AttributeValue, verbose_name = u'Атрибут товара', on_delete=models.PROTECT, null=True, blank=True, related_name='product_attributes')
def __str__(self):
return '{}: {}'.format(self.attribute_value.attribute.label, self.attribute_value.value)
admin.py
from django.contrib import admin
from mptt.admin import DraggableMPTTAdmin
from .models import *
from django.contrib.admin.options import BaseModelAdmin
from django.db.models.constants import LOOKUP_SEP
class AdminBaseWithSelectRelated(BaseModelAdmin):
list_select_related = []
def get_queryset(self, request):
return super(AdminBaseWithSelectRelated, self).get_queryset(request).select_related(*self.list_select_related)
def form_apply_select_related(self, form):
for related_field in self.list_select_related:
splitted = related_field.split(LOOKUP_SEP)
if len(splitted) > 1:
field = splitted[0]
related = LOOKUP_SEP.join(splitted[1:])
form.base_fields[field].queryset = form.base_fields[field].queryset.select_related(related)
class AdminInlineWithSelectRelated(admin.TabularInline, AdminBaseWithSelectRelated):
def get_formset(self, request, obj=None, **kwargs):
formset = super(AdminInlineWithSelectRelated, self).get_formset(request, obj, **kwargs)
self.form_apply_select_related(formset.form)
return formset
class AdminWithSelectRelated(admin.ModelAdmin, AdminBaseWithSelectRelated):
def get_form(self, request, obj=None, **kwargs):
form = super(AdminWithSelectRelated, self).get_form(request, obj, **kwargs)
self.form_apply_select_related(form)
return form
class CategoryAttributeValueInline(AdminInlineWithSelectRelated):
model = CategoryAttributeValue
extra = 0
list_select_related = ['attribute_value__attribute']
autocomplete_fields = ('attribute_value',)
class ProductAttributeValueInline(AdminInlineWithSelectRelated):
model = ProductAttributeValue
extra = 0
list_select_related = ['attribute_value__attribute']
autocomplete_fields = ('attribute_value',)
class AttributeValueAdmin(AdminWithSelectRelated):
list_select_related = ('attribute',)
search_fields = ('value',)
class ProductCategoryAdmin(DraggableMPTTAdmin):
list_select_related = ('parent',)
inlines = [CategoryAttributeValueInline]
class ProductAdmin(admin.ModelAdmin):
inlines = [ProductAttributeValueInline]
admin.site.register(ProductCategory, ProductCategoryAdmin)
admin.site.register(Product, ProductAdmin)
admin.site.register(Attribute)
admin.site.register(AttributeValue, AttributeValueAdmin)
ProductCategory - категории продуктов.
Product - модель продукта, с какими-то общими параметрами (название,описание, цена и т.д.). Также имеет ссылку на категорию.
Модель Attribute - названия характеристики товара (размер, цвет, вес и т.д.).
AttributeValue - значения этих характеристик (черный, белый, 5 кг, 10 кг и т.д.). Имеет связь с Attribute.
Модель CategoryAttributeValue хранит в себе ссылки на ProductCategory и AttributeValue. Нужна собственно для фильтра. Чтобы при переходе на страницу какой-либо категории, в фильтре выводились правильные характеристики. Товары могут быть разные. И характеристики у них тоже разные. Например,у футболок - цвет, размер. У смартфонов - память, диагональ и т.д.
ProductAttributeValue - ссылки на Product и AttributeValue. Для выборки самих товаров по фильтру.
Для удобства редактирования CategoryAttributeValue добавлен к ProductCategory как inline. При запросе к модели категории для редактирования, Silk показывает лишние sql-запросы к связанным моделям.
По хорошему должен быть только 1 запрос (который 4 ый снизу)
SELECT `catalogue_categoryattributevalue`.`id`,
`catalogue_categoryattributevalue`.`category_id`,
`catalogue_categoryattributevalue`.`attribute_value_id`,
`catalogue_attributevalue`.`id`,
`catalogue_attributevalue`.`attribute_id`,
`catalogue_attributevalue`.`value`,
`catalogue_attribute`.`id`,
`catalogue_attribute`.`label`
FROM `catalogue_categoryattributevalue`
LEFT OUTER JOIN `catalogue_attributevalue` ON (`catalogue_categoryattributevalue`.`attribute_value_id` = `catalogue_attributevalue`.`id`)
LEFT OUTER JOIN `catalogue_attribute` ON (`catalogue_attributevalue`.`attribute_id` = `catalogue_attribute`.`id`)
WHERE `catalogue_categoryattributevalue`.`category_id` = 2
ORDER BY `catalogue_categoryattributevalue`.`id` ASC
Но он делает еще 6 отдельных запросов с разными айди (сейчас там как раз 6 связанных объектов)
SELECT `catalogue_attributevalue`.`id`,
`catalogue_attributevalue`.`attribute_id`,
`catalogue_attributevalue`.`value`,
`catalogue_attribute`.`id`,
`catalogue_attribute`.`label`
FROM `catalogue_attributevalue`
INNER JOIN `catalogue_attribute` ON (`catalogue_attributevalue`.`attribute_id` = `catalogue_attribute`.`id`)
WHERE `catalogue_attributevalue`.`id` IN (5)
Классы AdminBaseWithSelectRelated, AdminInlineWithSelectRelated, AdminWithSelectRelated - это решение, которое я нашел
здесь. Без них кол-во запросов могло достигать до 100.
При сохранении модели так же много запросов
Во фронтенде через select_related() таких проблем нет. Могу предположить, что, чтобы сократить запросы при сохранении (POST), нужно переопределить save()-метод. Но у кого, у модели или формы... А вот где делается эти 6 запросов, не понятно.