【问题标题】:How to optimise number of queries when using raw_id_fields in Django Admin在 Django Admin 中使用 raw_id_fields 时如何优化查询数量
【发布时间】: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后问题依然存在,渲染内联时发送单个查询以获取每个技能
  • 这些额外的查询究竟出现在哪里?在列表页还是更改页?

标签: django django-queryset django-orm


【解决方案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 类中设置自定义小部件。有很多方法可以做到这一点。

    【讨论】:

      猜你喜欢
      • 2011-01-07
      • 2011-09-01
      • 2016-06-06
      • 1970-01-01
      • 2021-08-24
      • 1970-01-01
      • 2017-05-25
      • 2022-01-08
      • 2018-11-08
      相关资源
      最近更新 更多