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

Question

In order to avoid time consuming and costly exact database count queries, I'd like to override the count() method inside a Django admin class like so:

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

But I don#t know how to access the original queryset within my CountProxy class. Any idea? I know I can overwrite the whole changelist view through get_changelist. But that involves a lot of duplicating code from Django's repo.


Show source
| python   | django   | count   | admin   2017-01-04 16:01 2 Answers

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

  1. 2017-01-04 16:01

    I did something similar before so I can help.

    I defined a custom queryset 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)
    

    Then defined a custom model manager:

    class MyManager(models.Manager):
    
        def get_query_set(self):
            return MyQuerySet(self.model)
    

    Then used it in my model:

    class MyModel(models.Model):
        objects = MyManager()
    
  2. 2017-01-04 16:01

    I could be wrong, but could you pass qs as an instance attribute for 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
    

Leave a reply to - How to override queryset count() method in Django's admin list

◀ Go back