I'm faced with a dramatic performance issue in connection with django-mptt. Here is my case:
Now, I have an actual Quizz with 7 Questions and an admin view that shows the Questions as inlines to the QuizzAdmin view, and the inlines bear the Category as Select field.
Then comes the trouble:
These 16 queries are a succession of the below: (pls note I'm in test with dummy categories)
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
and
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
Any idea of what I could do to reduce the number of queries?
Thanks ahead LA
[EDIT 1]
There was a stupid thing that explains half of the issue: my Category's __unicode__() was looking at the object's parents' __unicode__() (fortunately my tree is only 2-level deep)
Now in my optimal configuration, I still have 9 times "SELECT ... FROM quizz_category" (no WHERE clause) for 8 entries, supposedly for building the choices of the Select field.
Anyone has an idea about how to get this query cached and only run once?
Note: my current optimal configuration is to have .select_related('category') in 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
Here is what the debug toolbar says about these queries (all the same):
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
So.. I found a solution, which is inspired from Caching queryset choices for ModelChoiceField or ModelMultipleChoiceField in a Django form and which I describe in that post.
Django admin has a strange overhead of 1 query due to the inline factory mechanism (I haven't gone in depth into this). This explains why in the normal case you have 2*k + 1 queries (k=number of items in the inline formset).
issue solved hopefully.
LAI