Как реализовать кнопку подгрузки дополнительных статей. На Django, REST API, HTMx и daisyUI

Часы
01.04.2025
Часы
15.04.2025
Часы
6 минут
Глазик
53
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0

Вступление

В этой статье я покажу как можно реализовать подгрузку дополнительного контента, аля страниц пагинации. С использованием django, rest_API, htmx и daisyUI. Так же я исхожу из мысли, что ты, мой дорогой уже создал виртуальное окружение для django-проекта, установил и настроил rest-api и в целом имеешь проект готовый для разработки. В этой статье настроек и предустановок не будет, сразу перейдём к сути. Демонстрировать всё я буду на своём новом сайте, про историю.

Пишем фронтенд

Для начала, нам потребуется страница для размещения кнопки загрузки контента и шаблоны для отрисовки загружаемого контента. Например, у меня есть домашняя страница, мне нужно вставить подключить следующий шаблон.
<div class="max-w-[1200px] w-[95%] mb-[50px]">
{% include 'parts/article-simple-paginator.html' with articles=articles %}
</div>
Не так важно во что ты обернёшь подключаемый шаблон. Я вот, собираюсь подгружать статьи и хочу чтобы они все были в столбик, но не слишком широкий. Важно то, что нужно передать переменную articles в подключаемый шаблон parts/article-simple-paginator.html. Откуда её взять, переменную смотри в следующей главе, сейчас разберём другие составляющие наших шаблонов.
Исходный код 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>
Данный сниппет кода представляет собой флекс-бокс в колонку, для карточек статей и собственно кнопки загрузки в самом конце. Когда мы подключаем шаблон для отрисовки карточек статей, мы так же не забываем отправить туда articles переменную.
После (выделено жёлтым) идёт логика отображения кнопки подгрузки контента. Если is_not_init равно false, то есть, если это мой первый визит страницы, то при помощи django-тега with я вручную собираю ссылку которую потом, будет использовать кнопка для отправки запроса на сервер.
Хочу заметить, что я ещё использую кастомный django-фильтр addstr, его нужно будет создать самостоятельно. У меня нет статьи о кастомных фильтрах, но можно подсмотреть реализацию такого фильтра на вот этой вот странице (смотри второй ответ!!!)
Но если, это всё-таки не первый визит и is_not_init равно true, то использя rest_api пагинатор, я получаю и отравляю ссылку на следующую страницу. И если, переменная next не определена, то есть мы в конце и больше нечего загружать, мы не отрисовываем данную кнопку.
Теперь, собственно говоря, про саму кнопку загрузки, шаблон parts/more-button.html:
{% 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>
Пройдёмся по атрибутам, в порядке нисходящем).
  1. id - обычный АйДи кнопки, потребуется для того чтобы после можно было её найти
  2. class - используя классы от daisyUI, стилизуем кнопку
  3. hx-get - куда отправляем запрос
  4. hx-target - куда вставляем ответ от сервера
  5. hx-indicator - индикатор для отображения пока грузится контент
  6. hx-swap - как вставляем контент, относительно hx-target. outerHTML значит вставить загруженный контент в родительский элемент дерева DOM, с сохранением самой кнопки.
Класс .htmx-indicator - это указатель со стилями по умолчанию. Собственно, указываем, что это наш индикатор.
Можно ещё рассмотреть шаблон parts/article-cards.html, но в этом не очень много смысла, ибо он уже зависит от того что конкретно, ты хочешь отобразить. В моём случае это статьи, но в твоём это может быть что угодно. Но в любом случае, это обязательно должна быть коллекция чего-то. И вот код:
{% 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 %}
И вот, так это выглядит на самом сайте:
Я ещё не совсем полностью стилизовал сайт, но уже не плохо, я думаю

Пишем бэкенд

