Django admin inline выполняет слишком много запросов при наличии дополнительных ForeignKey
У меня есть следующие модели Django:
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,
)
А это мой 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)
Проблема в том, что поскольку мой inline содержит не только foreignkey предприятия, но и дополнительный foreignkey, указывающий на MediaTopic, каждый раз, когда загружается страница изменения админки django, она делает много запросов к базе данных. Я знаю, что это знаменитая проблема N+1, и что select_related или prefetch_related решили бы ее, проблема в том, что я не знаю, куда его поместить, я пробовал поместить его в класс EnterpriseAdmin, а также в класс EnterpriseRelatedMediaInline, и, похоже, это не работает! Каждый раз, когда админ загружает страницу, для каждого экземпляра инлайна (которых 10), он делает запрос, чтобы вытащить все экземпляры MediaTopic, чтобы быть доступным в атрибуте foreignkey формы.
Я использую Django Debug Toolbar, и вот как выглядит страница изменений:
(Обратите внимание, что справа внизу, когда написано «SQL», выполняется 17 запросов)
Вот какие запросы выполняются:
Далее, я понимаю, что это потому, что он вытягивает все экземпляры MediaTopic для каждого экземпляра EnterpriseRelatedMedia, показанного как Inline, но я не знаю, куда поместить select_related и prefetch_related! Пожалуйста, помогите
Да, я сделал тест, и когда я удаляю атрибут «topic» из модели EnterpriseRelatedMedia, проблема прекращается, и она делает всего несколько запросов, максимум 5
Это одно из последствий FormSet
: это коллекция Form
, и каждый Form
заполняется выпадающим списком отдельным QuerySet
, поэтому для каждого элемента в строке, имеющего такой выпадающий список, будет сделан запрос, и если количество элементов в строке велико, то будет использовано много запросов.
Возможно, самый удобный способ избавиться от этого - добавить topic
в .autocomplete_fields
[Django-doc], тогда он будет лениво заполнять их вызовами AJAX, и объединить это с .select_related('topic')
для первоначального заполнения 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')
Таким образом, это означает, что вы добавляете search_fields = ['name']
на ModelAdmin
модели MediaTopic
.
Возможно, вы также можете предварительно получить элементы с соответствующими темами, используя Prefetch
объект [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'),
)
)
)