8000 Fixed #36175 -- Refactor admin pagination to make it reusable across … · django/django@50ba1d5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 50ba1d5

Browse files
committed
Fixed #36175 -- Refactor admin pagination to make it reusable across different queryset.
1 parent 1ba5fe1 commit 50ba1d5

File tree

17 files changed

+684
-93
lines changed

17 files changed

+684
-93
lines changed

django/contrib/admin/options.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,37 @@ def get_form(self, request, obj=None, change=False, **kwargs):
834834
% (e, self.__class__.__name__)
835835
)
836836

837+
def get_pagination_class(self, request):
838+
"""
839+
Return the Pagination class for use on pages where pagination is required.
840+
"""
841+
from django.contrib.admin.pagination import Pagination
842+
843+
return Pagination
844+
845+
def get_pagination_instance(
846+
self,
847+
request,
848+
model,
849+
queryset,
850+
list_per_page,
851+
list_max_show_all,
852+
model_admin,
853+
):
854+
"""
855+
Return an instance of the `Pagination` class to be
856+
used for paginating the queryset.
857+
"""
858+
Pagination = self.get_pagination_class(request)
859+
return Pagination(
860+
request,
861+
model,
862+
queryset,
863+
list_per_page,
864+
list_max_show_all,
865+
model_admin,
866+
)
867+
837868
def get_changelist(self, request, **kwargs):
838869
"""
839870
Return the ChangeList class for use on the changelist page.
@@ -2125,6 +2156,7 @@ def changelist_view(self, request, extra_context=None):
21252156
"is_popup": cl.is_popup,
21262157
"to_field": cl.to_field,
21272158
"cl": cl,
2159+
"pagination": cl.pagination,
21282160
"media": media,
21292161
"has_add_permission": self.has_add_permission(request),
21302162
"opts": cl.opts,
@@ -2232,7 +2264,7 @@ def _delete_view(self, request, object_id, extra_context):
22322264
def history_view(self, request, object_id, extra_context=None):
22332265
"The 'history' admin view for this model."
22342266
from django.contrib.admin.models import LogEntry
2235-
from django.contrib.admin.views.main import PAGE_VAR
2267+
from django.contrib.admin.pagination import PAGE_VAR
22362268

22372269
# First check if the user can see this history.
22382270
model = self.model
@@ -2255,20 +2287,24 @@ def history_view(self, request, object_id, extra_context=None):
22552287
.select_related()
22562288
.order_by("action_time")
22572289
)
2258-
2259-
paginator = self.get_paginator(request, action_list, 100)
2260-
page_number = request.GET.get(PAGE_VAR, 1)
2261-
page_obj = paginator.get_page(page_number)
2262-
page_range = paginator.get_elided_page_range(page_obj.number)
2290+
pagination = self.get_pagination_instance(
2291+
request,
2292+
LogEntry,
2293+
action_list,
2294+
self.list_per_page,
2295+
self.list_max_show_all,
2296+
self,
2297+
)
22632298

22642299
context = {
22652300
**self.admin_site.each_context(request),
22662301
"title": _("Change history: %s") % obj,
22672302
"subtitle": None,
2268-
"action_list": page_obj,
2269-
"page_range": page_range,
2303+
"action_list": pagination.get_objects(),
2304+
"page_range": pagination.page_range,
22702305
"page_var": PAGE_VAR,
2271-
"pagination_required": paginator.count > 100,
2306+
"pagination_required": pagination.paginator.count > 100,
2307+
"pagination": pagination,
22722308
"module_name": str(capfirst(self.opts.verbose_name_plural)),
22732309
"object": obj,
22742310
"opts": self.opts,

django/contrib/admin/pagination.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from django.contrib.admin.options import IncorrectLookupParameters
2+
from django.core.paginator import InvalidPage
3+
from django.template.defaulttags import querystring
4+
from django.utils.html import format_html, format_html_join
5+
from django.utils.safestring import mark_safe
6+
7+
ALL_VAR = "all"
8+
PAGE_VAR = "p"
9+
10+
11+
class Pagination:
12+
def __init__(
13+
self,
14+
request,
15+
model,
16+
queryset,
17+
list_per_page,
18+
list_max_show_all,
19+
model_admin,
20+
):
21+
self.model = model
22+
self.opts = model._meta
23+
self.list_per_page = list_per_page
24+
self.list_max_show_all = list_max_show_all
25+
self.model_admin = model_admin
26+
try:
27+
self.page_num = int(request.GET.get(PAGE_VAR, 1))
28+
except ValueError:
29+
self.page_num = 1
30+
self.show_all = ALL_VAR in request.GET
31+
self.filter_params = request.GET.copy()
32+
self.queryset = queryset
33+
self.setup(request)
34+
35+
@property
36+
def pagination_required(self):
37+
return self.multi_page and not (self.show_all and self.can_show_all)
38+
39+
@property
40+
def page_range(self):
41+
return (
42+
self.paginator.get_elided_page_range(self.page_num)
43+
if self.pagination_required
44+
else []
45+
)
46+
47+
@property
48+
def all_rendered_pages(self):
49+
"""
50+
HTML for all the pages in the pagination.
51+
"""
52+
return format_html_join(
53+
"",
54+
"{}",
55+
((self.render_page(i),) for i in self.page_range),
56+
)
57+
58+
@property
59+
def show_all_url(self):
60+
"""
61+
Return the query string to display all objects.
62+
"""
63+
if self.can_show_all and not self.show_all and self.multi_page:
64+
return querystring(None, self.filter_params, **{ALL_VAR: ""})
65+
66+
def setup(self, request):
67+
paginator = self.model_admin.get_paginator(
68+
request, self.queryset, self.list_per_page
69+
)
70+
result_count = paginator.count
71+
can_show_all = result_count <= self.list_max_show_all
72+
multi_page = result_count > self.list_per_page
73+
74+
self.result_count = result_count
75+
self.can_show_all = can_show_all
76+
self.multi_page = multi_page
77+
self.paginator = paginator
78+
79+
def render_page(self, i):
80+
"""
81+
Generate an individual page index link in a paginated list.
82+
"""
83+
if i == self.paginator.ELLIPSIS:
84+
return format_html("{} ", self.paginator.ELLIPSIS)
85+
if i == self.page_num:
86+
return format_html('<span class="this-page">{}</span> ', i)
87+
return format_html(
88+
'<a href="{}"{}>{}</a> ',
89+
querystring(None, self.filter_params, **{PAGE_VAR: i}),
90+
mark_safe(' class="end"' if i == self.paginator.num_pages else ""),
91+
i,
92+
)
93+
94+
def get_objects(self):
95+
if (self.show_all and self.can_show_all) or not self.multi_page:
96+
result_list = self.queryset._clone()
97+
else:
98+
try:
99+
result_list = self.paginator.page(self.page_num).object_list
100+
except InvalidPage:
101+
raise IncorrectLookupParameters
102+
return result_list
103+
104+
def pagination_context(self, **kwargs):
105+
"""
106+
Return context data for pagination rendering.
107+
"""
108+
return {
109+
"pagination": self,
110+
"pagination_required": self.pagination_required,
111+
"show_all_url": self.show_all_url,
112+
"ALL_VAR": ALL_VAR,
113+
"1": 1,
114+
**kwargs,
115+
}

django/contrib/admin/templates/admin/change_list.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
{% result_list cl %}
7070
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
7171
{% endblock %}
72-
{% block pagination %}{% pagination cl %}{% endblock %}
72+
{% block pagination %}{% change_list_pagination pagination cl=cl %}{% endblock %}
7373
</form>
7474
</div>
7575
{% block filters %}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{% extends "admin/pagination.html" %}
2+
{% load admin_list %}
3+
{% load i18n %}
4+
5+
{% block pagination_tools %}
6+
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% translate 'Save' %}">{% endif %}
7+
{% endblock %}

django/contrib/admin/templates/admin/object_history.html

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "admin/base_site.html" %}
2-
{% load i18n admin_urls %}
2+
{% load i18n admin_urls admin_list %}
33

44
{% block breadcrumbs %}
55
<div class="breadcrumbs">
@@ -34,20 +34,7 @@
3434
{% endfor %}
3535
</tbody>
3636
</table>
37-
<p class="paginator">
38-
{% if pagination_required %}
39-
{% for i in page_range %}
40-
{% if i == action_list.paginator.ELLIPSIS %}
41-
{{ action_list.paginator.ELLIPSIS }}
42-
{% elif i == action_list.number %}
43-
<span class="this-page">{{ i }}</span>
44-
{% else %}
45-
<a role="button" href="?{{ page_var }}={{ i }}" {% if i == action_list.paginator.num_pages %} class="end" {% endif %}>{{ i }}</a>
46-
{% endif %}
47-
{% endfor %}
48-
{% endif %}
49-
{{ action_list.paginator.count }} {% blocktranslate count counter=action_list.paginator.count %}entry{% plural %}entries{% endblocktranslate %}
50-
</p>
37+
{% block pagination %}{% pagination pagination %}{% endblock %}
5138
{% else %}
5239
<p>{% translate 'This object doesn’t have a change history. It probably wasn’t added via this admin site.' %}</p>
5340
{% endif %}

django/contrib/admin/templates/admin/pagination.html

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
{% load i18n %}
33
<p class="paginator">
44
{% if pagination_required %}
5-
{% for i in page_range %}
6-
{% paginator_number cl i %}
7-
{% endfor %}
5+
{{ pagination.all_rendered_pages }}
86
{% endif %}
9-
{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %}
7+
{{ pagination.result_count }} {% if pagination.result_count == 1 %}{{ pagination.opts.verbose_name }}{% else %}{{ pagination.opts.verbose_name_plural }}{% endif %}
108
{% if show_all_url %}<a href="{{ show_all_url }}" class="showall">{% translate 'Show all' %}</a>{% endif %}
11-
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% translate 'Save' %}">{% endif %}
9+
{% block pagination_tools %}{% endblock %}
1210
</p>

django/contrib/admin/templatetags/admin_list.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import datetime
2+
import warnings
23

4+
from django.contrib.admin.pagination import ALL_VAR, PAGE_VAR
35
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
46
from django.contrib.admin.utils import (
57
display_for_field,
@@ -9,11 +11,9 @@
911
lookup_field,
1012
)
1113
from django.contrib.admin.views.main import (
12-
ALL_VAR,
1314
IS_FACETS_VAR,
1415
IS_POPUP_VAR,
1516
ORDER_VAR,
16-
PAGE_VAR,
1717
SEARCH_VAR,
1818
)
1919
from django.core.exceptions import ObjectDoesNotExist
@@ -24,6 +24,7 @@
2424
from django.templatetags.static import static
2525
from django.urls import NoReverseMatch
2626
from django.utils import formats, timezone
27+
from django.utils.deprecation import RemovedInDjango70Warning
2728
from django.utils.html import format_html
2829
from django.utils.safestring import mark_safe
2930
from django.utils.text import capfirst
@@ -39,6 +40,14 @@ def paginator_number(cl, i):
3940
"""
4041
Generate an individual page index link in a paginated list.
4142
"""
43+
warnings.warn(
44+
"paginator_number is deprecated. "
45+
"use django.contrib.admin.pagination.Pagination.render_page instead. "
46+
"paginator_number template tag has been replaced by an method in the "
47+
"Pagination class.",
48+
RemovedInDjango70Warning,
49+
stacklevel=2,
50+
)
4251
if i == cl.paginator.ELLIPSIS:
4352
return format_html("{} ", cl.paginator.ELLIPSIS)
4453
elif i == cl.page_num:
@@ -56,6 +65,14 @@ def pagination(cl):
5665
"""
5766
Generate the series of links to the pages in a paginated list.
5867
"""
68+
warnings.warn(
69+
"pagination is deprecated. "
70+
"use django.contrib.admin.pagination.Pagination.pagination_context instead. "
71+
"Pagination now provides pagination_required, page_range, "
72+
"show_all_url(need_show_all_link) as attributes.",
73+
RemovedInDjango70Warning,
74+
stacklevel=2,
75+
)
5976
pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
6077
page_range = (
6178
cl.paginator.get_elided_page_range(cl.page_num) if pagination_required else []
@@ -76,12 +93,23 @@ def pagination_tag(parser, token):
7693
return InclusionAdminNode(
7794
parser,
7895
token,
79-
func=pagination,
96+
func=lambda pagination, **kwargs: pagination.pagination_context(**kwargs),
8097
template_name="pagination.html",
8198
takes_context=False,
8299
)
83100

84101

102+
@register.tag(name="change_list_pagination")
103+
def changelist_pagination_tag(parser, token):
104+
return InclusionAdminNode(
105+
parser,
106+
token,
107+
func=lambda pagination, **kwargs: pagination.pagination_context(**kwargs),
108+
template_name="change_list_pagination.html",
109+
takes_context=False,
110+
)
111+
112+
85113
def result_headers(cl):
86114
"""
87115
Generate the list column headers.

0 commit comments

Comments
 (0)
0