ForeignKey для дерева Django-MPTT, генерирующего шторм SQL-запросов в встроенном админке

Я столкнулся с серьезной проблемой производительности в связи с django-mptt. Вот мой случай:

  • У меня класс викторины
  • У меня есть класс вопросов с FK для викторины и FK для класса Category
  • У меня есть класс категории, который представляет собой дерево MPTT (потому что моя категоризация является иерархической)

Теперь у меня есть настоящий тест с 7 вопросами и административным представлением, которое показывает вопросы как встроенные в представление QuizzAdmin, а встроенные строки содержат поле Category as Select.

Затем возникает проблема:

  • У меня были вопросы, загруженные как prefetch_related (и даже пытался, чтобы вопросы__category) были загружены таким образом
  • Несмотря на это, я вижу свою панель инструментов отладки, показывающую серию из 16 запросов, происходящих во время рендеринга шаблона (template / edit_inline / tabular.html). На моем ноутбуке разработчика это означает 1 минуту для загрузки всего этого (а в моей тестовой среде с фактическими данными это означает 10 минут!)

Эти 16 запросов являются последовательностью следующих: (пожалуйста, обратите внимание, что я тестируюсь с фиктивными категориями)

SELECT "quizz_category"."id", "quizz_category"."parent_id", "quizz_category"."name", 
"quizz_category"."name_en", "quizz_category"."name_fr", "quizz_category"."lft",
"quizz_category"."rght", "quizz_category"."tree_id", "quizz_category"."level",
"quizz_category"."description", "quizz_category"."description_en",
"quizz_category"."description_fr" FROM "quizz_category" ORDER BY
"quizz_category"."tree_id" ASC, "quizz_category"."lft" ASC

а также

SELECT "quizz_category"."id", "quizz_category"."parent_id", "quizz_category"."name", 
"quizz_category"."name_en", "quizz_category"."name_fr", "quizz_category"."lft",
"quizz_category"."rght", "quizz_category"."tree_id", "quizz_category"."level",
"quizz_category"."description", "quizz_category"."description_en",
"quizz_category"."description_fr" FROM "quizz_category" WHERE ("quizz_category"."lft" <= 3
AND "quizz_category"."rght" >= 6 AND "quizz_category"."tree_id" = 1 ) ORDER BY
"quizz_category"."lft" ASC

Есть идеи, что я могу сделать, чтобы уменьшить количество запросов?

Спасибо вперед, Лос-Анджелес

[ИЗМЕНИТЬ 1]

Была одна глупая вещь, которая объясняет половину проблемы: __unicode __ () моей категории смотрел на __unicode __ () родителей объекта (к счастью, мое дерево всего на 2 уровня)

Теперь в моей оптимальной конфигурации у меня все еще есть 9 раз «SELECT ... FROM quizz_category» (без предложения WHERE) для 8 записей, предположительно для создания вариантов выбора в поле Select.

Кто-нибудь знает, как кэшировать этот запрос и запускать его только один раз?

Примечание: моя текущая оптимальная конфигурация - иметь .select_related ('category') в QuestionInline


class QuestionInline(admin.TabularInline): # admin.StackedInline
    model = Question
    extra = 0
    ordering = ['position',]

    def queryset(self, request):
        return super(QuestionInline, self).queryset(request).select_related('category')


class QuizzAdmin(admin.ModelAdmin):
    list_display = ["name","rating_scale"]
    inlines = [QuestionInline]
    fieldsets = (
        (None, {'fields': (('name'), ('type',), 'description',
                           'rating_scale' )}),
    )

    def queryset(self, request):
        if getattr(self,'is_change_list', False):
            # it's a changelist view, we don't need details on ForeignKey-accessible objects
            return super(QuizzAdmin, self).queryset(request)
        else:
            return super(QuizzAdmin, self).queryset(request).select_related('rating_scale')

    def changelist_view(self, request, extra_context=None):
        self.is_change_list = True
        return super(QuizzAdmin, self).changelist_view(request, extra_context)

class Category(AbstractAnalyticTreeCategory):
    description         = BusinessTextField(_("description"))  # basically a text field of mine

    tree = AnalyticTreeManager()

    def __unicode__(self):
        return self.name

