在 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 时如何优化查询数量的主要内容,如果未能解决你的问题,请参考以下文章

django-select2 不能在 django-admin 中使用内联

在 django-admin 中更新记录的问题

Django使用admin管理后台

django-admin小试

Django Admin后台管理模块的使用

Django:在 django admin 中扩展 base.html