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%}