【问题标题】:How to override queryset count() method in Django's admin list如何覆盖 Django 管理列表中的 queryset count() 方法
【发布时间】:2017-01-04 15:42:11
【问题描述】:

为了避免耗时且昂贵的精确数据库计数查询,我想在 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 无法访问,因此永远不会执行,而且也未定义。

标签: python django count admin


【解决方案1】:

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

【讨论】:

    【解决方案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 的管理界面中应用这个特殊的计数方法。
    • 这个答案中的代码实际上并没有什么意义。 return qs._clone(klass=ApproxCountQuerySet) 应该做什么? klass 属性可能来自外部且不可见的代码......但这个问题/答案实际上是我自己的代码/问题的原件。
    • 赞成您的建议,因为它肯定是其他人的解决方案。
    【解决方案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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-23
      • 2013-02-18
      • 1970-01-01
      • 2021-07-04
      • 1970-01-01
      • 2022-11-10
      • 2016-03-12
      • 2014-12-17
      相关资源
      最近更新 更多