Skip to content

Commit 347a55f

Browse files
committed
Updated new pagination feature.
1 parent 55cc528 commit 347a55f

File tree

16 files changed

+233
-110
lines changed

16 files changed

+233
-110
lines changed

cab/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

cab/pagination.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from django.core.paginator import InvalidPage, Paginator
2+
from django.utils.http import urlencode
3+
4+
from .exceptions import IncorectLookupParameter
5+
6+
PAGE_VAR = "page"
7+
8+
9+
class Pagination:
10+
def __init__(
11+
self,
12+
request,
13+
model,
14+
queryset,
15+
list_per_page,
16+
):
17+
self.model = model
18+
self.opts = model._meta
19+
self.queryset = queryset
20+
self.list_per_page = list_per_page
21+
try:
22+
# Get the current page from the query string.
23+
self.page_num = int(request.GET.get(PAGE_VAR, 1))
24+
except ValueError:
25+
self.page_num = 1
26+
self.params = dict(request.GET.lists())
27+
self.setup()
28+
29+
@property
30+
def page_range(self):
31+
return (
32+
self.paginator.get_elided_page_range(self.page_num)
33+
if self.multi_page
34+
else []
35+
)
36+
37+
def get_query_string(self, new_params=None):
38+
if new_params is None:
39+
new_params = {}
40+
params = self.params.copy()
41+
for param, value in new_params.items():
42+
if value is None:
43+
if param in params:
44+
del params[param]
45+
else:
46+
params[param] = value
47+
return "?%s" % urlencode(sorted(params.items()), doseq=True)
48+
49+
def setup(self):
50+
paginator = Paginator(self.queryset, self.list_per_page)
51+
result_count = paginator.count
52+
# Determine use pagination.
53+
multi_page = result_count > self.list_per_page
54+
55+
self.result_count = result_count
56+
self.multi_page = multi_page
57+
self.paginator = paginator
58+
self.page = paginator.get_page(self.page_num)
59+
60+
def get_objects(self):
61+
if not self.multi_page:
62+
result_list = self.queryset._clone()
63+
else:
64+
try:
65+
result_list = self.paginator.page(self.page_num).object_list
66+
except InvalidPage:
67+
raise IncorectLookupParameter
68+
return result_list
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% load components %}
2+
3+
{% if pagination.multi_page %}
4+
<nav class="pagination" aria-labelledby="pagination">
5+
{% if pagination.page.has_previous %}
6+
<a class="previous-page" href="{{ previous_page_link }}" rel="prev" aria-label="Previous page">Previous</a>
7+
{% else %}
8+
<span class="previous-page disabled" rel="prev" aria-label="Previous page">Previous</span>
9+
{% endif %}
10+
<ul>
11+
{% for i in pagination.page_range %}
12+
<li>{% pagination_number pagination i %}</li>
13+
{% endfor %}
14+
</ul>
15+
{% if pagination.page.has_next %}
16+
<a class="next-page" href="{{ next_page_link }}" rel="next" aria-label="Next page">Next</a>
17+
{% else %}
18+
<span class="next-page disabled" rel="next" aria-label="Next page">Next</span>
19+
{% endif %}
20+
</nav>
21+
{% endif %}

cab/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 cab.pagination import PAGE_VAR
6+
7+
register = template.Library()
8+
9+
10+
@register.simple_tag
11+
def pagination_number(pagination, i):
12+
"""
13+
Generate an individual page index link in a paginated list.
14+
"""
15+
if i == pagination.paginator.ELLIPSIS:
16+
return format_html("{} ", pagination.paginator.ELLIPSIS)
17+
elif i == pagination.page_num:
18+
return format_html('<em class="current-page" aria-current="page">{}</em> ', i)
19+
else:
20+
link = pagination.get_query_string({PAGE_VAR: i})
21+
return format_html(
22+
'<a href="{}" aria-label="page {}" {}>{}</a> ',
23+
link,
24+
i,
25+
mark_safe(' class="end"' if i == pagination.paginator.num_pages else ""),
26+
i,
27+
)
28+
29+
30+
@register.inclusion_tag("components/pagination.html", name="pagination")
31+
def pagination_tag(pagination):
32+
previous_page_link = f"?{PAGE_VAR}={pagination.page_num - 1}"
33+
next_page_link = f"?{PAGE_VAR}={pagination.page_num + 1}"
34+
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: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
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 cab.pagination import Pagination
11+
1112

1213
def object_list(
1314
request,
@@ -58,71 +59,32 @@ def object_list(
5859
if extra_context is None:
5960
extra_context = {}
6061
queryset = queryset._clone()
62+
model = queryset.model
6163
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)
64+
pagination = Pagination(request, model, queryset, paginate_by)
65+
object_list = pagination.get_objects()
6566

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,
67+
context = {
68+
"%s_list" % template_object_name: object_list,
69+
"pagination": pagination,
70+
"hits": pagination.result_count,
10571
}
10672
else:
107-
c = {
108-
"%s_list" % template_object_name: queryset,
109-
"paginator": None,
110-
"page_obj": None,
111-
"is_paginated": False,
73+
context = {
74+
"%s_list" % template_object_name: object_list,
11275
}
11376
if not allow_empty and len(queryset) == 0:
11477
raise Http404
11578

11679
for key, value in extra_context.items():
11780
if callable(value):
118-
c[key] = value()
81+
context[key] = value()
11982
else:
120-
c[key] = value
83+
context[key] = value
12184
if not template_name:
122-
model = queryset.model
12385
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)
86+
template = template_loader.get_template(template_name)
87+
return HttpResponse(template.render(context, request=request), content_type=content_type)
12688

12789

12890
def object_detail(

djangosnippets/static/scss/main.scss

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ body.with-sidebar {
317317
@include grid-column(4);
318318
}
319319
}
320-
.pagination, .count {
320+
.count {
321321
text-align: center;
322322
}
323323
}
@@ -358,6 +358,66 @@ body.simple {
358358
}
359359
}
360360

361+
nav.pagination {
362+
display: flex;
363+
justify-content: center;
364+
text-align: center;
365+
ul {
366+
margin-left: 1rem;
367+
margin-right: 1rem;
368+
}
369+
370+
li {
371+
display: inline-block;
372+
a, em, span {
373+
padding: 5px 10px;
374+
min-width: 32px;
375+
line-height: 20px;
376+
border: 1px solid transparent;
377+
border-radius: 6px;
378+
transition: border-color .2s cubic-bezier(0.3, 0, 0.5, 1);
379+
cursor: pointer;
380+
}
381+
a:hover {
382+
border-color: $secondary-color;
383+
text-decoration: none;
384+
}
385+
em {
386+
font-style: normal;
387+
cursor: default;
388+
}
389+
.current-page {
390+
font-weight: bold;
391+
color: white;
392+
background-color: $secondary-color;
393+
}
394+
.disabled {
395+
color: gray;
396+
cursor: default;
397+
border-color: transparent;
398+
}
399+
}
400+
401+
.previous-page::before, .next-page::after {
402+
display: inline-block;
403+
width: 1rem;
404+
height: 1rem;
405+
vertical-align: text-bottom;
406+
content: "";
407+
background-color: currentColor;
408+
}
409+
410+
.previous-page::before {
411+
clip-path: polygon(9.8px 12.8px, 8.7px 12.8px, 4.5px 8.5px, 4.5px 7.5px, 8.7px 3.2px, 9.8px 4.3px, 6.1px 8px, 9.8px 11.7px, 9.8px 12.8px);
412+
margin-right: 4px;
413+
}
414+
415+
.next-page::after {
416+
clip-path: polygon(6.2px 3.2px, 7.3px 3.2px, 11.5px 7.5px, 11.5px 8.5px, 7.3px 12.8px, 6.2px 11.7px, 9.9px 8px, 6.2px 4.3px, 6.2px 3.2px);
417+
margin-left: 4px;
418+
}
419+
}
420+
361421
footer {
362422
padding: 30px 0 30px 0;
363423
clear: both;

djangosnippets/templates/base.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@
3939
{% block secondary_nav %}
4040
<nav id="subnav">
4141
<ul>
42-
<li><a hx-get="{% url 'cab_top_authors' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click">By author</a></li>
43-
<li><a hx-get="{% url 'cab_language_list' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click" >By language</a></li>
44-
<li><a hx-get="{% url 'cab_top_tags' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click">By tag</a></li>
45-
<li><a hx-get="{% url 'cab_top_rated' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click">Highest rated</a></li>
46-
<li><a hx-get="{% url 'cab_top_bookmarked' %}" hx-target="#base-container" hx-swap="innerHTML" hx-trigger="click">Most bookmarked</a></li>
42+
<li><a href="{% url 'cab_top_authors' %}">By author</a></li>
43+
<li><a href="{% url 'cab_language_list' %}">By language</a></li>
44+
<li><a href="{% url 'cab_top_tags' %}">By tag</a></li>
45+
<li><a href="{% url 'cab_top_rated' %}">Highest rated</a></li>
46+
<li><a href="{% url 'cab_top_bookmarked' %}">Most bookmarked</a></li>
4747
</ul>
4848
</nav>
4949
{% endblock %}

djangosnippets/templates/cab/language_list.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "base.html" %}
2-
{% load core_tags %}
2+
{% load core_tags components %}
33

