Django База [2023]: Постраничная навигация в Django #12
Django

Django База [2023]: Постраничная навигация в Django #12

Razilator

В данной статье мы рассмотрим как добавить перелистывание по страницам статей в Django. Будем использовать два способа, для представлений на основе функций и на основе классов.

Перед созданием пагинации я добавлю на наш разрабатываемый проект ещё 7 тестовых статей.

Django заполнение статьями
Django заполнение статьями

Разбивка на страницы имеет смысл только тогда, когда база данных активно используется, размещается большое количество экземпляров.

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

Проще говоря, база данных выполняет подкачку, а Django запрашивает только номер страницы и смещение.

Класс Paginator принимает четыре аргумента.

Требуется два из них:

  • object_list - Обычно это набор запросов Django, но это может быть любой разделяемый объект .count().__len__() методом or, например список или кортеж. (Требуется)
  • per_page - Определяет количество элементов, которые вы хотите отобразить на каждой странице. (Требуется)
  • orphans - Объявляет минимальное количество элементов, которые вы разрешаете на последней странице. Если на последней странице столько же или меньше элементов, они будут добавлены на предыдущую страницу. Значение по умолчанию равно 0, что означает, что вы можете иметь последнюю страницу с любым количеством элементов между единицей и значением, которое вы установили для per_page. (Опционально)
  • allow_empty_first_page - Имеет значение по умолчанию True. Если object_list пусто, тогда вы получите одну пустую страницу. Набор allow_empty_first_page = False позволяет получить исключение EmptyPage. (Опционально)

Пагинация на основе функций

Давайте перейдем в файл views.py нашего приложения blog, и рассмотрим следующий пример:

Первый пример:

blog/views.py
from django.shortcuts import render
from django.core.paginator import Paginator

def articles_list(request, page):
    articles = Article.objects.all()
    paginator = Paginator(articles, per_page=2)
    page_object = paginator.get_page(page)
    context = {'page_obj': page_object}
    return render(request, 'blog/articles_func_list.html', context)

Для него создадим тестовый шаблон в папке templates/blog/ - articles_func_list.html, это чтобы я мог вам показать, как это работает на деле.

blog/articles_func_list.html
{% extends 'main.html' %}

{% block content %}
    {% for article in page_obj %}
    <div class="card mb-3">
        <div class="row">
            <div class="col-4">
                <img src="{{ article.thumbnail.url }}" class="card-img-top" alt="{{ article.title }}">
            </div>
            <div class="col-8">
                <div class="card-body">
                    <h5 class="card-title">{{ article.title }}</h5>
                    <div class="card-subtitle">{{ article.author.username }}</div>
                    <p class="card-text">{{ article.short_description }}</p>
                    <a href="{% url 'articles_by_category' article.category.slug %}" class="btn btn-primary">{{ article.category.title }}</a>
                  </div>
                </div>
            </div>
      </div>
    {% endfor %}
{% endblock %}

Примечания: в этом шаблоне мы обращаемся к переменной page_obj.

Далее добавим в urls.py обработку страницы со статьями по такому пути site.ru/articles/<int:page>/

blog/urls.py
from django.urls import path

from .views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, articles_list

urlpatterns = [
    path('', ArticleListView.as_view(), name='home'),
    path('articles/<int:page>/', articles_list, name='articles_by_page'),
    path('articles/<str:slug>/', ArticleDetailView.as_view(), name='articles_detail'),
    path('category/<str:slug>/', ArticleByCategoryListView.as_view(), name="articles_by_category"),
]

Добавим в шаблон саму пагинацию:

Для этого я сделаю отдельный файл в папке templates/pagination.html

templates/pagination.html
{% for page_number in page_obj.paginator.get_elided_page_range %}
    {% if page_number == page_obj.paginator.ELLIPSIS %}
        {{page_number}}
    {% else %}
        <a
            href="{% url 'articles_by_page' page_number %}"
            class="{% if page_number == page_obj.number %}current{% endif %}"
        >
            {{page_number}}
        </a>
    {% endif %}
{% endfor %}

И подключим данный фрагмент в main.html:

