如何强制 Django Admin 使用 select_related?

Posted

技术标签:

【中文标题】如何强制 Django Admin 使用 select_related?【英文标题】:How to force Django Admin to use select_related? 【发布时间】:2011-10-17 02:23:48 【问题描述】:

我的一个模型特别复杂。当我尝试在 Django Admin 中对其进行编辑时,它会执行 1042 次查询并需要 9 秒以上的时间来处理。

我知道我可以用raw_id_fields 替换一些下拉菜单,但我认为更大的瓶颈是它没有按照应有的方式执行select_related()

我可以让管理站点执行此操作吗?

【问题讨论】:

【参考方案1】:

虽然 jimbob 博士的回答是有道理的,但根据我的需要,我能够简单地用单行重写 get_queryset() 方法,甚至选择外键的外键。也许这对某人有帮助。

class MyModelAdmin(admin.ModelAdmin):
    model = MyModel
    ...
    def get_queryset(self, request):
        return super(MyModelAdmin, self).get_queryset(request).select_related(
            'foreign_key1', 'foreign_key2__fk2_foreign_key')

【讨论】:

查询集已重命名为get_queryset 这在 change 页面上似乎不起作用。即,表单仍在进行数千次查询以获取相关实例。只是我,还是这对其他人有用? django==1.11.3 它给出了这个错误:super(type, obj): obj must be an instance or subtype of type【参考方案2】:

你可以试试这个

class Foo(admin.ModelAdmin):
    list_select_related = (
        'foreign_key1',
        'foreign_key2',
    )

https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_select_related

【讨论】:

不幸的是,我认为这不能解决原始查询。 list_select_related 属性用于管理 listing 页面,而不是对象 edit 页面。当然,根据我遇到同样问题的经验,设置列表相关设置不会加快编辑页面,只有列表和选择页面。【参考方案3】:

对于我的特定模型,特别慢的方面是当 ForeignKeys 显示在表单中时,它们不是使用 select_related 调用的,所以这就是我要加快速度的部分。

查看相关的django源码,你在django/contrib/admin/options.py看到方法formfield_for_foreignkeys接受每个FK db_field并调用ForeignKey类的formfield方法,该方法定义在django/db/models/领域/相关/喜欢:

def formfield(self, **kwargs):
    db = kwargs.pop('using', None)
    defaults = 
        'form_class': forms.ModelChoiceField,
        'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
        'to_field_name': self.rel.field_name,
    
    defaults.update(kwargs)
    return super(ForeignKey, self).formfield(**defaults)

由此,我们看看是否为db_field 提供kwargs['queryset'],我们可以定义一个将使用select_related 的自定义查询集(这可以由formfield_for_foreignkey 提供)。

所以基本上我们想要做的就是用SelectRelatedModelAdmin 覆盖admin.ModelAdmin,然后将我们的ModelAdmin 子类设为SelectRelatedModelAdmin 而不是admin.ModelAdmin

class SelectRelatedModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if 'queryset' in kwargs:
            kwargs['queryset'] = kwargs['queryset'].select_related()
        else:
            db = kwargs.pop('using', None)
            kwargs['queryset'] = db_field.rel.to._default_manager.using(db).complex_filter(db_field.rel.limit_choices_to).select_related()
        return super(SelectRelatedModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

此代码示例不包括 admin Inlines 或 ManyToManyFields,或由 readonly_fields 或自定义 select_related 查询调用的函数中的 foreign_key 遍历,但类似的方法应该适用于这些情况。

【讨论】:

这可以通过get_field_queryset 来简化(尽管它没有记录,因此将来可能会更改)。【参考方案4】:

在 Django 2.0+ 中,提高 ForeignKey 和 ManyToMany 关系性能的一个好方法是使用autocomplete fields。

这些字段不会显示所有相关对象,因此加载的查询要少得多。

【讨论】:

【参考方案5】:

对于管理员编辑/更改特定项目页面,外键选择框可能需要很长时间才能加载,以改变 django 查询外键数据的方式:

Django docs on Using formfield_for_foreignkey

假设我的Example 模型上有一个名为foo 的字段,我希望选择相关的bar 对象:

class ExampleAdmin(admin.ModelAdmin):

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
            if db_field.name == "foo":
                kwargs["queryset"] = Example.objects.select_related('bar')
            return super().formfield_for_foreignkey(db_field, request, **kwargs)

【讨论】:

【参考方案6】:

为了完整起见,我想添加另一个最适合我的用例的选项。

正如其他人所指出的,问题通常是为选择框加载数据。 list_select_related 在这种情况下没有帮助。

如果您实际上不想通过管理员编辑外键字段,最简单的解决方法是将相应字段设为只读:

class Foo(admin.ModelAdmin):
    readonly_fields = ('foreign_key_field1','foreign_key_field2',)

您仍然可以显示这些字段,根本不会有选择框,因此 Django 不需要从数据库中检索所有选择框选项。

【讨论】:

以上是关于如何强制 Django Admin 使用 select_related?的主要内容,如果未能解决你的问题,请参考以下文章

如果项目文件夹已经存在,则强制 django-admin startproject

强制转换为 Unicode:需要字符串或缓冲区,在 django admin 中渲染时发现 NoneType

Django Admin Cookbook-18如何限制对Django Admin管理部分功能的使用

如何将 django-admin-bootstrapped 与 django 1.10 集成

我如何告诉 django-admin 使用哪个设置模块?

Django Admin Cookbook-8如何在Django admin中优化查询