3 horizontal lines, burger
3 horizontal lines, burger
3 horizontal lines, burger
3 horizontal lines, burger

3 horizontal lines, burger
Remove all
LOADING ...

Content



    How to implement pagination using Django rest framework + HTMx pr. 1

    Clock
    02.04.2025
    /
    Clock
    01.10.2025
    /
    Clock
    5 minutes
    An eye
    573
    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.


    Do not forget to share, like and leave a comment :)

    Comments

    (0)

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

    Other

    Similar articles


    What to do if pagination pages with canonical tags appear in the search index and such errors as Duplicate|Similar content appear?

    Clock
    02.03.2025
    /
    Clock
    02.10.2025
    An eye
    408
    Hearts
    0
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0
    Why you should be careful while adding a canonical tag for a pagination page, or what to do if you now have a ton of duplicate pages in GSC plus …

    How to implement More button (loading more posts for pagination). Using Django, REST API, HTMx

    Clock
    01.04.2025
    /
    Clock
    01.10.2025
    An eye
    298
    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
    /
    Clock
    01.10.2025
    An eye
    363
    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 implement the feedback form in Django and HTMx

    Clock
    11.04.2025
    /
    Clock
    01.10.2025
    An eye
    945
    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 make 404 and 500 error pages in Django

    Clock
    12.04.2025
    /
    Clock
    01.10.2025
    An eye
    805
    Hearts
    0
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0
    In this article, I will describe how to make custom errors pages such as 404 and 500. I will show two main ways to do this and how you can …

    Used termins


    Related questions