How to make simple paginator in Django and HTMx. Adding fitering and sorting feature. pr. 2
08.04.2025
15.04.2025
10 minutes
26
0
0
0
0
Introduction
Any paginator is as good as its filters. In this article, I will show how you can "organically" integrate any filters and sorting into your paginator. This paginator will be written using HTMx. Which is going to communicate with the API of our site, configured using the Django REST framework.
The filters and sorters that we will add to the paginator will be static. What do I mean? By static filters and sorters, I mean those filters that require you to click the sort button to apply. That is, first select the necessary options, then click the corresponding "sort" button.
I could make it dynamic, but this would require writing additional JS code and greatly complicating the implementation of the paginator, and this would no longer be an article about "adding filters to a simple paginator." That's not what the article is about.
What the filters will look like on the page
Frontend part
Our filters and sorters are located in one place and are framed by the form tag. There is no fundamental difference between them. At least, they will be formatted the same way on the client side.
Let's create a Django template where our filters and sorters will be rendered. I called it paginator-filters.html and placed it in the parts/ directory. Now let's figure out what is written there. Here is the full template code:
There are two buttons in this form. One sends a request with parameters, the other sends a request without parameters, that is, clears the filters.
The one that is highlighted sends a request with parameters, that is, filters the output
We have figured out how and where requests for filtering and sorting are sent from. Now let's figure out what and how we send to the server.
At the very beginning, there is a hidden input field that contains the page number. It is always equal to 1, by the way. Why start filtering from the first page, and not, say, from the current one? In general, this can be done, it will just lead to an overcomplicating the code on the server. For example, we have access to only 5 pagination pages, based on the filtering results, and we are now on the 6th.
After the page number, the form is divided into a part of filters and a part of sorters. Let's first look at the sorters:
The first thing I want to note is the template variables, such as alphabetic_sort, last_pub_sort and last_upd_sort. They are defined and returned by the server. And if they are defined, the input fields will already be checked. Also in the input element I define the name of the parameter being sent, it will also be the name of the variable in the template.
That's all about the sorters, let's move on to the filters. In the filter block, you can find two types:
Simple, these are those that work exactly the same as sorters. The same markup.
Complex, these are those that will have to make a preliminary request to the server to find out which filter elements to display.
An example of a complex filter.
I make a GET request to the address "api/epochs/" using the hx-get attribute. The hx-tirgger attribute specifies when this request should be made. And the target element is specified into which the response from the server will be loaded, this target element is specified using the hx-target attribute.
As you can see, I access the API of my site to get all possible filtering options. To render them, you will need to write a corresponding template, let's call it paginator-filter-items.html and put it in the parts/ directory.
This template will render all available filter elements and mark active input fields.
We are almost done with the frontend, we just need to update the paginator-buttons.html file. In each hx-post attribute, at the end, add the following template variable, base_url. This is done so that filtering and sorting are preserved when moving between pages. Here is the updated file:
The lines where changes have occurred are marked in yellow.
Backend part
The whole point of the client part of filters and sorters is to build the corresponding URL and send it to the server, where the server then figures out what and how to return to the user.
All this will happen in the previously written HomeView class based view, in the Frontend/views.py file. Which we connected in Frontend/urls.py.
Now let's look at how I changed the HomeView class:
In this file I added the creation of a base URL address, which is attached to the hx-post attribute, in the pagination buttons. It works quite simply. It takes the request address, breaks it into key-value pairs and connects them back, but only without specifying the page number.
Oh, and I almost forgot. I also added the logic for filtering and sorting articles. It is at the very beginning. Let's go through it in order:
Here, I extract the query itself using variables in the URL.
Here, basic filtering and sorting by publication date occurs (the newest articles are displayed first).
Afterward, I perform direct filtering and sorting by the parameters that were sent via the URL.
This sorting works on the "pipe" principle. That is, each subsequent filter works with the output data of the previous filter. Thus, only those articles that have passed through all filters remain.
Another class to consider is the FiltersView. This is a class based view that works and returns html pages with a list of all available filters for records in the Figure, Epoch, Period, and Category models.
Here is the code for its FiltersView class:
The main purpose of this view is to "filter filters" by language and mark them in the template if needed (i.e. mark them checked if they are already in use). It also needs to be connected in Backend/urls.py:
When connecting FiltersView, you need to specify the following parameters:
Parameter name (it will be used in the URL)
Serializer class
List of objects that the FilterView class will work with
Conclusion
Here's what I'd like to say about filters on paginator pages. It's not difficult, if you don't complicate it and reinvent the wheel. As you may have noticed, I used HTMx to a minimum, and almost didn't use REST API, because it was not required. We are creating a simple paginator, not a complex-universal uber-duper ultimate search. Cheers.
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 …
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 …
In this article, I will describe how to create a paginator using Django and the HTMx library. And why it was so easy compared to the paginator on my site. …
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.
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 …
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
Pagination ⟶ Is the design pattern, which divide content on the website into smaller pieces (pages)
Django views ⟶ In Django, views are a fundamental component that handle the logic of your web application. They process incoming web requests, interact with models (to retrieve or modify data), and return an HTTP response. In essence, views determine what data gets displayed and how it is presented by associating URLs with Python functions or classes.
HTML (Hyper Text Markup Language) ⟶ HTML, or Hyper Text Markup Language, is the standard markup language used to create and design documents on the World Wide Web. It is not a programming language, but a markup language that structures content on the web. HTML uses various elements and tags to define the different parts of a webpage.
CORS (Cross-Origin Resource Sharing) ⟶ Is a security feature implemented in web browsers that enables controlled access to resources located outside of a given origin (domain, protocol, and port combination). It is a mechanism that allows web servers to specify who can access their resources and which HTTP methods are permitted for cross-origin requests.
Related questions
Why did you choose django over website builders ?
Quite a difficult question. I guess I like to understand and delve into complex things, although it seems that it’s not worth it. It's a similar story with game development. Instead of making games using game engines, I made my own, **DI**. Well, I'm just not looking for easy ways ;)
What is the difference between OneToOne and ForeignKey fields ?
Their main and fundamental difference is how they relate to the model they refer to. If the relationship between the model with the OneToOne field and the target model is such that there can only be one such record in the database, then ForeignKey allows an unlimited number of such records.
When to use OneToOne model field in Django ?
This model field is perfect when it is necessary to add new functionality to an existing model (or rather, to a model entry in the DB). That is, when changing the existing functionality of already created models is not justified as it is difficult or expensive.