Skip to content

Instantly share code, notes, and snippets.

@adinhodovic
Forked from noviluni/admin.py
Last active May 30, 2024 09:48
Show Gist options
  • Save adinhodovic/f27f6c466900086a914e113e1d41031c to your computer and use it in GitHub Desktop.
Save adinhodovic/f27f6c466900086a914e113e1d41031c to your computer and use it in GitHub Desktop.
Large Table Paginator for Django: Scale Django admin pages and avoid timeouts.
from django.contrib.admin import ModelAdmin
from .paginator import LargeTablePaginator
class MyTableAdmin(ModelAdmin):
...
large_table_paginator = True
...
def get_paginator( # pylint: disable=too-many-arguments
self,
request,
queryset,
per_page,
orphans=0,
allow_empty_first_page=True,
):
# Always show count locally
if self.large_table_paginator and not settings.DEBUG:
return LargeTablePaginator(
queryset, per_page, orphans, allow_empty_first_page
)
return self.paginator(queryset, per_page, orphans, allow_empty_first_page)
from django.core.paginator import Paginator
from django.db import connection, transaction, OperationalError
from django.utils.functional import cached_property
class LargeTablePaginator(Paginator):
"""
# https://gist.github.com/noviluni/d86adfa24843c7b8ed10c183a9df2afe
Overrides the count method of QuerySet objects to avoid timeouts.
- Get an estimate instead of actual count when not filtered (this estimate can be stale and hence not fit for
situations where the count of objects actually matter).
- If any other exception occured fall back to default behaviour.
"""
@cached_property
def count(self):
"""
Returns an estimated number of objects, across all pages.
"""
if not self.object_list.query.where: # type: ignore
try:
with connection.cursor() as cursor:
# Obtain estimated values (only valid with PostgreSQL)
cursor.execute(
"SELECT reltuples FROM pg_class WHERE relname = %s",
[self.object_list.query.model._meta.db_table], # type: ignore
)
estimate = int(cursor.fetchone()[0])
return estimate
except Exception: # pylint: disable=broad-except
# If any other exception occurred fall back to default behaviour
pass
return super().count
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment