Skip to content

Commit 0c79cc4

Browse files
committed
Updated new pagination feature.
1 parent 55cc528 commit 0c79cc4

22 files changed

+301
-138
lines changed

base/__init__.py

Whitespace-only changes.

base/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class IncorectLookupParameter(Exception):
2+
"""
3+
Raised when a query parameter contains an incorrect value.
4+
"""
5+
6+
pass

base/migrations/__init__.py

Whitespace-only changes.

base/pagination.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from django.core.paginator import InvalidPage, Paginator
2+
3+
from .exceptions import IncorectLookupParameter
4+
5+
PAGE_VAR = "page"
6+
7+
8+
class Pagination:
9+
def __init__(
10+
self,
11+
request,
12+
model,
13+
queryset,
14+
list_per_page,
15+
):
16+
self.model = model
17+
self.opts = model._meta
18+
self.queryset = queryset
19+
self.list_per_page = list_per_page
20+
try:
21+
# Get the current page from the query string.
22+
self.page_num = int(request.GET.get(PAGE_VAR, 1))
23+
except ValueError:
24+
self.page_num = 1
25+
self.params = dict(request.GET.lists())
26+
self.setup()
27+
28+
@property
29+
def page_range(self):
30+
"""
31+
Returns the full range of pages.
32+
"""
33+
return (
34+
self.paginator.get_elided_page_range(self.page_num)
35+
if self.multi_page
36+
else []
37+
)
38+
39+
def setup(self):
40+
paginator = Paginator(self.queryset, self.list_per_page)
41+
result_count = paginator.count
42+
# Determine use pagination.
43+
multi_page = result_count > self.list_per_page
44+
45+
self.result_count = result_count
46+
self.multi_page = multi_page
47+
self.paginator = paginator
48+
self.page = paginator.get_page(self.page_num)
49+
50+
def get_objects(self):
51+
if not self.multi_page:
52+
result_list = self.queryset._clone()
53+
else:
54+
try:
55+
result_list = self.paginator.page(self.page_num).object_list
56+
except InvalidPage:
57+
raise IncorectLookupParameter
58+
return result_list

base/templatetags/__init__.py

Whitespace-only changes.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from collections.abc import Iterable, Mapping
2+
3+
from django import template
4+
from django.http import QueryDict
5+
from django.template.exceptions import TemplateSyntaxError
6+
7+
register = template.Library()
8+
9+
10+
# This template tag is scheduled to be added in Django 6.0.
11+
# Imported for use before the release of Django 6.0.
12+
@register.simple_tag(name="querystring", takes_context=True)
13+
def querystring(context, *args, **kwargs):
14+
"""
15+
Build a query string using `args` and `kwargs` arguments.
16+
17+
This tag constructs a new query string by adding, removing, or modifying
18+
parameters from the given positional and keyword arguments. Positional
19+
arguments must be mappings (such as `QueryDict` or `dict`), and
20+
`request.GET` is used as the starting point if `args` is empty.
21+
22+
Keyword arguments are treated as an extra, final mapping. These mappings
23+
are processed sequentially, with later arguments taking precedence.
24+
25+
A query string prefixed with `?` is returned.
26+
27+
Raise TemplateSyntaxError if a positional argument is not a mapping or if
28+
keys are not strings.
29+
30+
For example::
31+
32+
{# Set a parameter on top of `request.GET` #}
33+
{% querystring foo=3 %}
34+
35+
{# Remove a key from `request.GET` #}
36+
{% querystring foo=None %}
37+
38+
{# Use with pagination #}
39+
{% querystring page=page_obj.next_page_number %}
40+
41+
{# Use a custom ``QueryDict`` #}
42+
{% querystring my_query_dict foo=3 %}
43+
44+
{# Use multiple positional and keyword arguments #}
45+
{% querystring my_query_dict my_dict foo=3 bar=None %}
46+
"""
47+
if not args:
48+
args = [context.request.GET]
49+
params = QueryDict(mutable=True)
50+
for d in [*args, kwargs]:
51+
if not isinstance(d, Mapping):
52+
raise TemplateSyntaxError(
53+
"querystring requires mappings for positional arguments (got "
54+
"%r instead)." % d
55+
)
56+
for key, value in d.items():
57+
if not isinstance(key, str):
58+
raise TemplateSyntaxError(
59+
"querystring requires strings for mapping keys (got %r "
60+
"instead)." % key
61+
)
62+
if value is None:
63+
params.pop(key, None)
64+
elif isinstance(value, Iterable) and not isinstance(value, str):
65+
params.setlist(key, value)
66+
else:
67+
params[key] = value
68+
query_string = params.urlencode() if params else ""
69+
return f"?{query_string}"

base/templatetags/components.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from django import template
2+
from django.utils.html import format_html
3+
from django.utils.safestring import mark_safe
4+
5+
from base.pagination import PAGE_VAR
6+
from .base_templatetags import querystring
7+
8+
register = template.Library()
9+
10+
11+
@register.simple_tag
12+
def pagination_number(pagination, i):
13+
"""
14+
Generate an individual page index link in a paginated list.
15+
"""
16+
if i == pagination.paginator.ELLIPSIS:
17+
return format_html("{} ", pagination.paginator.ELLIPSIS)
18+
elif i == pagination.page_num:
19+
return format_html('<em class="current-page" aria-current="page">{}</em> ', i)
20+
else:
21+
link = querystring(None, pagination.params, {PAGE_VAR: i})
22+
return format_html(
23+
'<a href="{}" aria-label="page {}" {}>{}</a> ',
24+
link,
25+
i,
26+
mark_safe(' class="end"' if i == pagination.paginator.num_pages else ""),
27+
i,
28+
)
29+
30+
31+
@register.inclusion_tag("base/components/pagination.html", name="pagination")
32+
def pagination_tag(pagination):
33+
previous_page_link = f"?{PAGE_VAR}={pagination.page_num - 1}"
34+
next_page_link = f"?{PAGE_VAR}={pagination.page_num + 1}"
35+
return {
36+
"pagination": pagination,
37+
"previous_page_link": previous_page_link,
38+
"next_page_link": next_page_link,
39+
}

cab/utils.py

Lines changed: 21 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33
import bleach
44
from django.core.exceptions import ObjectDoesNotExist
5-
from django.core.paginator import InvalidPage, Paginator
65
from django.http import Http404, HttpResponse
76
from django.template import loader
87
from django.utils.safestring import mark_safe
98
from markdown import markdown as markdown_func
109

10+
from base.pagination import Pagination
11+
1112

1213
def object_list(
1314
request,
1415
queryset,
1516
paginate_by=None,
16-
page=None,
1717
allow_empty=True,
1818
template_name=None,
1919
template_loader=loader,
@@ -28,101 +28,43 @@ def object_list(
2828
Context:
2929
object_list
3030
list of objects
31-
is_paginated
32-
are the results paginated?
33-
results_per_page
34-
number of objects per page (if paginated)
35-
has_next
36-
is there a next page?
37-
has_previous
38-
is there a prev page?
39-
page
40-
the current page
41-
next
42-
the next page
43-
previous
44-
the previous page
45-
pages
46-
number of pages, total
31+
pagination
32+
This is a pagination object that holds attributes
33+
related to pagination.
34+
For more detail, please refer to the `base.pagination.Pagination` class.
4735
hits
4836
number of objects, total
49-
last_on_page
50-
the result number of the last of object in the
51-
object_list (1-indexed)
52-
first_on_page
53-
the result number of the first object in the
54-
object_list (1-indexed)
55-
page_range:
56-
A list of the page numbers (1-indexed).
5737
"""
5838
if extra_context is None:
5939
extra_context = {}
6040
queryset = queryset._clone()
41+
model = queryset.model
42+
opts = model._meta
6143
if paginate_by:
62-
paginator = Paginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
63-
if not page:
64-
page = request.GET.get("page", 1)
44+
pagination = Pagination(request, model, queryset, paginate_by)
45+
object_list = pagination.get_objects()
6546

66-
if page == "last":
67-
page_number = paginator.num_pages
68-
else:
69-
try:
70-
page_number = int(page)
71-
except ValueError:
72-
# Page is not 'last', nor can it be converted to an int.
73-
raise Http404
74-
try:
75-
page_obj = paginator.page(page_number)
76-
except InvalidPage:
77-
raise Http404
78-
try:
79-
next_page = page_obj.next_page_number()
80-
except InvalidPage:
81-
next_page = None
82-
try:
83-
previous_page = page_obj.previous_page_number()
84-
except InvalidPage:
85-
previous_page = None
86-
87-
c = {
88-
"%s_list" % template_object_name: page_obj.object_list,
89-
"paginator": paginator,
90-
"page_obj": page_obj,
91-
"is_paginated": page_obj.has_other_pages(),
92-
# Legacy template context stuff. New templates should use page_obj
93-
# to access this instead.
94-
"results_per_page": paginator.per_page,
95-
"has_next": page_obj.has_next(),
96-
"has_previous": page_obj.has_previous(),
97-
"page": page_obj.number,
98-
"next": next_page,
99-
"previous": previous_page,
100-
"first_on_page": page_obj.start_index(),
101-
"last_on_page": page_obj.end_index(),
102-
"pages": paginator.num_pages,
103-
"hits": paginator.count,
104-
"page_range": paginator.page_range,
47+
context = {
48+
"%s_list" % template_object_name: object_list,
49+
"pagination": pagination,
50+
"hits": pagination.result_count,
10551
}
10652
else:
107-
c = {
108-
"%s_list" % template_object_name: queryset,
109-
"paginator": None,
110-
"page_obj": None,
111-
"is_paginated": False,
53+
context = {
54+
"%s_list" % template_object_name: object_list,
11255
}
11356
if not allow_empty and len(queryset) == 0:
11457
raise Http404
11558

11659
for key, value in extra_context.items():
11760
if callable(value):
118-
c[key] = value()
61+
context[key] = value()
11962
else:
120-
c[key] = value
63+
context[key] = value
12164
if not template_name:
122-
model = queryset.model
123-
template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
124-
t = template_loader.get_template(template_name)
125-
return HttpResponse(t.render(c, request=request), content_type=content_type)
65+
template_name = "%s/%s_list.html" % (opts.app_label, opts.object_name.lower())
66+
template = template_loader.get_template(template_name)
67+
return HttpResponse(template.render(context, request=request), content_type=content_type)
12668

12769

12870
def object_detail(

djangosnippets/settings/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def user_url(user):
5656
"allauth.socialaccount.providers.bitbucket",
5757
"allauth.socialaccount.providers.github",
5858
"allauth.socialaccount.providers.twitter",
59+
"base",
5960
"cab",
6061
"comments_spamfighter",
6162
"ratings",

0 commit comments

Comments
 (0)