How to make simple paginator in Django and HTMx pr. 1

Clock
02.04.2025
Clock
15.04.2025
Clock
5 minutes
An eye
53
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0

Introduction

This article describes the process of creating a paginator using Django and HTMX. And as a UI library - daisyUI. This will be a simple and no-frills paginator, the bare minimum for work. Also, this article will not describe the process of creating and configuring a virtual environment, and installing all the accompanying packages and modules.
This is how the pagination buttons will look on my new site
Also, this article is only one of three articles about creating your own paginator on Django. In this one, I will only create a base, a foundation on which we will bring this paginator to its logical conclusion. In the second, we will add filters and sorting. In the third, we will add a search field. So, let's get started.

Frontend part

I prefer to start my work with the frontend, and then we'll see how it goes ┐(シ)┌. To begin with, we'll need a page to host the paginator and templates to render the downloaded content. For example, I have a home page, I need to insert and connect the following template.
<div class="max-w-[1200px] w-[95%] mb-[50px]">
{% include 'parts/article-simple-paginator.html' with articles=articles %}
</div>
It doesn't really matter what you wrap the pluggable template in. I'm going to load articles and I want them all to be in a column, but not too wide. The important thing is that you need to pass the articles variable to the pluggable template parts/article-simple-paginator.html. Where to get it, see the next chapter, now we'll look at other components of our templates.
Source code parts/article-simple-paginator.html:
{% load static %}
{% load i18n %}

<div id="search-results" class="flex flex-col gap-[10px]">

{% include 'parts/article-cards.html' with articles=articles %}
{% include 'parts/paginator-buttons.html' %}

</div>
This code snippet is a flexbox in a column direction, for article cards and the paginator buttons themselves. When we connect the template for rendering article cards, we also do not forget to send the articles variable there. After that, we also connect the paginator.
Now, actually, about the paginator and its buttons, the parts/paginator-buttons.html template:
{% load i18n %}
{% load static %}

<form id="pagination_buttons" class="join self-center">
{% csrf_token %}
<input class="hidden" value="" name="one" type='text'>
<input class="hidden" value="2" name="two" type='text'>
<input class="hidden" value="3" name="three" type='text'>

{% if is_articles_prev %}
<button
class="join-item btn"
hx-post="?page=1"
hx-target="#search-results"
hx-replace-url="true"
value="1"
name="page"
>
{% trans '<<' %}
</button>
<button
class="join-item btn"
hx-post="?page={{articles_prev_page}}"
hx-target="#search-results"
hx-replace-url="true"
value="{{articles_prev_page}}"
name="page"
>
{% trans '<' %}
</button>
{% else %}
<button class="join-item btn btn-disabled">{% trans '<<' %}</button>
<button class="join-item btn btn-disabled">{% trans '<' %}</button>
{% endif %}

<button class="join-item btn btn-disabled">{{articles_start_page}}/{{articles_length}}</button>

{% if is_articles_next%}
<button
class="join-item btn"
hx-post="?page={{articles_next_page}}"
hx-target="#pagination_buttons"
hx-swap="outerHTML"
hx-replace-url="true"
value="{{articles_next_page}}"
name="page"
>
{% trans '>' %}
</button>
<button
class="join-item btn"
hx-post="?page={{articles_length}}"
hx-target="#search-results"
hx-replace-url="true"
value="{{articles_length}}"
name="page"
>
{% trans '>>' %}
</button>
{% else %}
<button class="join-item btn btn-disabled">{% trans '>' %}</button>
<button class="join-item btn btn-disabled">{% trans '>>' %}</button>
{% endif %}
</form>
This paginator is implemented as a form with the ability to send several requests at once. And these requests differ only in the number of the next page. At the very beginning, I pass csrf_token and several inputs, they are not needed yet, but in the next article we will supplement them to implement filtering and sorting.
The form is sent using one of the mechanisms of inserting parameters into a request. In my case, I used the mechanism of automatic insertion of parameters into a request of the nearest form in which my buttons are wrapped.
It, the paginator, consists of 4 buttons and one inactive button to show the current pagination page and how much is left. And this is how these buttons behave:
  1. Buttons >> and << send to the last and first page, respectively. If the user is already on the last or first page, these buttons are disabled.
  2. Buttons > and < send to the next and previous page, respectively. If the user is already on the last or first page, these buttons are disabled.