class Quizz(models.Model):
    name                = models.CharField(_("name of the quizz"), unique=True, max_length=60)
    description         = BusinessTextField(_("description"))
    type                = models.CharField(_("type"), choices=QUIZZ_TYPE_CHOICES, default=QUIZZ_SELF_EVALUATION, null=False, blank=False, max_length=2)
    rating_scale        = models.ForeignKey(MCQScale, verbose_name=_("applicable rating scale"), on_delete=models.PROTECT)


    def __unicode__(self):
        return self.name



class Question(models.Model):
    position = models.IntegerField(verbose_name=_("order index"), help_text=_("Order in which the question will appear."))
    quizz               = models.ForeignKey(Quizz, verbose_name=_("Related quizz"), null=False, blank=False, related_name='questions')
    title               = BusinessCharField(_("item"), max_length=60, null=True, blank=True)
    text                = BusinessTextField(_("question text"),)
    category            = TreeForeignKey(Category, verbose_name=_("dimension"), null=True, blank=False, on_delete=models.SET_NULL)

    def __unicode__(self):
        return self.title

Вот что говорит об этих запросах панель отладки (все равно):

SELECT "quizz_category"."id", "quizz_category"."parent_id", "quizz_category"."name", "quizz_category"."name_en", "quizz_category"."name_fr", "quizz_category"."lft", "quizz_category"."rght", "quizz_category"."tree_id", "quizz_category"."level", "quizz_category"."description", "quizz_category"."description_en", "quizz_category"."description_fr" FROM "quizz_category" ORDER BY "quizz_category"."tree_id" ASC, "quizz_category"."lft" ASC 3,68816058264% 1,66 Sel Expl Connection: default Isolation Level: Read committed Transaction Status: In transaction /Library/Python/2.7/site-packages/django/contrib/staticfiles/handlers.py in call(72) return self.application(environ, start_response) /Library/Python/2.7/site-packages/django/contrib/admin/widgets.py in render(263) output = [self.widget.render(name, value, *args, **kwargs)] 49

{{ field.contents|linebreaksbr }}

50 {% else %} 51
{{ field.field.errors.as_ul }} 52
{{ field.field }} 53
{% endif %} 54
55
{% endfor %} /Library/Python/2.7/site-packages/django/contrib/admin/templates/admin/edit_inline/tabular.html


person lai    schedule 06.09.2013    source источник
comment
покажите нам код нарушения, пожалуйста   -  person dan-klasson    schedule 06.09.2013
comment
Хорошо, ребята, вы думаете, что это stackoverflow.com/questions/8176200/ могут помочь в моей ситуации? (Я так думаю и постараюсь)   -  person lai    schedule 07.09.2013


Ответы (2)


Я использую django 1.10, и ни одно из решений в кешировании вариантов набора запросов для ModelChoiceField или ModelMultipleChoiceField в форме Django не помогло мне. Я закончил использовать другое решение, основанное на tree_item_iterator django-mptt. Моя форма увеличилась с 560 SQL-запросов до 10. Мой код:

from categories.models import Category
from mptt.utils import tree_item_iterator


def get_tree_choices(queryset, level_indicator='+----', ancestors=False):
    choices = []
    for node, tree in tree_item_iterator(queryset, ancestors=ancestors):
        name=''
        if ancestors:
            for i in tree['ancestors']:
                name+=level_indicator
            name+=' %s' % node.name
        else:
            name = str(node)
        choices.append((node.id, name))
    return choices


# Register your models here.
class CompanyAdminForm(forms.ModelForm):
    category=forms.ChoiceField(choices=get_tree_choices(Category.objects.all()))

    error_messages = {
        'invalid_vat_id': _('Invalid Portuguese VAT ID')
    }

    class Meta:
        exclude = ('category',)
        ...

    def save(self, commit=True):
        company=super(CompanyAdminForm, self).save(commit=False)
        company.category=Category.objects.get(id=self.cleaned_data['category'])
        company.save()
        return company
    ...


@admin.register(Company)
class CompanyAdmin(admin.ModelAdmin):
        ...

Все еще не на 100% удовлетворен, но, по крайней мере, это быстро.

person Dário    schedule 12.10.2016

Итак .. Я нашел решение, которое вдохновлено Варианты набора запросов кеширования для ModelChoiceField или ModelMultipleChoiceField в форме Django, которые я описываю в этом сообщении.

У администратора Django есть странные накладные расходы на 1 запрос из-за встроенного механизма фабрики (я не вдавался в подробности). Это объясняет, почему в обычном случае у вас есть 2 * k + 1 запроса (k = количество элементов во встроенном наборе форм).

Надеюсь, проблема решена.

LAI

person lai    schedule 07.09.2013