Теперь про бэкенд. Начнём с того, что напишем класс-представление для той страницы, где надо подгружать статьи. В файле, Frontend/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)
В общем и целом, идея такова: нужно вернуть коллекцию статей и номер страницы пагинации при GET запросе. Вообще, это всё не обязательно писать, если тебе не надо контент при первом запросе, то есть ты не против пустой страницы изначально.
Здесь всё, хотя ещё надо заметить, как и сколько элементов я возвращаю. Фильтрую я их по языку (не обязательно если одноязычный сайт). Сортирую по дате публикации(сначала идут самые новые). И наконец, возвращаю ровно столько, сколько определено в пагинаторе StandartPagination.
Теперь напишем сереализатор той модели, которую нужно будет подгружать. Это для работы с REST API. И, так как у меня это статьи, выглядит этот сереализатор вот так, в файле Backend/serializers.py:
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')
Да, столько вот у меня полей для статьи, много ...
С готовым серелиазатором и моделью для подгрузки, можно написать ещё один класс-представление, специально для подгрузки при нажатии кнопки "Ещё":
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
})
Здесь, мы создаём пагинатор, унаследовавшись от класса PageNumberPaginator от REST API. Где указываем количество элементов на страницу(page_size) и всего доступных элементов(max_page_size).
Непосредственно загрузкой необходимых статей будет заниматься класс ArticlePaginatedModelView, которую мы сделаем, унаследовавшись от миксина ListAPIView. Специальный класс от REST API, который создан, чтобы проходить по большим коллекциям элементов.
Правда, его нужно предварительно настроить, чтобы он заработал корректно:
  1. serializer_class - Указываем серелиазатор, который будет испльзоваться для получения сырых данных о модели
  2. renderer_classes - то как будем возвращать результат, если не указывать будет возвращать JSON файл, нам нужен отрендереный кусок HTML, по этому используем TemplateHTMLRenderer
  3. template_name - в какой шаблон будет отрисовываться коллекция подгружаемых элементов
  4. pagination_class - как будем проходить по коллекции элементов
  5. queryset - какие элементы будем подгружать
И чтобы вернуть, конкретно коллекцию, необходимо переопределить метод list. Где мы фильтруем нашу коллекцию по языку(необязательно). Можно просто get_queryset().
В документации при переопределении метода list, рекомендуется использовать именно get_queryset метод, но не queryset поле класса. Это связано с кешем и скоростью возврата ответа.
Дальше, получаем срез необходимых элементов для подгрузки и ссылку на следующую страницу, если таковая будет (элементы закончились). И чтобы обозначить, что данный запрос это не первый, определяем тут переменную is_not_init = True.
Всё готово, осталось только подключить данные представления в 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')
]

Заключение

Данного кода должно хватить, чтобы написать обычную кнопку подгрузки контента с сервера, используя только Django как основу, немного REST API и ещё меньше HTMx.


Комментарии

(0)

captcha
Отправить
Сейчас тут пусто. Буть первым (o゚v゚)ノ

Другое

Похожие статьи


Django rest framework, как настроить и как использовать

Часы
24.02.2025
Глазик
149
Сердечки
0
Соединённые точки
1
Соединённые точки
0
Соединённые точки
0
В этой статье описывается процесс настройки и добавления REST фреймворка на сайт, написанный на Django. Добавляется он для того, чтобы построить API для доступа со стороны клиента (фронтенда). Так же …

Как сделать простой пагинатор на Django и HTMx ч. 1

Часы
02.04.2025
Глазик
62
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я опишу то, как создать пагинатор используя Django и HTMx библиотеку. И то, почему это было так просто в сравнении с пагинатором на моём сайте. С шаблонами …

Как сделать простой пагинатор на Django и HTMx. Добавляем сортировку и фильтры ч. 2

Часы
08.04.2025
Глазик
47
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я опишу процесс и основные блоки кода, для того чтобы добавить сортировку и фильтрацию к пагинатору. Данный пагинатор написан на Django используя HTMx.

Как кастомизировать 404 и 500 страницы ответов в Django

Часы
12.04.2025
Глазик
70
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я опишу процесс кастомизации таких страниц как 404 и 500. Я покажу два основных способа как это сделать и то как можно быстро и легко настроить сервер …