如何在Django的管理列表中覆盖queryset count()方法

7
为了避免耗时和昂贵的精确数据库计数查询,我想在Django管理员类中覆盖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类中访问原始查询集。有什么想法吗?我知道我可以通过get_changelist重写整个changelist视图。但那需要从Django的库中复制很多重复代码。


object_list在您的解决方案中未定义。return estimated_count是无法执行的,并且也未定义。 - radtek
4个回答

4

我可能是错的,但您能将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

3

这是我在使用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()

2

我之前做过类似的事情,所以我可以帮忙。

我定义了一个自定义的查询集类:

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()

好的解决方案,但我无法覆盖模型的对象管理器。我需要仅在Django的管理界面中应用此特殊计数方法。 - Simon Steinberger
这个答案中的代码并不是很有意义。return qs._clone(klass=ApproxCountQuerySet) 这段代码的作用是什么?klass 属性可能来自于外部且不可见的代码...但是这个问题/答案实际上是我自己代码/问题的原始版本。 - Simon Steinberger
1
我赞同你的建议,因为它可能确实是其他人的解决方案。 - Simon Steinberger

0

如果您正在尝试为大量数据加速管理页面,您可以定义一个模型.Admin的包装器,该包装器返回一个查询集,该查询集在结果未被过滤时仅使用近似计数而不是正常计数。这适用于Postgres 12和Django 4.1.7:

class FastCountAdmin(admin.ModelAdmin):
    class FastCountQuerySet(QuerySet):
        def count(self):
            """
            Override count queries (performed by Django ORM) to display approximate value.
            This will speed up count 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 = connection.cursor()
                cursor.execute("SELECT reltuples FROM pg_class WHERE relname = %s", 
                    [self.model._meta.db_table])
                return int(cursor.fetchone()[0])
            else:
                return self.query.get_count(using=self.db)

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        return FastCountAdmin.FastCountQuerySet(qs.model, using=qs.db)

然后你可以像这样使用它:

class MyHugeModelAdmin(FastCountAdmin):
    model = MyHugeModel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接