如何覆盖 Django 管理列表中的查询集 count() 方法

How to override queryset count() method in Django's admin list

为了避免耗时且昂贵的精确数据库计数查询,我想重写 Django 管理 class 中的 count() 方法,如下所示:

from django.contrib import admin
from django.db import connection

class CountProxy:
    def __call__(self):
        # how to access the queryset `query` here?
        query = ...

        try:
            if not query.where:
                cursor = connection.cursor()
                cursor.execute("SELECT reltuples FROM pg_class WHERE relname = %s", [query.model._meta.db_table])
                n = int(cursor.fetchone()[0])
                if n >= 1000: return n # exact count for small tables
            return object_list.count()
        except:
            # exception for lists
            return len(object_list)
        return estimated_count

class MyAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super(MyAdmin, self).get_queryset(request)
        qs.count = CountProxy()
        return qs

但我不知道如何访问我的 CountProxy class 中的原始查询集。任何想法?我知道我可以通过 get_changelist 覆盖整个 changelist 视图。但这涉及到 Django 的 repo 中的大量重复代码。

我以前做过类似的事情,所以我可以提供帮助。

我定义了一个自定义查询集class:

class MyQuerySet(QuerySet):

    def count(self):
        """
        Override count queries (performed by Django ORM) to display approximate value.
        This will speed the admin interface.

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

        query = self.query
        if not (query.group_by or query.having or query.distinct):
            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)

然后定义自定义模型管理器:

class MyManager(models.Manager):

    def get_query_set(self):
        return MyQuerySet(self.model)

然后在我的模型中使用它:

class MyModel(models.Model):
    objects = MyManager()

我可能是错的,但是你可以将 qs 作为 CountProxy 的实例属性传递吗?

class CountProxy:
    def __init__(self, query):
        self.query = query

    def __call__(self):
        # you've already had the query here, do something with self.query

class MyAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super(MyAdmin, self).get_queryset(request)
        qs.count = CountProxy(qs)
        return qs

这对我来说对 postgres 和 Django 2 有用。2.x

from django.db.models.query import QuerySet
from django.db import connection

class FastCountQuerySet(QuerySet):
    """
    Fast count (estimate) queryset to speedup count
    """
    def count(self):
        """
        Override count queries (performed by Django ORM) to display approximate value.
        This will speed up count i.e. in the admin interface.
        """
        if self._result_cache is not None:
            return len(self._result_cache)

        query = self.query
        if not (query.group_by or query.where or query.distinct):
            # cursor = connections[self.db].cursor()
            cursor = connection.cursor()
            cursor.execute("SELECT reltuples FROM pg_class WHERE relname = %s", [self.query.model._meta.db_table])
            n = int(cursor.fetchone()[0])
            if n >= 1000:
                return n  # exact count for small tables
            else:
                return self.query.get_count(using=self.db)
        else:
            return self.query.get_count(using=self.db)


class CustomManager(models.Manager):
    """
    Custom db manager
    """
    def get_queryset(self):
        return FastCountQuerySet(self.model)

最后覆盖你的模型管理器:

class YourModel(models.Model):
    objects = CustomManager()