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

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

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

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

【问题讨论】:

    标签: django django-models django-admin django-orm


    【解决方案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 不需要从数据库中检索所有选择框选项。

        【讨论】:

          猜你喜欢
          • 2016-01-18
          • 2011-10-01
          • 2015-02-02
          • 2016-12-06
          • 1970-01-01
          • 2018-09-13
          • 2011-10-31
          • 1970-01-01
          • 2011-10-01
          相关资源
          最近更新 更多