For some time now, I started doing all kinds of things with Django, ideas that you can check on this Blog and on my YouTube channel, and I realized that some basic elements are missing that we still have to see, that cannot be other than pagination.
To paginate in Django we can do it with a simple class called Paginator, as you can see in the examples in this publication, the trick here is to know that there are two types of views in Django, the generic ones, using the ListView for this particular case, which Of course, we can also paginate them, and the personalized views, which is what we are going to see in this post.
View and template side pagination
We're going to paginate on both sides, on one side, the client or the web page, display the listing, set it up pretty, with CSS with Bootstrap in this case, although you can use custom CSS, Bulma, Tailwind, or whatever you work with.
We are going to learn how we can create a fairly generic and reusable simple pagination system for our listing.
This is a fairly common practice nowadays that we want to paginate a list or such of records, such as post publications, a list of products for an online store, etc.
Pagination in Django in any view
Following our example, we are going to create a simple pagination system for comments; For this we are going to continue with the code that we showed previously:
Pagination in Django
In Django we have a class called Paginator which receives two arguments, the list of all records, and the pagination level, that is, if you want a page with 5 records (page size 5) 10, etc.
As you can see, we also validate that we receive a parameter via get to indicate which page we want to see, in this case we are going to use the parameter that travels via get, called page, but it can have any name; remember to put some validation to avoid problems and to handle a corresponding range:
page_number = request.GET.get('page')
So, with Django's Paginator class, we pass it the list we want to paginate and the pagination level; the rest of the code remains the same as the previous entry:
comments = Comment.objects.all()
paginator = Paginator(comments, 5) # Show 25 contacts per page.
page_number = request.GET.get('page')
comments_page = paginator.get_page(page_number)
return render(request,'index.html',{'comments_page': comments_page})
Generate the links or links for the pagination
The main code would be exactly the same as the one used above, but now we are going to add some pagination links; as you can see, to create a reusable schema, we are including a view, which is the navigation links view:
<body>
{% for c in comments_page %}
<div>
<p>
{{ c.text|escape }}
{{ c.date_posted|date:"D d M Y" }}
</p>
</div>
{% endfor %}
{% include "partials/pagination.html" with page_obj=comments_page %}
</body>
Build the pagination links with Bootstrap 4 or 5 layout
We are going to save the view to generate the links in the following location as you could see in the previous code:
partials/pagination.html
This view will simply receive the complete list of records that we want to paginate; the code itself is not complicated at all, just check if we have previous or subsequent links and finally iterate according to the pagination range we have to generate the pagination links:
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center mt-5">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">previous</a>
</li>
{% endif %}
{% for i in page_obj.paginator.page_range %}
{% if i == page_obj.number %}
<li class="page-item active">
<a class="page-link" href="#">{{ i }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ i }}">{{ i }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">next</a>
</li>
{% endif %}
</ul>
</nav>
The code, although it seems complicated if you analyze it a bit, is self-explanatory, we simply want to build navigation links, we have two conditionals, at the beginning and end of the block, simply to ask if there are previous or next links to show; and in the middle, we paint all the navigation links.
Explanation of parameters used
We use several parameters in the view to build our navigation links:
- page_obj.has_previous: Boolean indicating if there is a previous page.
- page_obj.has_next: Boolean indicating if there is a next page.
- page_obj.previous_page_number: Returns the number of the previous page.
- page_obj.next_page_number: Returns the number of the next page.
- page_obj.number: Returns the number of the current page.
- page_obj.paginator.num_pages: Returns the total number of pages.
Extra: Pagination in Django with ListView
We can also use the pagination scheme using the generic views that Django offers us; specifically the generic ListView view, for lists.
We have to simply define a class that extends from ListView and specify some parameters:
- The model with which we are going to work
- The location of the template, which we can perfectly use from the previous list
- The pagination level.
In addition to this, we have to create a function called get_queryset which takes the page as a parameter and we have to return an instance of the current page.
So with this, our code looks like this:
from django.views.generic import ListView
from django.core.paginator import Paginator
class Comentarios(ListView):
model = Comment
template_name = 'index.html'
paginate_by = 3
def get_queryset(self,pag=1):
comments = Comment.objects.all()
paginacion = Paginator(comments,'cantidad objetos')
if len(paginacion.page(1)) == 0:
return None
else:
return paginacion.page(pag)
The parameter that we are going to pass to the template is called object_list, therefore, we have to change the reference in the template:
{% for c in object_list %}
Conclusions
Pagination is a scheme that we use in any modern framework like today and today it cannot be missing to be able to paginate different types of development that we want to do, either to use them in a table, or another customizable element, including a RestApi.
Of course, when Django handles different types of templates, we can define the same for each type of template, either in the use of the ListViem as a component of generic views or a view/template 100% customizable by us and everything rotating with the queryset which is our data source.
Of course, when Django handles different types of templates, we can define the same for each type of template, either in the use of the ListViem as a component of generic views or a view/template 100% customizable by us.
Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter