如何在Django Admin中使用raw_id_fields时优化查询数

时间:2016-11-30 12:17:31

标签: django django-queryset django-orm

我有一个数据模型如下:

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>)。如果我将skill中的字段CandidateSkillInline添加为read_only,则不会有额外的查询。但是,我希望能够在内联中添加新项目。我试过了:

1)向CandidateSkillInline添加了自定义formset:

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)覆盖get_queryset上的CandidateAdmin

def get_queryset(self, request):
    return super(CandidateAdmin, self).get_queryset(request).prefetch_related('candidate_skills__skill')

但是,我仍然可以查询每项技能。查询未发送的唯一方法是我在CandidateSkillInilne中的skill中设置read_only_fields。问题是我如何在一个查询中选择或预取技能,而不是每个内联一个?

2 个答案:

答案 0 :(得分:1)

这似乎是您尝试实现自己的ManyToManyField。你可以使用ManyToManyField和内联吗?它在管理员中有一个很好的多选小部件。

https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models

答案 1 :(得分:0)

嗯,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类中设置自定义窗口小部件。并且有很多方法可以做到这一点。