Buttons << < and >> have a value in the hx-target attribute, "#search-results", that is, the root element of displaying articles and the paginator. When clicked, the entire node is replaced, and not supplemented with new articles, as in the case of the > button.
This completes the frontend part. To summarize, you need the following templates:
  1. parts/article-cards.html - for rendering product cards
  2. parts/article-simple-paginator.html - combines the list of product cards and pagination buttons
  3. parts/paginator-buttons.html - interface for managing pagination
  4. A place where you could insert parts/article-simple-paginator.html.

Backend part

Before we start creating the backend for the paginator, we need to discuss a few details. In particular, why I will not use the REST API for pagination.
This decision is actually dictated by the need for my site to have a paginator on the main page, and not on a separate section of the site, as is already done on this site. Plus, I want people to be able to save the pagination pages via bookmarks.
As you may have noticed, all the paginator buttons have the hx-replace-url attribute. It replaces the old URL with the new one, to which the request was sent. A very cool HTMX feature.
Here are the contents of the views.py file, which contains the view for the home page.
from django.shortcuts import render
from django.http import Http404
from django.core.paginator import Paginator
from django.utils.translation import gettext as _
from django.utils.translation import get_language
from django.views.generic import TemplateView
from Backend.models import *



class HomeView(TemplateView):

template_name = 'domashnyaya.html'
page_size = 2

def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)

return context
def fetch(self, request, context, page_num, *args, **kwargs):
# Filter out other languages versions and order by time published, newest first
articles = Article.objects.filter(lang_type=get_language()).order_by('-time_published')
# Create a paginator, build-in Django one
paginator = Paginator(articles, self.page_size)
pagination_length = paginator.num_pages
# Check if page out of range
if page_num > pagination_length or page_num <= 0:
raise Http404()
# Paginate to page
page = paginator.page(page_num)
is_prev = page.has_previous()
is_next = page.has_next()
page_articles = page.object_list
# Update context
context.update({
'articles': page_articles,
'articles_start_page': page_num,
'articles_next_page': page_num + 1,
'articles_prev_page': page_num - 1,
'articles_length': pagination_length,
'is_articles_next': is_next,
'is_articles_prev': is_prev
})

def post(self, request, *args, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
page_num = int(request.POST.get('page', 1))
self.fetch(request, context, page_num, *args, **kwargs)
return render(request, 'parts/article-simple-paginator.html', context=context)

def get(self, request, *args, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
page_num = int(request.GET.get('page', 1))
self.fetch(request, context, page_num, *args, **kwargs)
return render(request, self.template_name, context=context)
This view is created as a class because it is convenient. And it works with two requests, POST and GET. The difference between them is where they render pagination pages. If a GET request renders the entire page, then a POST request renders only the necessary part, the previously known parts/article-simple-paginator.html template.
The fetch method encapsulates the common logic between GET and POST requests. In the next article, we will expand this method and add filtering with sorting, but for now, let's connect this view to urls.py:
from django.urls import path
from .views import HomeView

urlpatterns = [
path('', HomeView.as_view(), name='domashnyaya'),
]

Conclusion

It's not that hard, actually. If, of course, you compare it to what I had to write to make the paginator work on this site. And to write it, I had to write a huge canvas of JS code, and even more code to make the pagination buttons work.
I had to rewrite it even more ( ; ω ; ) later.
Also, dynamic URL replacement made life much easier. It's not hard to implement this in JS, but there are things that you just don't want to implement on your own.
And just like that, you can implement a paginator in Django using HTMX.


Comments

(0)

captcha
Send
It's empty now. Be the first (o゚v゚)ノ

Other

Similar articles


About duplicated and Not-canonical pages while implementing paginator

Clock
02.03.2025
An eye
63
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article, I will use my site as an example to show what duplicate (non-canonical) content is that appeared in Google Search Console as a result of implementing a …

How to make More button. Using Django, REST API, HTMx

Clock
01.04.2025
An eye
52
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article I will describe a way how you can implement asynchronous loading of content (articles) using the "More" button, which is made using Django, REST API, HTMx and …

How to make simple paginator in Django and HTMx. Adding fitering and sorting feature. pr. 2

Clock
08.04.2025
An eye
26
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article I will describe the process and main code blocks to add sorting and filtering feature to a paginator. This paginator is written in Django using HTMx.

How to add the feedback form using Django and HTMx

Clock
11.04.2025
An eye
48
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article, I will describe the way to add on your Django website feedback form using only HTMx and a little bit of DaisyUI as a UI library. Everything …

How to customize yourown 404 and 500 pages in Django

Clock
12.04.2025
An eye
49
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article, I will describe the process of customizing pages such as 404 and 500. I will show two main ways to do this and how you can quickly …

Used termins


Related questions