阻止django admin在列表表单上运行SELECT COUNT(*)

Nov*_*ova 22 django django-models django-admin

每次我使用Admin列出模型的条目时,Admin都会计算表中的行数.更糟糕的是,即使您过滤查询,它似乎也是如此.

例如,如果我只想显示id为123,456,789的模型,我可以这样做:

/admin/myapp/mymodel/?id__in=123,456,789
Run Code Online (Sandbox Code Playgroud)

但查询运行(以及其他)是:

SELECT COUNT(*) FROM `myapp_mymodel` WHERE `myapp_mymodel`.`id` IN (123, 456, 789) # okay
SELECT COUNT(*) FROM `myapp_mymodel` # why???
Run Code Online (Sandbox Code Playgroud)

哪个杀了mysql + innodb.似乎这个问题在这个故障单中被部分承认,但我的问题似乎更具体,因为它计算所有行,即使它不应该.

有没有办法禁用全局行数?

注意:我使用的是django 1.2.7.

Col*_*son 22

Django 1.8允许你通过设置禁用它show_full_result_count = False.

https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.show_full_result_count

  • 这仅适用于已过滤的查询集.如果您在没有过滤器的情况下点击管理员,它仍会生成使用SELECT COUNT(*)... (20认同)

Nov*_*ova 20

好的,我想我找到了解决方案.正如彼得建议,最好的办法是对工作的count性质,它可以通过自定义查询设置(如图覆盖它做 这个职位),专门用一个近似等价计数:

from django.db import connections, models
from django.db.models.query import QuerySet

class ApproxCountQuerySet(QuerySet):
    """Counting all rows is very expensive on large Innodb tables. This
    is a replacement for QuerySet that returns an approximation if count()
    is called with no additional constraints. In all other cases it should
    behave exactly as QuerySet.

    Only works with MySQL. Behaves normally for all other engines.
    """

    def count(self):
        # Code from django/db/models/query.py

        if self._result_cache is not None and not self._iter:
            return len(self._result_cache)

        is_mysql = 'mysql' in connections[self.db].client.executable_name.lower()

        query = self.query
        if (is_mysql and not query.where and
                query.high_mark is None and
                query.low_mark == 0 and
                not query.select and
                not query.group_by and
                not query.having and
                not query.distinct):
            # If query has no constraints, we would be simply doing
            # "SELECT COUNT(*) FROM foo". Monkey patch so the we
            # get an approximation instead.
            cursor = connections[self.db].cursor()
            cursor.execute("SHOW TABLE STATUS LIKE %s",
                    (self.model._meta.db_table,))
            return cursor.fetchall()[0][4]
        else:
            return self.query.get_count(using=self.db)
Run Code Online (Sandbox Code Playgroud)

然后在管理员:

class MyAdmin(admin.ModelAdmin):

    def queryset(self, request):
        qs = super(MyAdmin, self).queryset(request)
        return qs._clone(klass=ApproxCountQuerySet)
Run Code Online (Sandbox Code Playgroud)

近似函数可能会在页码100000上搞乱,但对我的情况来说已经足够了.

  • 谢谢.稍微调整一下,这在PostgreSQL中也适用于我,虽然我不得不使用查询`SELECT reltuples :: int FROM pg_class WHERE oid ='%s':: regclass;` (2认同)
  • 我认为对于现代 Django,您可以使用“def get_queryset”(如 Paweł Krzyżaniak 在另一个答案中所述),并将“return qs._clone(klass=ApproxCountQuerySet)”替换为“return qs._chain(__class__= ApproxCountQuerySet)”。 (2认同)

Woo*_*son 7

我发现Nova的答案非常有用,但我使用了postgres.我稍微修改它以适用于postgres,稍微改变一下来处理表名称空间,稍微不同的"检测postgres"逻辑.

这是pg版本.

class ApproxCountPgQuerySet(models.query.QuerySet):
  """approximate unconstrained count(*) with reltuples from pg_class"""

  def count(self):
      if self._result_cache is not None and not self._iter:
          return len(self._result_cache)

      if hasattr(connections[self.db].client.connection, 'pg_version'):
          query = self.query
          if (not query.where and query.high_mark is None and query.low_mark == 0 and
              not query.select and not query.group_by and not query.having and not query.distinct):
              # If query has no constraints, we would be simply doing
              # "SELECT COUNT(*) FROM foo". Monkey patch so the we get an approximation instead.
              parts = [p.strip('"') for p in self.model._meta.db_table.split('.')]
              cursor = connections[self.db].cursor()
              if len(parts) == 1:
                  cursor.execute("select reltuples::bigint FROM pg_class WHERE relname = %s", parts)
              else:
                  cursor.execute("select reltuples::bigint FROM pg_class c JOIN pg_namespace n on (c.relnamespace = n.oid) WHERE n.nspname = %s AND c.relname = %s", parts)
          return cursor.fetchall()[0][0]
      return self.query.get_count(using=self.db)
Run Code Online (Sandbox Code Playgroud)

  • 返回光标应该缩进 (2认同)

小智 5

Nova 的解决方案 (ApproxCountQuerySet) 效果很好,但是在较新版本的 Django queryset 方法中被 get_queryset 替换,所以现在应该是:

class MyAdmin(admin.ModelAdmin):

    def get_queryset(self, request):
        qs = super(MyAdmin, self).get_queryset(request)
        return qs._clone(klass=ApproxCountQuerySet)
Run Code Online (Sandbox Code Playgroud)