Django Admin Inline含额外ForeignKey时查询过多的解决咨询
解决Django Admin Inline中外键选择框的N+1查询问题
问题根源
你遇到的是Django Admin中Inline表单的外键选择框重复查询问题:每个EnterpriseRelatedMediaInline实例的topic字段下拉框,都会单独执行一次SELECT * FROM mediatopic查询来获取选项,导致页面加载时产生大量重复SQL请求。
解决方法
方法1:重写Inline的formfield_for_foreignkey缓存查询集
通过在Inline类中重写该方法,将MediaTopic的查询集缓存为类级别变量,让所有Inline实例共享同一个查询结果,避免重复执行SQL:
from django.contrib import admin from . import models class EnterpriseRelatedMediaInline(admin.StackedInline): model = models.EnterpriseRelatedMedia extra = 1 def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'topic': # 类级别缓存查询集,仅执行一次SQL if not hasattr(self, '_cached_media_topics'): self._cached_media_topics = models.MediaTopic.objects.all() kwargs['queryset'] = self._cached_media_topics return super().formfield_for_foreignkey(db_field, request, **kwargs) @admin.register(models.Enterprise) class EnterpriseAdmin(admin.ModelAdmin): inlines = [ EnterpriseRelatedMediaInline, ] admin.site.register(models.MediaTopic)
方法2:自定义表单并缓存查询集
通过自定义ModelForm,在表单类级别缓存MediaTopic的查询集,让所有表单实例复用同一查询结果:
from django.contrib import admin from django import forms from . import models class EnterpriseRelatedMediaForm(forms.ModelForm): class Meta: model = models.EnterpriseRelatedMedia fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 表单类级别缓存,所有实例共享查询结果 if not hasattr(self.__class__, '_cached_media_topics'): self.__class__._cached_media_topics = models.MediaTopic.objects.all() self.fields['topic'].queryset = self.__class__._cached_media_topics class EnterpriseRelatedMediaInline(admin.StackedInline): model = models.EnterpriseRelatedMedia form = EnterpriseRelatedMediaForm extra = 1 @admin.register(models.Enterprise) class EnterpriseAdmin(admin.ModelAdmin): inlines = [ EnterpriseRelatedMediaInline, ] admin.site.register(models.MediaTopic)
额外优化:预取现有关联数据
如果还需要优化已关联EnterpriseRelatedMedia的topic数据加载(避免编辑页面加载现有数据时的N+1),可以在EnterpriseAdmin中重写get_queryset添加预取:
@admin.register(models.Enterprise) class EnterpriseAdmin(admin.ModelAdmin): inlines = [ EnterpriseRelatedMediaInline, ] def get_queryset(self, request): return super().get_queryset(request).prefetch_related( 'related_medias__topic' )
内容的提问来源于stack exchange,提问作者Ives Furtado
相关产品推荐
相关产品推荐

