Flask SQLAlchemy: Разбивка на страницы

Python

Если вы используете Flask SQLAlchemy, то у вас есть замечательная возможность делать пагинацию - разбивку на страницы для удобной навигации между записями. Делается это очень просто, буквально в несколько строк кода. Я расскажу как это реализовано в моем блоге в упрощенном виде, с использованием двух ссылок и вариант с нумерацией страниц.

Иногда возникает ситуация, когда на страницу выводится большое число записей, которое не совсем удобно воспринимается, в таком случае лучше отображать, только определенное число за раз, позволяя пользователю нажимать на ссылки «следующий» и «предыдущий» для перемещения между страницами.
Очень часто это называется пагинацией от английского глагола (paginate) В нашем случае я не буду расписывать полный запрос, возьму для примера только таблицу Posts, которая содержит все записи блога.

Добавляем endpoint

Добавляем к приложению эндпоинт, где будет происходить разбивка на страницы, в нашем случае это главная страница.
@app.route("/", defaults={'page': 1}, methods=["GET"])
@app.route("/pages/", methods=["GET"])
    def pages(page):
    pagination = db.session.query(Posts).paginate(page=1, per_page=5)

Если разобрать этот код подробнее, мы попадаем на главную страницу и передаем аргумент page = 1, либо на странице /page берем аргумент page из строки через GET. Далее выполняется функция pages, здесь мы получаем переменную pagination и применяем к ней метод paginate cо следующими параметрами: page - текущая страница(которая по умолчанию равна 1), per_page - число записей на страницу (Полное описание параметров можно получить на странице официальной документации)

posts = pagination.items

Применим метод items, для того, чтобы в дальнейшем итерировать наши записи.

Получим ссылки "Назад" и "Далее"

  next_url = url_for('pages', page=pagination.next_num) if pagination.has_next else False
  prev_url = url_for('pages', page=pagination.prev_num) if pagination.has_prev else False

Это обычное тернарное условие и в случае возврата True, мы присваиваем переменным next_url и prev_url с помощью встроенной функции Flask url_for, ссылку на эндпоинт с необходимым параметром, в нашем случае это номер страницы.
Методы has_next и has_prev показывают существует ли ссылка на следующую страницу или нет, а методы next_num и prev_num возвращают номера страниц.

Передаем данные шаблону

Осталось лишь передать данные в шаблон и настроить вывод кнопок

return render_template('pagination.html', posts=posts, next_url=next_url, prev_url=prev_url)

Настраиваем шаблон

Делаем простой шаблон, для упрощения восприятия я не применял стилей, в основном использован синтаксис шаблонизатора Jinja

{%block content%}
    {%for post in posts%}
        {{post.title}}    <br>

    {% endfor %}
{%endblock%}
{%if prev_url%} <a href="{{prev_url}}">Назад</a>{%else%}Назад{%endif%}
{%if next_url%}<a href="{{next_url}}">Далее</a>{%else%}Далее{%endif%}

Разберем подробнее шаблон - сначала мы выводим список статей с помощью цикла for, всего будет отображено 5 записей, потому что наш параметр per_page = 5. Далее идет два условия: если next_url и prev_url не False, то выведем ссылку на следующую и предыдущую страницу, в противном случае будет ссылки будет не подсвечена. Ниже представлена демонстрация работы.

Простая разбивка на страницы

Виджет выбора страницы

Усложним задачу, добавим нумерацию страниц. Для этого создадим переменную iter

 iter = pagination.iter_pages(left_edge=2, left_current=3, right_current=3, right_edge=2)

Переменная num_pages получает значение pagination с примененным к ней методом iter_pages, где параметры отображают left_edge – Сколько страниц показывать, начиная с первой страницы, left_current – сколько страниц отображать слева от текущей страницы, right_current – сколько страниц отображать справа от текущей страницы, right_edge – сколько страниц показывать, начиная с последней.

Передадим шаблону переменную num_pages и номер текущей страницы - pagination.iter

return render_template('pagionation.html', posts=posts, next_url=next_url, prev_url=prev_url, num_pages=num_pages,
                           current_page=pagination.page)

Изменим шаблон, добавив нумерацию страниц

{%block content%{%for post in posts%}
{{post.title}}    <br>
{% endfor %}
{%endblock%}
{%if prev_url%} <a href="{{prev_url}}">Назад</a>{%else%}Назад{%endif%}
{% for page in num_pages%}
    {%if page == None %} ... {% else %}
        {%if page == current_page%}
            <b>{{page}}</b>
        {%else%}<a href="{{url_for('test', page = page)}}">{{page}}</a>{%endif%}
    {%endif%}
{%endfor%}
{%if next_url%}<a href="{{next_url}}">Далее</a>{%else%}Далее{%endif%}

Первым делом запускаем цикл for по num_pages, внутри цикла проверяем значение page, пропущенные страницы между краями и серединой представлены None, поэтому мы заменим их "...", так же уберем ссылку с текущей страницы и подсветим ее жирным шрифтом.
Ниже пример работы (для наглядности вывожу по одной новости на страницу, чтобы увеличить число страниц)

Разбивка с нумерацией страниц

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


Итоговый код
Основная часть
@app.route("/", defaults={'page': 1}, methods=["GET"])
@app.route("/pages/", methods=["GET"])
def pages(page):
    pagination = db.session.query(Posts).order_by(Posts.date.desc()).paginate(page=page, per_page=1, error_out=False)
    posts = pagination.items
    num_pages = pagination.iter_pages(left_edge=2, left_current=3, right_current=3, right_edge=2)
    next_url = url_for('test', page=pagination.next_num) if pagination.has_next else False
    prev_url = url_for('test', page=pagination.prev_num) if pagination.has_prev else False
    return render_template('pagination.html', posts=posts, next_url=next_url, prev_url=prev_url, num_pages=num_pages,
                           current_page=pagination.page)
Шаблон
{%block content%{%for post in posts%}
{{post.title}}    <br>
{% endfor %}
{%endblock%}
{%if prev_url%} <a href="{{prev_url}}">Назад</a>{%else%}Назад{%endif%}
{% for page in num_pages%}
    {%if page == None %} ... {% else %}
        {%if page == current_page%}
            <b>{{page}}</b>
        {%else%}<a href="{{url_for('test', page = page)}}">{{page}}</a>{%endif%}
    {%endif%}
{%endfor%}
{%if next_url%}<a href="{{next_url}}">Далее</a>{%else%}Далее{%endif%}

404 2025.01.06 22:53 python flask sqlalchemy pagination