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 запросов)

My Django Admin Change Page

Вот какие запросы выполняются:

The queries being made

Далее, я понимаю, что это потому, что он вытягивает все экземпляры 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'),
                )
            )
        )
Вернуться на верх