Created
September 12, 2024 03:59
-
-
Save pirate/7877ada8370c516ce01b73c1385f018c to your computer and use it in GitHub Desktop.
A faster version of the default Django Paginator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is an improved Django Paginator that makes Admin list view pages load much faster. | |
# You can tell if you need this if you see a big slow COUNT() query | |
# in the django_debug_toolbar SQL panel on every admin list view. | |
# It improves performance by avoiding running a potentially costly DISTINCT query to | |
# count total rows on every pageload. Instead, when possible (when there are no filters) | |
# it does a significantly simpler SELECT COUNT(*) tablename to get the total. | |
from django.core.paginator import Paginator | |
from django.utils.functional import cached_property | |
class AccelleratedPaginator(Paginator): | |
""" | |
Accellerated Pagniator ignores DISTINCT when counting total number of rows. | |
Speeds up SELECT Count(*) on Admin views by >20x. | |
https://hakibenita.com/optimizing-the-django-admin-paginator | |
""" | |
@cached_property | |
def count(self): | |
if self.object_list._has_filters(): | |
# fallback to normal count method on filtered queryset | |
return super().count | |
# otherwise count total rows in a separate fast query | |
return self.object_list.model.objects.count() | |
# Alternative approach for PostgreSQL: fallback if count takes > 200ms | |
# from django.db import connection, transaction, OperationalError | |
# with transaction.atomic(), connection.cursor() as cursor: | |
# cursor.execute('SET LOCAL statement_timeout TO 200;') | |
# try: | |
# # try potentially slow count query, gets interrupted if it takes too long | |
# return super().count | |
# except OperationalError: | |
# # fallback to a faster but less accurate count (just returns total object of this type, without any costly filters) | |
# return self.object_list.model.objects.count() | |
# USAGE: | |
from django.contrib import admin | |
@admin.register(SomeModel) | |
class SomeModelAdmin(admin.ModelAdmin): | |
... | |
paginator = AccelleratedPaginator |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment