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 More button (loading more posts for pagination). Using Django, REST API, HTMx

    Clock
    01.04.2025
    /
    Clock
    01.10.2025
    /
    Clock
    6 minutes
    An eye
    326
    Hearts
    0
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0

    Introduction

    In this article I will show you, how to implement loading of additional content, via more button. Using django, REST API, htmx and daisyUI. I also assume that you, my dear, have already created a virtual environment for the django project, installed and configured rest-api and generally have a project ready for development. There will be no settings or presets in this article, let's get straight to the point. I will demonstrate everything on my new site, it's about history.

    Frontend part

    First, we need a page to place the loading button and templates to render the loading content. For example, I have a home page, I need to insert the following template code inside it:
    <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 included 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 to pass the articles variable to the included template parts/article-simple-paginator.html. Where to get it, the variable, see in the next chapter, now we'll look at other components of our templates.
    Source code of parts/article-simple-paginator.html:
    {% load static %} {% load i18n %} {% load addstr %} <div id="search-results" class="flex flex-col gap-[10px]"> {% include 'parts/article-cards.html' with articles=articles %} {% if is_not_init %} {% if next %} {% include 'parts/more-button.html' with more_url=next %} {% endif %} {% else %} {% with "api/articles-cards/?page="|addstr:articles_start_page as next_url %} {% include 'parts/more-button.html' with more_url=next_url %} {% endwith %} {% endif %} </div>
    This code snippet is a flex box in a column direction, for article cards, and the actual download button at the very end. When we connect the template for rendering article cards, we also do not want to forget to send the articles variable there.
    After (highlighted in yellow) comes the logic for displaying the content download button. If is_not_init is false, that is, if this is my first visit to the page. Then using the django tag, with, I manually create a link that the button will then use to send a request to the server.
    I want to note that I also use a custom django filter addstr, you will need to create it yourself. I don't have an article about custom filters, but you can take a look at the implementation of such a filter on this page (see the second answer!!!)
    But if this is not the first visit and is_not_init is true, then using the rest_api paginator, I get and send a link to the next page. And if the variable next is not defined, that is, we are at the end and there is nothing more to load, we do not render this button.
    Now, actually, about the download button itself, the parts/more-button.html template:
    {% load i18n %} {% load static %} <button id="more_btn" class="btn btn-error btn-block" hx-get="{{more_url}}" hx-target="#more_btn" hx-indicator=".htmx-indicator" hx-swap="outerHTML"> {% trans 'Ещё' %} <span class="loading loading-bars loading-xl htmx-indicator"></span> </button>
    Let's go through the attributes, in descending order:
    1. id - the usual ID of the button, you will need it so that you can find it later
    2. class - using classes from daisyUI, we style the button
    3. hx-get - where we send the request
    4. hx-target - where we insert the response from the server
    5. hx-indicator - an indicator to display while the content is loading
    6. hx-swap - how we insert the content, relative to hx-target. outerHTML means inserting the loaded content into the parent element of the DOM tree, while preserving the button itself.
    The .htmx-indicator class is an indicator with default styles. Actually, we indicate that this is our indicator.
    You can also consider the parts/article-cards.html template, but this does not make much sense, because it already depends on what exactly you want to display. In my case, these are articles, but in yours it can be anything. But in any case, it must be a collection of something. And here is the code:
    {% load static %} {% load i18n %} {% for article in articles %} <div class="card card-side bg-base-100 shadow-sm flex flex-row flex-wrap rounded-none"> <figure class="max-w-[300px] min-w-[300px] min-h-[300px] m-[0]"> {% if article.preview %} <img class="flex items-center justify-center" src="{% get_media_prefix %}{{article.preview.file}}" alt="Album" /> {% else %} <img class="flex items-center justify-center" src="{% static 'media/images/default-article-preview.svg' %}" alt="Album" /> {% endif %} </figure> <div class="card-body basis-[300px]"> <h3 class="card-title">{{article.header}}</h2> <p>{{article.description}}</p> <div class="flex flex-row gap-[5px] flex-wrap"> {% for figure in article.figures.all %} <button class="btn btn-xs"> <img width="16" src="{% static 'media/images/person.svg' %}"/> {{figure.name}} </button> {% endfor %} </div> <div class="flex flex-row flex-wrap gap-[5px]"> {% for cat in article.categories.all %} <button class="btn btn-xs"> <img width="16" src="{% static 'media/images/category.svg' %}"/> {{cat.name}} </button> {% endfor %} {% for epoch in article.epoch.all %} <button class="btn btn-xs"> <img width="16" src="{% static 'media/images/epoch.svg' %}"/> {{epoch.name}} </button> {% endfor %} {% for period in article.period.all %} <button class="btn btn-xs"> <img width="16" src="{% static 'media/images/period.svg' %}"/> {{period.name}} </button> {% endfor %} </div> <div class="card-actions justify-end"> <button class="btn btn-error">{% trans 'Читать' %}</button> </div> </div> </div> {% endfor %}
    And this is how it looks on the site itself:
    I haven't completely styled the site yet, but it's not bad, I think.

    Backend part

    Now about the backend. Let's start by writing a class-based view for the page where articles need to be loaded. In the file, views.py:
    from django.shortcuts import render from django.utils.translation import gettext as _ from django.utils.translation import get_language from django.views.generic import TemplateView from Backend.models import * from Backend.views import StandardtPagination class HomeView(TemplateView): template_name = 'domashnyaya.html' def get_context_data(self, **kwargs): context = super(HomeView, self).get_context_data(**kwargs) return context def get(self, request, *args, **kwargs): context = super(HomeView, self).get_context_data(**kwargs) page_num = request.GET.get('page', 2) context.update({ 'articles': Article.objects.filter(lang_type=get_language()).order_by('-time_published')[:StandartPagination.page_size], 'articles_start_page': page_num }) return render(request, self.template_name, context=context)
    In general, the idea is this: you need to return a collection of articles and a pagination page number with a GET request. You don’t have to write all this if you don’t need content with the first request, that is, you don’t mind an empty page initially.
    That’s all, although it’s also worth noting how and how many elements I return. I filter them by language (not necessarily if the site is monolingual). I sort them by publication date (the newest ones come first). And finally, I return exactly as many as defined in the StandartPagination class.
    Now, let’s write a serializer for the model that needs to be loaded. This is for working with the REST API. And, since I have articles, this serializer looks like this, in the Backend/serializers.py file:
    from rest_framework import serializers from .models import Article class ArticleSerializer(serializers.ModelSerializer): class Meta: model = Article fields = ('id', 'lang_en', 'lang_be', 'lang_ru', 'lang_type', 'is_audio_version', 'is_pdf_version', 'slug', 'title', 'description', 'meta_keywords', 'time_published', 'time_updated', 'header', 'content', 'preview', 'figures', 'categories', 'epoch', 'period', 'links', 'files', 'facts')
    Yes, I have so many fields for an article, a lot...
    With a ready serializer and model for loading, you can write another class-based view, specifically for loading when the "More" button is pressed:
    from django.utils.translation import get_language from rest_framework import viewsets, generics from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination from .serializers import ArticleSerializer from .models import Article class StandartPagination(PageNumberPagination): page_size = 2 page_size_query_param = 'page_size' max_page_size = len(Article.objects.all()) class ArticlePaginatedModelView(generics.ListAPIView): serializer_class = [ArticleSerializer] renderer_classes = [TemplateHTMLRenderer] template_name = 'parts/article-simple-paginator.html' pagination_class = StandartPagination queryset = Article.objects.all().order_by('-time_published') def list(self, request): queryset = self.get_queryset().filter(lang_type=get_language()) paginator = StandartPagination() page = paginator.paginate_queryset(queryset,request, ArticleModelView) next = paginator.get_next_link() return Response({ 'articles': page, 'is_not_init': True, 'next': next })
    Here, we create a paginator, inheriting from the PageNumberPaginator class from the REST API. Where we specify the number of elements per page (page_size) and the total number of available elements (max_page_size).
    The ArticlePaginatedModelView class will directly load the necessary articles, which we will do by inheriting from the ListAPIView mixin. A special class from the REST API, which is created to go through large collections of elements.
    But, it needs to be configured in advance so that it works correctly:
    1. serializer_class - Specify the serializer that will be used to obtain raw data about the model
    2. renderer_classes - how we will return the result, if not specified, it will return a JSON file. We need a rendered piece of HTML, so we use TemplateHTMLRenderer
    3. template_name - in which template the collection of loaded elements will be rendered
    4. pagination_class - how we will go through the collection of elements
    5. queryset - which elements will be loaded
    And to return, specifically the collection, it is necessary to override the list method. Where we filter our collection by language (optional). You can just use get_queryset() method.
    In the documentation, when overriding the list method, it is recommended to use the get_queryset method, but not the queryset class field. This is due to the cache and the speed of returning the response.
    Next, we get a piece of the necessary elements for loading and a link to the next page. And to indicate that this request is not the first, we define the variable is_not_init = True here.
    Everything is ready, all that remains is to connect these views to Backend/urls.py:
    from django.urls import path, include from rest_framework import routers from Backend import views router = routers.DefaultRouter() router.register(r'articles', views.ArticleModelView, 'article') urlpatterns = [ path('api/', include(router.urls)), path('api/articles-cards/', views.ArticlePaginatedModelView.as_view(), name='articles-cards') ]

    Conclusion

    This code should be enough to write a simple button to load content from the server, using only Django as a base, a little REST API and even less HTMx code.


    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


    How to implement yourown API using Django rest framework

    Clock
    24.02.2025
    /
    Clock
    02.10.2025
    An eye
    673
    Hearts
    1
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0
    This article describes the process of setting up and adding a REST framework to a site written in Django. It is added in order to build an API for access …

    How to implement pagination in Django + HTMx pr. 1

    Clock
    02.04.2025
    /
    Clock
    01.10.2025
    An eye
    669
    Hearts
    0
    Connected dots
    0
    Connected dots
    0
    Connected dots
    0
    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. …

    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
    422
    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 make 404 and 500 error pages in Django

    Clock
    12.04.2025
    /
    Clock
    01.10.2025
    An eye
    1022
    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 …