DevGang
Авторизоваться

Пагинация с бесконечной прокруткой в Django с помощью HTMX

Несколько лет назад я сделал веб-приложение на Django. Я также добавил в него пагинацию. Но чего-то не хватало...

Да, это была бесконечная пагинация прокрутки!

Итак, в сегодняшнем посте мы будем создавать пагинацию с бесконечной прокруткой в Django, используя HTMX.

Кроме того, пагинатор будет работать, даже если у пользователя отключен Javascript.

Для начала создадим проект django и приложение django

  • Установите django
python -m pip install django
  • Создайте приложения и проекта django
django-admin startproject InfiniteScroll

cd ./InfiniteScroll

python manage.py startapp core
  • Добавьте наше приложение django в INSTALLED_APP в InfiniteScroll/setting.py
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Our Apps
    'core.apps.CoreConfig' #<-- ADD THIS
]
  • Чтобы облегчить некоторые задачи, создадим в нашем основном приложении файл утилиты с именем utils.py.
from django.core.paginator import Paginator


def is_htmx(request, boost_check=True):
    hx_boost = request.headers.get("Hx-Boosted")
    hx_request = request.headers.get("Hx-Request")
    if boost_check and hx_boost:
        return False

    elif boost_check and not hx_boost and hx_request:
        return True


def paginate(request, qs, limit=2):
    paginated_qs = Paginator(qs, limit)
    page_no = request.GET.get("page")
    return paginated_qs.get_page(page_no)

Функция is_htmx проверяет, является ли запрос htmx-запросом или нет. Также проверяется, имеет ли запрос статус "boosted".

Функция paginate поможет нам разбить набор запросов на страницы. При желании можно настроить параметр ограничения. Для тестирования я установил ограничение на 2.

  • В файл views.py добавьте следующее:
from django.shortcuts import render

from django.contrib.auth.models import User

from .utils import is_htmx, paginate

# Create your views here.


def index(request):
    users = paginate(request, User.objects.all())
    if is_htmx(request):
        return render(request, "islands/user_list.html", {"users": users})
    return render(request, "index.html", {"users": users})

Здесь мы возвращаем список пользователей, а если запрос является htmx-запросом, то мы возвращаем его части.

  • Давайте создадим шаблоны: templates/islands/pagination.html
<div {% if page.has_next %} hx-get="?page={{ page.next_page_number }}" hx-trigger="revealed"
hx-swap="outerHTML" {% endif %}>
    {% if page.has_next or page.has_previous %}
    <nav class="pagination is-centered is-small mt-2" role="navigation" aria-label="pagination">
        <ul class="pagination-list">
            {% if page.has_previous %}
            <a class="pagination-link" aria-label="Goto page 1" href="?page=1">First Page</a>
            <a class="pagination-link" href="?page={{ page.previous_page_number }}">«
                Previous</a>
            {% endif %}
            <a class="pagination-link is-current" aria-label="Goto page {{ page.number }}">{{ page.number }}</a>
            {% if page.has_next %}
            <a class="pagination-link" href="?page={{ page.next_page_number }}">Next page
                »</a>
            <a class="pagination-link" aria-label="Goto page 4&amp;query="
                href="?page={{ page.paginator.num_pages }}">Last
                Page</a>
            {% endif %}
        </ul>
    </nav>
    {% endif %}
<script>
    htmx.on("htmx:load", function (evt) {
        document.querySelector(".pagination").style.display = "none";
    });
</script>
</div>

Элемент .pagination имеет следующие атрибуты:

  • hx-get: Ссылка на следующую страницу
  • hx-trigger: Триггерное событие, которое будет вызывать конечную точку
  • hx-swap: Тип подмены, которая должна произойти, здесь это outerHTML

Этот код довольно прост.

Сначала мы проверяем, есть ли следующая страница или нет, используя django, затем, если страница есть, мы вызываем функцию ?page.next_page_number }}, например, если текущая страница равна 1, то {{ page.next_page_number }} вернет 2 и т.д.

Далее указывается тип события-триггера.

Он означает, какие события должны произойти, чтобы вызвать запрос от htmx. В данном случае событие revealed означает, что когда элемент пагинации будет раскрыт, htmx выполнит запрос. Подробнее здесь.

И, наконец, у нас есть hx-swap, то есть, когда htmx получит ответ, нам нужно заменить весь html в .pagination на ответный html. Подробнее тут.

Здесь мы скрываем .pagination при каждом htmx-запросе. Если js отключен, то пользователь получит кнопки пагинации. Это называется прогрессивным расширениемtemplates/islands/user_list.html.


{% for user in users %}
<div class="box">
    <p class="subtitle is-1">{{ user }}</p>

    {% if user.is_staff %}
        <span class="tag is-success">Staff</span>
    {% endif %}

</div>
    <br>
{% endfor %}
{% include 'islands/pagination.html' with page=users %}

Это простой файл шаблона django, который будет принимать список пользователей и показывать их с помощью цикла for-loop.

У нас также есть наш шаблон пагинации, который будет отображаться после списка пользователей. templates/index.html.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Infinite Scroll Pagination</title>
    <script src="https://unpkg.com/htmx.org@1.9.6"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
    <link rel="stylesheet" type="text/css" href="https://unpkg.com/bulma-prefers-dark" />

</head>
<body>
    <div class="container">
        <section class="section">

            {% include 'islands/user_list.html' %}
        </section>
    </div>

</body>
</html>

Здесь мы используем bulma и bulma-prefers-dark для стилизации нашей страницы.

Как видите, мы можем использовать наши частичные шаблоны в любом месте нашего кода, поэтому мы можем просто включить islands/user_list.html в index.html.

  • Теперь, наконец, подключим его в urls.pyInfiniteScroll/urls.py.
from django.contrib import admin
from django.urls import path

from core.views import index  #<-- ADD THIS

urlpatterns = [
    path('admin/', admin.site.urls),

    path('', index), #<-- ADD THIS
]

Импортируйте представление index из core.views и отобразите его в urlpatterns

  • Теперь создадим суперпользователя для входа в панель администратора
python manage.py createsuperuser

Запустите сервер и перейдите на http://localhost:8000/admin/auth/user/, чтобы добавить еще несколько пользователей, а затем перейдите на индексную страницу для проверки.

python manage.py runserver
Добавление пользователя в панели администратора
Добавление пользователя в панели администратора
Индексная страница с javascript
Индексная страница с javascript
Индексная страница без javascript
Индексная страница без javascript

Демонстрация

Ну вот и всё! Спасибо за прочтение!

Источник:

#HTML #Python #Django
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу