Django Pagination with Bootstrap
There are some great Django pagination examples out there but none of them quite worked for me, so here's another one. If you are first starting out, pagination is "the process of dividing a document into discrete pages, either electronic pages or printed pages."
From the official Django documentation, to Simple Is Better Than Complex, I was able to get the basics, but none worked perfectly. This Medium blog post by sumitlni has a great solution but I had some issues with it.
This is how I render my blog list page:
def saved_directory_path(instance, filename, root):
def blog_posts(request, subject=None):
"""Display all blog posts"""
if subject:
qs = BlogPost.objects.filter(subject__subject=subject, published=True)
else:
qs = BlogPost.objects.filter(published=True)
subjects = Subject.objects.all()
keywords = request.GET.get('q', None)
if keywords:
query = SearchQuery(keywords)
title_vector = SearchVector('title', weight='A')
tag_vector = SearchVector(StringAgg('tag__tag', delimiter=' '), weight='B')
content_vector = SearchVector('body', weight='C')
vectors = title_vector + tag_vector + content_vector
qs = qs.annotate(search=vectors).filter(search=query).distinct()
posts = qs.annotate(rank=SearchRank(vectors, query)).distinct().order_by('-rank')
else:
posts = qs.order_by('-created_date')
paginator = Paginator(posts, 20)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
return render(request, 'blog.html', {'page_obj': page_obj, 'keywords': keywords, 'subjects': subjects, })
Here, I set the paginator to 20 posts and get the requested page:
def saved_directory_path(instance, filename, root):
...
paginator = Paginator(posts, 20)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
return render(request, 'blog.html', {'page_obj': page_obj, 'keywords': keywords, 'subjects': subjects, })
Next, the required Bootstrap HTML. I am only showing the pagination if there is more than 1 page with
{% if page_obj.paginator.num_pages > 1 %}
and then I am loading the custom
pagination tags url_replace
and proper_paginate
that I will describe below
with {% load pagination %}
. The rest is semi-custom styling, so please ask any
questions if something doesn't make sense. Here is the completed Bootstrap pagination:
def saved_directory_path(instance, filename, root):
{% if page_obj.paginator.num_pages > 1 %}
{% load pagination %}
<ul class="mt-3 pagination justify-content-center">
{% if page_obj.number != 1 %}
<li><a class="page-link" href="?{% url_replace request 'page' 1 %}">⇤</a></li>
{% endif %}
{% if page_obj.has_previous %}
<li><a class="page-link"
href="?{% url_replace request 'page' page_obj.previous_page_number %}">«</a>
</li>
{% endif %}
{% for i in page_obj.paginator|proper_paginate:page_obj.number %}
{% if page_obj.number == i %}
<li class="page-item active"><a class="page-link"
href="?{% url_replace request 'page' i %}">{{ i }}</a>
</li>
{% else %}
<li class="page-item"><a class="page-link"
href="?{% url_replace request 'page' i %}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li><a class="page-link" href="?{% url_replace request 'page' page_obj.next_page_number %}">»</a>
</li>
{% endif %}
{% if page_obj.number != page_obj.paginator.num_pages %}
<li><a class="page-link"
href="?{% url_replace request 'page' page_obj.paginator.num_pages %}">⇥</a></li>
{% endif %}
</ul>
{% endif %}
Finally, the custom pagination tags as described by sumitlni "calculates the start and end indices" and "generates the proper URL based on which page number we wish to go to":
def saved_directory_path(instance, filename, root):
from django import template
register = template.Library()
@register.filter(name='proper_paginate')
def proper_paginate(paginator, current_page, neighbors=2):
if paginator.num_pages > 2 * neighbors:
start_index = max(1, current_page - neighbors)
end_index = min(paginator.num_pages, current_page + neighbors)
if end_index < start_index + 2 * neighbors:
end_index = start_index + 2 * neighbors
elif start_index > end_index - 2 * neighbors:
start_index = end_index - 2 * neighbors
if start_index < 1:
end_index -= start_index
start_index = 1
elif end_index > paginator.num_pages:
start_index -= (end_index - paginator.num_pages)
end_index = paginator.num_pages
page_list = [f for f in range(start_index, end_index + 1)]
return page_list[:(2 * neighbors + 1)]
return paginator.page_range
@register.simple_tag
def url_replace(request, field, value):
query_string = request.GET.copy()
query_string[field] = value
return query_string.urlencode()
The issues I had with the code and blog post above were:
1) I put them both in a single file called pagination.py
2) I didn't put it in a sub-directory named templatetags
Note, thanks to this question at Stack Overflow
After changing {% load filename %}
and
moving pagination.py
to a folder named templatetags
at the same level as
my apps models.py, it worked!
Comments
Post a Comment