在 Django Admin 中使用 raw_id_fields 时如何优化查询数量
Posted
技术标签:
【中文标题】在 Django Admin 中使用 raw_id_fields 时如何优化查询数量【英文标题】:How to optimise number of queries when using raw_id_fields in Django Admin 【发布时间】:2017-04-14 18:21:40 【问题描述】:我有一个数据模型如下:
class Candidate(models.Model):
name = models.CharField()
class Skill(models.Model):
name = models.CharField()
class CandidateSkill(models.Model):
candidate = models.ForeignKey(Candidate)
skill = models.ForeignKey(Skill, related_name='candidate_skills')
proficiency = models.CharField()
在管理员中我有:
class CandidateSkillInline(admin.TabularInline):
model = CandidateSkill
fields = ('skill', )
extra = 0
raw_id_fields = ('skill',)
class CandidateAdmin(admin.ModelAdmin):
model = Candidate
fields = ('name',)
inlines = [CandidateSkillInline]
每个候选人都可以拥有许多技能。这里的问题是,在每个内联的更改页面中,将使用一个查询来获取技能 (SELECT ••• FROM "skill" WHERE "skill"."id" = <id>
)。如果我在CandidateSkillInline
中将skill
字段添加为read_only
,则不会有额外的查询。但是我希望能够在内联中添加新项目。我试过的东西:
1) 为CandidateSkillInline
添加了自定义表单集:
class CandidateSkillInlineFormset(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(CandidateSkillInlineFormset, self).__init__(*args, **kwargs)
self.queryset = self.queryset.select_related('skill')
2) 覆盖内联的get_queryset
:
def get_queryset(self, request):
super(CandidateSkillInline, self).get_queryset(request).select_related('skill')
3) 覆盖CandidateAdmin
上的get_queryset
:
def get_queryset(self, request):
return super(CandidateAdmin, self).get_queryset(request).prefetch_related('candidate_skills__skill')
但是,我仍然会收到每个技能的查询。不发送查询的唯一方法是当我在 CandidateSkillInilne 中的 read_only_fields
中设置 skill
时。问题是如何在一个查询中选择或预取技能,而不是为每个内联查询一个技能?
【问题讨论】:
请澄清一下。你说“每个候选人都可以有很多技能”,但这里建模的是多对多关系(没有明确使用 Django 的 ManyToManyField) @e4c5 我已更新模型以获得更多说明。我还需要为每个技能存储proficiency
,因此需要多对多
hm,我看到的唯一变化是名称字段被更改为 proficiency,这仍然使它成为许多 through
CandidateSkilll
添加through
后问题依然存在,渲染内联时发送单个查询以获取每个技能
这些额外的查询究竟出现在哪里?在列表页还是更改页?
【参考方案1】:
这似乎是您尝试实现自己的ManyToManyField
。您可以改用 ManyToManyField 和 inline 吗?它在管理员中有一个不错的多选小部件。
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models
【讨论】:
【参考方案2】:嗯,ForeignKeyRawIdWidget
的设计并不那么优雅。 ForeignKeyRawIdWidget
不只是以特定方式显示一些数据,这是小部件的基本职责,而是进行额外查询以在屏幕上显示更多相关信息(它显示 str(obj)
方法的值)。
查询在label_and_url_for_value
方法中执行。因此,您可以尝试使用自己的自定义小部件来避免此查询,但您必须考虑可视化中的权衡。
一种可能的解决方案:
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
from django.urls import reverse
class OptimisedForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
def label_and_url_for_value(self, value):
try:
url = reverse(
'%s:%s_%s_change' % (
self.admin_site.name,
self.rel.model._meta.app_label,
self.rel.model._meta.object_name.lower(),
),
args=(value,)
)
except NoReverseMatch:
url = '' # Admin not registered for target model.
return str(value), url
最后一步,您必须在 ModelForm
类中设置自定义小部件。有很多方法可以做到这一点。
【讨论】:
以上是关于在 Django Admin 中使用 raw_id_fields 时如何优化查询数量的主要内容,如果未能解决你的问题,请参考以下文章