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

ojsjcaue  于 2023-04-22  发布在  Go
关注(0)|答案(4)|浏览(120)

为了避免耗时和昂贵的精确数据库计数查询,我想在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仓库代码。

tzdcorbm

tzdcorbm1#

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

lstz6jyr2#

我以前做过类似的事情,所以我可以帮助你。
我定义了一个自定义的queryset类:

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

ymdaylpp3#

这就是我在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()
h5qlskok

h5qlskok4#

如果你想让管理页面在大计数时更快,你可以定义一个模型的 Package 器。Admin作为queryset返回一个queryset,而不是正常的计数,只有当结果没有被过滤时才使用一个近似值。这适用于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

相关问题