Проблему разбивки выводимых записей на несколько страниц хорошо решает расширение Flask Flask-SQLAlchemy
. Это расширение поставляется с очень хорошим классом flask_sqlalchemy.Pagination()
.
Но как быть разработчикам, которые не хотят замедлять работу приложения, увеличивать потребление оперативной памяти сервера и повышать нагрузку на базу данных внедрением дополнительных абстракций, таких как ORM SQLAlchemy? Как быть с пагинацией разработчикам, которые используют в своих проектах "ванильный" SQL. Ответ прост, необходимо разработать свой класс пагинации или воспользоваться расширением flask-paginate
, которое не имеет зависимостей от ORM.
Модуль flask-paginate
представляет собой простое расширение фреймворка flask
для разбивки выводимых записей на несколько страниц, которое поддерживает разметку пагинации нескольких популярных CSS-фреймворков.
Разбивка на страницы (пагинация) в шаблоне
jinja2
выводится одним тегом{{ pagination.links }}
.
Bootstrap 5
;Bootstrap 4
;Bootstrap 3
;Bootstrap 2
;Bulma
;Foundation
;SemanticUI
;Materialize framework
;flask-paginate
в виртуальное окружение.# создаем виртуальное окружение, если нет $ python3 -m venv .venv --prompt VirtualEnv # активируем виртуальное окружение $ source .venv/bin/activate # обновляем `pip` (VirtualEnv):~$ python3 -m pip install -U pip # ставим модуль `flask-paginate` (VirtualEnv):~$ python3 -m pip install -U flask-paginate
Если нужно отобразить информацию о разбивке на страницы (например "всего 100 записей, отображаются с 20 по 30") необходимо разместить jinja2
тег {{ pagination.info }}
в шаблоне страницы в требуемом месте и добавить следующие строки CSS-разметки в файл проекта:
.pagination-page-info { padding: .6em; padding-left: 0; width: 40em; margin: .5em; margin-left: 0; font-size: 12px; } .pagination-page-info b { color: black; background: #6aa6ed; padding-left: 2px; padding: .1em .25em; font-size: 150%; }
Пример будет а чистом SQL (чтобы было понятнее, что происходит) с использованием драйвера БД MySQLdb
.
И так, в файле представлений Flask (например, user_search.py
):
from flask import Blueprint from flask_paginate import Pagination, get_page_parameter from MySQLdb import connect, Error from contextlib import closing mod = Blueprint('users', __name__) @mod.route('/search/', methods=['GET']) def search(): # словарь для передачи в шаблон content = {} # если в URL присутствует параметр запроса 'q' q = request.args.get('q') if q: # открываем соединение с БД db = connect(**MYSQLCONF) # определяем текущую страницу пагинации page = request.args.get(get_page_parameter(), type=int, default=1) # количество записей на странице limit = 20 # определяем `offset` записей для основного запроса offset = 0 if page == 1 else (page-1) * limit # общее кол-во строк для пагинации total = 0 with closing(db.cursor()) as cursor: cursor.execute("SELECT id FROM user WHERE nickname RLIKE %s", (q,)) total = cursor.rowcount # создаем информацию о пагинации content['pagination'] = Pagination(page=page, total=total, search=True, per_page=limit, bs_version=4) # `page` это номер текущей страницы, параметр URL (по умолчанию 'page') из которого # он будет извлекаться. Его можно настроить, например Pagination(page_parameter='p', ...) # или установить `PAGE_PARAMETER` в файле конфигурации. # Также можно настроить параметр URL, который будет передавать количество выводимых # записей на одной странице, например Pagination(per_page_parameter='pp') или установить # параметр `PER_PAGE` в файле конфигурации if total: # собственно сам поисковый запрос with closing(db.cursor()) as cursor: try: cursor.execute("SELECT id, nickname, name, last_name FROM user " "WHERE nickname RLIKE %s LIMIT %s, %s", (q, offset, limit,)) except Error as e: # print(f'{e.args[0]} ==> {e.args[1]}') abort(404) # данные для отображения найденных пользователей content['user_search'] = [] for row in cursor.fetchall(): content['user_search'].append((row[0], row[1], row[2], row[3])) else: abort(404) if db: # закрываем соединение с БД db.close() # возвращаем результат для шаблона 'user_search.html' return render_template('user_search.html', content=content)
И в шаблоне user_search.html
:
{# вывод информации о разбивке #} {{ content.pagination.info }} {# вывод самой пагинации #} {{ content.pagination.links }} {# Здесь выводится информация #} {# о найденных записях пользователей #} {# вывод самой пагинации #} {{ content.pagination.links }}
Примечание. Модуль
flask-paginate
очень хорошо работает с ЧПУ URL. Например:@app.route('/<section>/', methods=['GET']) @app.route('/<section>/<int:cur_page>/', methods=['GET']) def home(section, cur_page=1): ... limit = DISPLAY_ROWS offset = 0 if cur_page == 1 else (cur_page-1)*limit Pagination(page_parameter='cur_page', cur_page=cur_page, total=total, per_page=limit) ...Этот код будет обслуживать URL:
- первая страница - http://somesite.ru/section/ (можно назначить как
canonical
)- вторая страница - http://somesite.ru/section/2/
- и т.д.
Pagination()
.page_parameter
: имя (строка) параметра URL в GET запросе (например /?page=2&q=vasya
), для извлечения из запроса индекса текущей страницы (по умолчанию 'page'
);page
: номер текущей страницы. Имя этого аргумента зависит от page_parameter
. Если задан page_parameter='p'
, то имя этого аргумента становиться p
;
# по умолчанию Pagination(page=page, total=total, per_page=15) # переопределим `page_parameter`, в этом случае имя # аргумента `page` трансформируется в имя `p` Pagination(page_parameter='p', p=page, total=total, per_page=15)
per_page_parameter
: имя (строка) параметра URL в GET запросе (например /?per_page=15&page=2&q=vasya
), для извлечения из запроса количества отображаемых записей на одной странице (по умолчанию 'per_page'
);
per_page
: сколько записей нужно отображать на одной странице. Имя этого аргумента зависит от per_page_parameter
. Если задан per_page_parameter='pp'
, то имя этого аргумента становиться pp
;
# по умолчанию Pagination(page=page, total=total, per_page=15) # переопределим `per_page_parameter`, в этом случае имя # аргумента `per_page` трансформируется в имя `pp` Pagination(p=page, total=total, per_page_parameter='pp', pp=15)
inner_window
: количество отображаемых ссылок пагинации вокруг текущей страницы;
outer_window
: количество отображаемых ссылок пагинации рядом с первой и последней страницей;
prev_label
: текст для предыдущей страницы, по умолчанию '«'
next_label
: текст для следующей страницы, по умолчанию '»'
total
: общее количество записей для разбивки на страницы;
css_framework
: используемый CSS-фреймворк, по умолчанию 'bootstrap'
, принимаемые строки: 'bootstrap'
, 'semantic'
, 'foundation'
, 'bulma'
, 'materialize'
;
bs_version
: версия CSS-фреймворка bootstrap
, по умолчанию 2;
display_msg
: настройка строки информации о разбивке на страницы, по умолчанию "displaying <b>{start} - {end}</b> {record_name} in total <b>{total}</b>"
;
search
: если True
, то показывает строку с сообщением аргумента search_msg
, иначе будет выведено сообщение аргумента display_msg
;
found
: количество найденных записей при поиске {found}
в строке аргумента search_msg
(по умолчанию равно аргументу total
);
record_name
: текст {record_name}, отображаемый в строке аргумента search_msg
и display_msg
;
search_msg
: настройка строки при поиске, по умолчанию "found <b>{found}</b> {record_name}, displaying <b>{start} - {end}</b>"
;
link_size
: размер шрифта ссылок пагинации на страницы;
alignment
: выравнивание ссылок по страницам;
href
: добавляет пользовательский href
для ссылок пагинации - поддерживает формы с методом POST
. Строка URL должна содержать {0}
(см. str.format()
) для форматирования номера страницы;
show_single_page
: если True
, то будет выводить пагинацию даже для одной страницы;
anchor
: параметр привязки, добавляется к href
URL-страницы;
format_total
: общий числовой формат, если True
, то 1,234
, по умолчанию False
(без запятой);
format_number
: числовой формат начала и конца пагинации, если True
, то 1,234
, по умолчанию False
(без запятой);
url_coding
: кодировка для кодировки URL, по умолчанию utf-8;
bulma_style
: стиль ссылки на страницу для CSS-фреймворка bulma
.