You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django多对多字段Inline管理页查询性能优化求助

问题

我定义了Page模型,其name和description字段为与Content模型关联的多对多字段。我创建了对应的Inline管理页面,但使用Django Silk进行性能测试时,发现产生了11次数据库查询,主要原因是Language字段被重复获取至少4次。相关代码如下:

models.py

class Page(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    name = models.ManyToManyField('content.Content', blank=True, related_name="as_page_names_set")
    description = models.ManyToManyField('content.Content', blank=True, related_name="as_page_description_set")
    slug = models.SlugField()

class Language(models.Model):
    language_name = models.CharField(max_length=16, unique=True)
    iso_code = models.CharField(max_length=6, unique=True)

class Content(models.Model):
    language = models.ForeignKey(Language, models.CASCADE)
    content = RichTextUploadingField(max_length=2048)

admin.py

class PageNameAdmin(admin.StackedInline):
    extra = 1
    model = Page.name.through
    verbose_name = "Page Name"
    form = PageContentForm
    

class PageDescriptionAdmin(admin.StackedInline):
    extra = 1
    model = Page.description.through
    form = PageContentForm
    verbose_name = "Page Description"


@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    inlines = [PageNameAdmin]
    exclude = ('name', 'description')
    
    def get_queryset(self, request: HttpRequest) -> QuerySet[Any]:
        return super().get_queryset(request).prefetch_related('name', 'description')

forms.py

class PageContentForm(forms.ModelForm):
    language = forms.ModelChoiceField(queryset=Language.objects.all(), required=True)
    content = RichTextFormField(required=True)

    FIELDS = ['content', 'language']

    class Meta:
        model = Page.name.through
        fields = ('page', 'content', 'language')
    
    def get_initial_for_field(self, field: Field, field_name: str) -> Any:
        if self.instance.pk and field_name in self.FIELDS:
            return getattr(self.instance.content, field_name)
        return super().get_initial_for_field(field, field_name)
        
    def clean(self) -> Dict[str, Any]:
        if self.instance.pk is None:
            self.cleaned_data['content'] = self.create_content_obj(**self.cleaned_data)
            self.cleaned_data.pop('language')
        else:
            self.cleaned_data['content'] = self.get_content_obj(**self.cleaned_data)
        return super().clean()
    
    def create_content_obj(self, content = None, language = None, *args, **kwargs):
        return Content.objects.create(content=content, language=language)
    
    def get_content_obj(self, *args, **kwargs):
        content_obj = self.instance.content
        for k in self.FIELDS:
            setattr(content_obj, k, kwargs[k])
        content_obj.save()
        return content_obj
问题根源与优化方案

1. 未预取Content关联的Language数据

在PageAdmin的get_queryset方法中,你只预取了name和description对应的Content对象,但没有同时预取Content关联的language字段。当每个Inline表单执行get_initial_for_field方法时,访问self.instance.content.language都会触发一次独立的数据库查询,形成N+1查询问题,这是重复获取Language的核心原因。

2. Language查询集未缓存(可选优化)

PageContentForm中language字段的queryset=Language.objects.all(),每次初始化表单都会执行一次查询。虽然Language数据量通常不大,但缓存这个查询集可以避免无意义的重复查询。


优化后的代码

1. 修改admin.py,预取Content关联的Language

from django.db.models import Prefetch

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    inlines = [PageNameAdmin]
    exclude = ('name', 'description')
    
    def get_queryset(self, request: HttpRequest) -> QuerySet[Any]:
        return super().get_queryset(request).prefetch_related(
            # 预取name关联的Content,并同时预取Content的language
            Prefetch('name', queryset=Content.objects.select_related('language')),
            # 如果启用description的Inline,同样需要添加该预取
            Prefetch('description', queryset=Content.objects.select_related('language'))
        )

2. 优化forms.py,缓存Language查询集

class PageContentForm(forms.ModelForm):
    language = forms.ModelChoiceField(queryset=Language.objects.all(), required=True)
    content = RichTextFormField(required=True)

    FIELDS = ['content', 'language']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 缓存Language查询集,避免重复执行查询
        if not hasattr(self.fields['language'], '_cached_queryset'):
            self.fields['language'].queryset = Language.objects.all()
            self.fields['language']._cached_queryset = True

    class Meta:
        model = Page.name.through
        fields = ('page', 'content', 'language')
    
    # 其余方法保持不变...

内容的提问来源于stack exchange,提问作者mohamed naser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:05:02