Skip to content

Commit 3ecb2d0

Browse files
authored
feat: new InfinitePaginator for large tables (#1169)
1 parent a56ecfe commit 3ecb2d0

File tree

6 files changed

+83
-22
lines changed

6 files changed

+83
-22
lines changed

docs/configuration/paginator.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: Paginator
3+
order: 4
4+
description: Learn how to optimize performance for large datasets in Django Unfold admin using the custom InfinitePaginator that avoids expensive COUNT operations.
5+
---
6+
7+
8+
# Paginator
9+
10+
Django Unfold provides a specialized paginator called `InfinitePaginator` designed specifically for handling large datasets efficiently. When working with tables containing millions of records, standard Django pagination can become slow due to expensive `COUNT` queries that calculate the total number of records.
11+
12+
## InfinitePaginator
13+
14+
The `InfinitePaginator` offers several advantages for large dataset management:
15+
16+
- Eliminates expensive `COUNT` operations on the database
17+
- Displays simplified navigation with only "Previous" and "Next" links
18+
- Removes the upper limit on page numbers
19+
- Significantly improves performance for very large tables
20+
21+
## Implementation
22+
23+
To use the `InfinitePaginator` in your admin interface, simply configure your ModelAdmin class as follows:
24+
25+
```python
26+
from unfold.admin import ModelAdmin
27+
from unfold.paginator import InfinitePaginator
28+
29+
30+
class YourAdmin(ModelAdmin):
31+
paginator = InfinitePaginator
32+
show_full_result_count = False
33+
```

src/unfold/paginator.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.core.paginator import Paginator
2+
from django.utils.functional import cached_property
3+
4+
5+
class InfinitePaginator(Paginator):
6+
template_name = "unfold/helpers/pagination_infinite.html"
7+
8+
@cached_property
9+
def count(self):
10+
return 9_999_999_999

src/unfold/templates/admin/pagination.html

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,12 @@
44
<div class="{% if not is_popup %}max-w-full lg:bottom-0 lg:fixed lg:left-0 lg:right-0{% endif %}" {% if not is_popup %}x-bind:class="{'xl:left-0': !sidebarDesktopOpen, 'xl:left-72': sidebarDesktopOpen}"{% endif %} x-bind:style="'width: ' + mainWidth + 'px'">
55
<div class="lg:backdrop-blur-sm lg:bg-white/80 lg:flex lg:items-center lg:dark:bg-base-900/80 {% if not is_popup %}lg:border-t lg:border-base-200 lg:h-[71px] lg:py-2 lg:relative lg:scrollable-top lg:px-8 lg:dark:border-base-800{% endif %}">
66
<div class="flex flex-row items-center {% if not cl.model_admin.list_fullwidth %}lg:mx-auto{% endif %}" x-bind:style="'width: ' + changeListWidth + 'px'">
7-
{% if pagination_required %}
8-
{% for i in page_range %}
9-
<div class="{% if forloop.last %}pr-2{% else %}pr-4{% endif %}">
10-
{% paginator_number cl i %}
11-
</div>
12-
{% endfor %}
7+
{% if cl.paginator.template_name %}
8+
{% include cl.paginator.template_name %}
9+
{% else %}
10+
{% include "unfold/helpers/pagination_default.html" %}
1311
{% endif %}
1412

15-
<div class="py-4">
16-
{% if pagination_required %}
17-
-
18-
{% endif %}
19-
20-
{{ cl.result_count }}
21-
22-
{% if cl.result_count == 1 %}
23-
{{ cl.opts.verbose_name }}
24-
{% else %}
25-
{{ cl.opts.verbose_name_plural }}
26-
{% endif %}
27-
</div>
28-
2913
{% if show_all_url %}
3014
<a href="{{ show_all_url }}" class="showall ml-4 text-primary-600 dark:text-primary-500">
3115
{% translate 'Show all' %}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% load unfold_list %}
2+
3+
{% if pagination_required %}
4+
{% for i in page_range %}
5+
<div class="{% if forloop.last %}pr-2{% else %}pr-4{% endif %}">
6+
{% paginator_number cl i %}
7+
</div>
8+
{% endfor %}
9+
{% endif %}
10+
11+
<div class="py-4">
12+
{% if pagination_required %}
13+
-
14+
{% endif %}
15+
16+
{{ cl.result_count }}
17+
18+
{% if cl.result_count == 1 %}
19+
{{ cl.opts.verbose_name }}
20+
{% else %}
21+
{{ cl.opts.verbose_name_plural }}
22+
{% endif %}
23+
</div>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% load unfold_list i18n %}
2+
3+
<div class="flex flex-row gap-4">
4+
<a {% if cl.page_num != 1 %}href="?p={{ cl.page_num|add:-1 }}"{% endif %} class="{% if cl.page_num != 1 %}hover:text-primary-600 dark:hover:text-primary-500{% endif %}">
5+
{% trans "Previous" %}
6+
</a>
7+
8+
<a href="?p={{ cl.page_num|add:1 }}" class="hover:text-primary-600 dark:hover:text-primary-500">
9+
{% trans "Next" %}
10+
</a>
11+
</div>

src/unfold/templates/unfold/helpers/welcomemsg.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% load i18n %}
1+
{% load unfold i18n %}
22

33
<div class="flex flex-row flex-grow font-semibold items-center min-w-0 mr-3">
44
<span class="cursor-pointer flex flex-row items-center">
@@ -23,7 +23,7 @@ <h1 class="overflow-hidden text-ellipsis text-sm whitespace-nowrap xl:text-base
2323
{{ content_title }}
2424
</span>
2525

26-
{% if cl and cl.full_result_count != cl.result_count %}
26+
{% if cl and cl.full_result_count != cl.result_count and cl.paginator|class_name != "InfinitePaginator" %}
2727
<span class="font-medium ml-2 text-font-subtle-light text-sm dark:text-font-subtle-dark">
2828
{% blocktranslate count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktranslate %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% if cl.show_full_result_count %}{% blocktranslate with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktranslate %}{% else %}{% translate "Show all" %}{% endif %}</a>)
2929
</span>

0 commit comments

Comments
 (0)