Django admin inline performing too many queries when having additional ForeignKey
I have the following Django models:
from django.db import models
from django.utils.translation import gettext_lazy as _
class Enterprise(models.Model):
nome = models.CharField(max_length=255)
class MediaTopic(models.Model):
name = models.CharField(max_length=255)
class EnterpriseRelatedMedia(models.Model):
enterprise = models.ForeignKey(
Enterprise,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="related_medias",
related_query_name="related_media",
)
topic = models.ForeignKey(
MediaTopic,
on_delete=models.DO_NOTHING,
related_name="enterprise_related_medias",
related_query_name="enterprise_related_media",
blank=True,
null=True,
)
And this is my admin.py:
from django.contrib import admin
from . import models
class EnterpriseRelatedMediaInline(admin.StackedInline):
model = models.EnterpriseRelatedMedia
extra = 1
@admin.register(models.Enterprise)
class EnterpriseAdmin(admin.ModelAdmin):
inlines = [
EnterpriseRelatedMediaInline,
]
admin.site.register(models.MediaTopic)
The problem is that since my inline contains not only the enterprise foreignkey but also an extra foreignkey pointing to MediaTopic, everytime my django admin change page loads, it makes a lot of queries to the database. Im aware that this is the famous N+1 problem, and that a select_related or prefetch_related would solve it, the problem is that i do not know where to put it, i tried putting it in the EnterpriseAdmin class as well as the EnterpriseRelatedMediaInline class, and it doesn't seem to work! Every time the admin loads the page, for each instance of the inline (which are 10), it makes a query to pull all the instances of MediaTopic to be available in the foreignkey attribute of the form.
Im using Django Debug Toolbar, and this is how the change page looks like:
(Notice how it does 17 queries on the bottom right when it says "SQL")
these are the queries being performed:
Again, im aware that its because its pulling all instances of MediaTopic for each instance of EnterpriseRelatedMedia shown as an Inline, but i do not know where to put the select_related and prefetch_related! Please help
And yes, i made the test, and when i remove the "topic" attribute from EnterpriseRelatedMedia model, the problem stops, and it makes only a few queries, 5 at maximum
This is one of the consequences of the FormSet
: this is a collection of Form
s, and each Form
is populated with a dropdown by an individual QuerySet
, so for each item in the inline that has such dropdown, it will make a query, and if the number of items in the inline is large, a lot of queries will be used.
Perhaps the most convenient way to get rid of this is to add topic
to the .autocomplete_fields
[Django-doc], then it will lazily populate these with AJAX calls, and combine this with a .select_related('topic')
for the initial population of the FormSet
:
class EnterpriseRelatedMediaInline(admin.StackedInline):
model = models.EnterpriseRelatedMedia
autocompete_fields = ('topic',)
extra = 1
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).select_related('topic')
This thus means that you add a search_fields = ['name']
on the ModelAdmin
of the MediaTopic
model.
You can probably also prefetch the elements with the corresponding topics by using a Prefetch
object [Django-doc]:
from django.db.models import Prefetch
@admin.register(models.Enterprise)
class EnterpriseAdmin(admin.ModelAdmin):
inlines = [
EnterpriseRelatedMediaInline,
]
def get_queryset(self, *args, **kwargs):
return (
super()
.get_queryset(*args, **kwargs)
.prefetch_related(
Prefetch(
'related_medias',
EnterpriseRelatedMedia.objects.select_related('topic'),
)
)
)