Search code examples
djangoperformanceadmincode-snippetspaginator

Faster Django Admin Paginator: Cannot get this Django snippet to work


I found this snippet for improving the performance of large database table queries in Django admin lists:

https://djangosnippets.org/snippets/2593/

There are some issues about it when using it with Django 1.10, which are already discussed in my previous question here:

How to speed up Django's admin pages with PostgreSQL count estimates?

Particularly, _count needs to renamed to count and query_set to queryset. Here's a short version of the relevant part of the snippet:

from django.core.paginator import Paginator
from django.core.cache import cache
class FasterAdminPaginator(Paginator):
    def _get_count(self):
        if self.count is None:
            try:
                key = "adm:{0}:count".format( hash(self.object_list.query.__str__()) )
                self.count = cache.get(key, -1);
                if self.count == -1 :
                    if not self.object_list.query.where:
                        # estimates COUNT: https://djangosnippets.org/snippets/2593/
                        cursor = connection.cursor()
                        cursor.execute("SELECT reltuples FROM pg_class WHERE relname = %s",
                            [self.object_list.query.model._meta.db_table])
                        self.count = int(cursor.fetchone()[0])
                    else :
                        self.count = self.object_list.count()
                    cache.set(key, self.count, 3600)
            except:
                # AttributeError if object_list has no count() method.
                self.count = len(self.object_list)
        return self.count
    count = property(_get_count)

Problem is, I still cannot get it to work. Current error log excerpt:

maximum recursion depth exceeded while calling a Python object
...
result_count = paginator.count 
...
if self.count is None:

No idea how to get the snippet working.


Solution

  • I think we can drag that paginator into the django 1.10 world (hopefullly not kicking and screaming) like this:

    from django.core.paginator import Paginator
    from django.core.cache import cache
    from django.utils.functional import cached_property
    from django.db import connection
    
    class FasterAdminPaginator(Paginator):
        @cached_property
        def count(self):
            try:
                if not self.object_list.query.where:
                    # estimates COUNT: https://djangosnippets.org/snippets/2593/
                    cursor = connection.cursor()
                    cursor.execute("SELECT reltuples FROM pg_class WHERE relname = %s",
                        [self.object_list.query.model._meta.db_table])
                    print 'Using the reltuples'
    
                    ret = int(cursor.fetchone()[0])
                else :
                    return self.object_list.count()
            except :
                import traceback
                traceback.print_exc()
                # AttributeError if object_list has no count() method.
                return len(self.object_list)
    

    Thakns to cached_property the extensive caching code used in the original snippets are no longer needed. For completeness, this is what the relevent section of django.core.paginator.Paginator looks like

    @cached_property
    def count(self):
        """
        Returns the total number of objects, across all pages.
        """
        try:
            return self.object_list.count()
        except (AttributeError, TypeError):
            # AttributeError if object_list has no count() method.
            # TypeError if object_list.count() requires arguments
            # (i.e. is of type list).
            return len(self.object_list)