44
{% block head_title %}All languages{% endblock %}
55

@@ -12,6 +12,6 @@
1212
{% endfor %}
1313
</ul>
1414

15-
<p class="pagination">{% if has_previous %}<a href="?page={{ previous }}">&lt; Previous {{ results_per_page }}</a>{% endif %}&nbsp;&nbsp;{% if has_next %}<a href="?page={{ next }}">Next {{ results_per_page }} &gt;</a>{% endif %}</p>
15+
{% pagination pagination %}
1616

1717
{% endblock %}

djangosnippets/templates/cab/partials/language_list.html

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

33

44
<img class="bars" src="{% static 'img/bars.svg' %}"/>
@@ -10,6 +10,5 @@ <h1>All languages</h1>
1010
{% endfor %}
1111
</ul>
1212

13-
<p class="pagination">{% if has_previous %}<a href="{% url 'cab_language_list' %}?page={{ previous }}">&lt; Previous {{ results_per_page }}</a>{% endif %}&nbsp;&nbsp;{% if has_next %}<a href="{% url 'cab_language_list' %}?page={{ next }}">Next {{ results_per_page }} &gt;</a>{% endif %}</p>
14-
13+
{% pagination pagination %}
1514
</div>

djangosnippets/templates/cab/partials/most_bookmarked.html

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
{% load core_tags %}
2+
{% load core_tags components %}
33
{% load static %}
44

55
<h1>Most bookmarked snippets{% if months %} last {{ months }} months{% endif %}</h1>
@@ -30,15 +30,7 @@ <h1>Most bookmarked snippets{% if months %} last {{ months }} months{% endif %}<
3030
{% endfor %}
3131
</tbody>
3232
</table>
33-
<p class="pagination">
34-
{% if has_previous %}
35-
<a href="{% url 'cab_top_bookmarked' %}?page={{ previous }}{% if months %}&amp;months={{ months }}{% endif %}">&lt; Previous {{ results_per_page }}</a>
36-
{% endif %}
37-
&nbsp;&nbsp;
38-
{% if has_next %}
39-
<a href="{% url 'cab_top_bookmarked' %}?page={{ next }}{% if months %}&amp;months={{ months }}{% endif %}">Next {{ results_per_page }} &gt;</a>
40-
{% endif %}
41-
</p>
33+
{% pagination pagination %}
4234
<p class="count">{{ hits }} snippet{{ hits|pluralize }} posted so far.</p>
4335
{% else %}
4436
<p class="empty">No snippets posted yet.</p>

djangosnippets/templates/cab/partials/tag_list.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
{% load core_tags %}
2+
{% load core_tags components %}
33
<h1>All tags</h1>
44
<div id="content">
55
{% if object_list %}
@@ -9,7 +9,7 @@ <h1>All tags</h1>
99
{% endfor %}
1010
</ul>
1111

12-
<p class="pagination">{% if has_previous %}<a href="{% url 'cab_top_tags' %}?page={{ previous }}">&lt; Previous {{ results_per_page }}</a>{% endif %}&nbsp;&nbsp;{% if has_next %}<a href="{% url 'cab_top_tags' %}?page={{ next }}">Next {{ results_per_page }} &gt;</a>{% endif %}</p>
12+
{% pagination pagination %}
1313
{% else %}
1414
<p>No tags have been used yet.</p>
1515
{% endif %}

0 commit comments

Comments
 (0)