templates/main.html
<!DOCTYPE html>
<html lang="ru">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- INCLUDE CSS -->
    <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" type="text/css" rel="stylesheet">
</head>
<body>
    <div class="container">
        {% include 'header.html' %}
        <div class="row">
            <div class="col-8">
            {% block content %}

            {% endblock %}
            {% include 'pagination.html' %}
            </div>
            <div class="col-4">
                {% include 'sidebar.html' %}
            </div>
        </div>
    </div>
<script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
</body>
</html>

Теперь проверим как это работает на самом сайте:

При переходе мы всегда должны передавать страницу, т.е http://127.0.0.1:8000/articles/1/ : 2 , 3, 4 и т.д

Находимся на второй странице
Находимся на второй странице

Но этот способ не очень удобен в случае, если мы хотим целенаправленно использовать только страницу articles, а не как переходную страницу с главной по страничной навигации, так как всегда требуется аргумент в виде числа для указания страницы, поэтому давайте немного изменим наше представление:

blog/views.py
from django.shortcuts import render
from django.core.paginator import Paginator

def articles_list(request):
    articles = Article.objects.all()
    paginator = Paginator(articles, per_page=2)
    page_number = request.GET.get('page')
    page_object = paginator.get_page(page_number)
    context = {'page_obj': page_object}
    return render(request, 'blog/articles_func_list.html', context)

И в urls.py удалим <int:page>:

blog/urls.py
from django.urls import path

from .views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, articles_list

urlpatterns = [
    path('', ArticleListView.as_view(), name='home'),
    path('articles/', articles_list, name='articles_by_page'),
    path('articles/<str:slug>/', ArticleDetailView.as_view(), name='articles_detail'),
    path('category/<str:slug>/', ArticleByCategoryListView.as_view(), name="articles_by_category"),
]

И изменим немного pagination.html:

templates/pagination.html
{% for page_number in page_obj.paginator.get_elided_page_range %}
    {% if page_number == page_obj.paginator.ELLIPSIS %}
        {{page_number}}
    {% else %}
        <a href="?page={{ page_number }}"class="{% if page_number == page_obj.number %}current{% endif %}">
            {{page_number}}
        </a>
    {% endif %}
{% endfor %}

Отлично, давайте проверим как это работает, перейдя на страницу http://127.0.0.1:8000/articles/

При наведении сылки на вторую страницу, мы получаем ссылку с параметром ?page=2
При наведении сылки на вторую страницу, мы получаем ссылку с параметром ?page=2

И видим результат работы:

Вторая страница, параметр в ссылке: ?page=2
Вторая страница, параметр в ссылке: ?page=2

Отлично. Мы рассмотрели функциональное представление с пагинацией для разбивки страницы, вы можете применить два способа, например первый способ применить можно, если вы находитесь на главной странице (/), и когда нажимаете на страницу 2, то переходите по следующей ссылке (/articles/2/).

Но второй способ является более часто используемым.

А теперь рассмотрим пагинацию на основе классов.

Пагинация на основе классов

Вернемся к нашему ArticleListView, который мы используем для главной странице и добавим ему параметр paginate_by = 2

blog/views.py
class ArticleListView(ListView):
    model = Article
    template_name = 'blog/articles_list.html'
    context_object_name = 'articles'
    paginate_by = 2

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Главная страница'
        return context

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

В шаблоне менять ничего не нужно, подходит тот же самый, что мы использовали для функциональных представлений, можно добавить условие {% if is_paginated %}:

templates/pagination.html
{% if is_paginated %}
    {% for page_number in page_obj.paginator.get_elided_page_range %}
        {% if page_number == page_obj.paginator.ELLIPSIS %}
            {{page_number}}
        {% else %}
            <a href="?page={{ page_number }}"class="{% if page_number == page_obj.number %}current{% endif %}">
                {{page_number}}
            </a>
        {% endif %}
    {% endfor %}
{%endif%}

Смотрим результат на главной странице: http://127.0.0.1:8000/

Главная страница с пагинацией
Главная страница с пагинацией

Переходим на страницу 3:

Страница 3
Страница 3
;