Продолжаем
изучать фреймворк Flask и на этом занятии поговорим об
использовании БД в WSGI-приложениях. Зачем они нужны, я думаю вы хорошо
себе представляете. В двух словах, в БД сохраняется вся изменяемая информация,
которая, затем, используется при формировании ответов на запросы пользователей.
Например, мы можем сохранить в БД меню сайта и при формировании HTML-страниц, брать
оттуда информацию и представлять ее в виде меню страницы.
В рамках наших
занятий мы будем использовать СУБД SQLite, которая
поставляется вместе с Python3. Но для реальных сайтов – это
не лучший выбор. Гораздо продуктивнее будет использовать функционал модуля
SQLAlchemy
или СУБД
PostgreSQL
Однако, в
качестве первого шага, вполне подойдет и SQLite. Если вы не
знаете как работать с этой СУБД, то смотрите занятие по этой теме.
Итак, первым
делом в программе помимо Flask выполним импорт дополнительных пакетов:
import sqlite3
import os
from flask import Flask, render_template, request
Затем, нам
понадобится выполнить конфигурацию нашего WSGI-приложения. Во Flask принято
соглашение: все переменные, записанные заглавными буквами, относятся к
конфигурационной информации. Мы этим воспользуемся и определим следующие
вспомогательные значения:
# конфигурация
DATABASE = '/tmp/flsite.db'
DEBUG = True
SECRET_KEY = 'fdgfh78@#5?>gfhf89dx,v06k'
USERNAME = 'admin'
PASSWORD = '123'
Здесь все
параметры, в принципе, понятны, кроме, может быть, одного – SECRET_KEY. Он необходим
для безопасной работы сессий на стороне клиента. С помощью этого секретного
ключа выполняется шифрование данных, которые, затем, сохраняются в куках
браузера. Поэтому, даже глядя на информацию, сохраненную в браузере,
пользователь не сможет понять, что она означает. Разумеется, все шифрование Flask выполняет
автоматически. От нас требуется только прописать этот ключ с как можно более
непонятным набором символов.
Далее создаем
само приложение и загружаем конфигурацию из текущего модуля:
app = Flask(__name__)
app.config.from_object(__name__)
Здесь последняя
строчка как раз и выполняет начальную инициализацию приложения, формируя
конфигурацию из вышеопределенных переменных модуля flsite.py.
Далее, мы
переопределим в конфигурации значение DATABASE, расположив БД
в текущем каталоге приложения:
app.config.update(dict(DATABASE=os.path.join(app.root_path,'flsite.db')))
Возможно, у вас
возникнет вопрос: почему нельзя было сразу вначале определить DATABASE как нужно и не
выполнять эту последнюю команду? Дело в том, что изначально у нас не было
созданного приложения app и, соответственно, не могли обратиться
к его свойству root_path, для определения корневого каталога. Конечно,
можно было бы воспользоваться вот такой конструкцией для определения рабочего каталога:
os.path.dirname(os.path.abspath(__file__))
Однако, в случае
использования нескольких WSGI-приложений рабочий каталог и каталог
текущего приложения могут различаться. По этой причине в программе используется
свойство root_path.
Создание БД
Итак,
конфигурацию нашего приложения определили. Теперь пришло время создать БД. Общая
концепция такая. Сначала отдельно создается БД с набором таблиц (без запуска
веб-сервера). А уже потом, обработчики запросов обращаются к созданным таблицам
БД, записывают и считывают из них информацию.
Как это сделать?
В самом простом случае мы можем поступить так. Создадим общую функцию для
установления соединения с БД:
def connect_db():
conn = sqlite3.connect(app.config['DATABASE'])
conn.row_factory = sqlite3.Row
return conn
И, затем,
объявим вспомогательную функцию, которая и будет создавать начальную БД с
набором необходимых таблиц:
def create_db():
"""Вспомогательная функция для создания таблиц БД"""
db = connect_db()
with app.open_resource('sq_db.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
db.close()
Здесь мы
используем метод open_resource, который открывает файл 'sq_db.sql' на чтение,
расположенный в рабочем каталоге нашего приложения. Затем, для открытой БД
выполняется скрипт, записанный в файле 'sq_db.sql'. В конце
вызывается метод commit, чтобы изменения применились к текущей
БД, и метод close закрывает
установленное соединение.
Теперь, давайте
посмотрим на содержимое файла 'sq_db.sql':
create table if not exists mainmenu (
id integer primary key autoincrement,
title text not null,
url text not null
);
Здесь записан SQL-запрос для
формирования таблицы mainmenu с тремя полями: id, title и url. Их назначение
вполне очевидно.
Как теперь
воспользоваться этой функцией для создания начальной БД? Можно поступить так.
Перейти в консоль Python и выполнить импорт функции create_db из
файла flsite.py:
from flsite import create_db
И, затем, просто
вызвать эту функцию:
Все, в
результате, в рабочем каталоге нашего приложения появится файл flsite.db с таблицей mainmenu. Мы в этом
можем легко убедиться, если откроем эту БД с помощью специальной программы DB Browser SQLite.
Подключение к БД в запросах
После создания
БД мы можем ее использовать при обработке запросов. И для оптимизации работы
делают так: в момент прихода запроса, устанавливается соединение с БД, а в
момент завершения обработки – разрывается соединение с БД.
Момент
поступления запроса мы можем «поймать» непосредственно в обработчике. Добавим
обработчик главной страницы:
@app.route("/")
def index():
db = get_db()
return render_template('index.html', menu = [])
Здесь вначале
вызывается вспомогательная функция get_db, которая возвращает активное
соединение с БД. Ее реализация, следующая:
def get_db():
'''Соединение с БД, если оно еще не установлено'''
if not hasattr(g, 'link_db'):
g.link_db = connect_db()
return g.link_db
Смотрите, мы
здесь используем объект g контекста
приложения, которое создается в момент поступления запроса. В этом объекте
можно сохранять любую пользовательскую информацию, которая будет доступна в
любой функции и шаблонах, в пределах этого запроса. Причем, для разных
запросов, объект g будет разным, то есть, он уникален в пределах
текущего запроса.
Далее, мы
проверяем: было ли соединение уже установлено (существует ли атрибут link_db, который мы
создаем в момент соединения с БД. Если соединения еще нет, то устанавливаем его
и, затем, возвращаем. Как вы уже догадались, если где-либо в функциях (или
шаблонах) будет повторное обращение к этой функции, то она просто возвратит
ранее установленное соединение, что очень удобно.
Отлично, с БД
связь установлена. Но как нам ее теперь разорвать в момент завершения запроса? Для
в Flask есть
специальный декоратор teardown_appcontext, который
позволяет определять функцию, вызываемую в момент уничтожения контекста
приложения. А это, обычно, происходит в момент завершения обработки запроса. В
итоге получаем следующий обработчик для завершения соединения с БД:
@app.teardown_appcontext
def close_db(error):
'''Закрываем соединение с БД, если оно было установлено'''
if hasattr(g, 'link_db'):
g.link_db.close()
Все, теперь в
нашей программе существует БД, к которой можно получить доступ в момент
возникновения запроса и автоматическое завершение соединения при окончании
работы с запросом.
На следующем
занятии мы продолжим работу с БД и посмотрим как можно добавлять и брать данные
из таблицы для их отображения на страницах сайта.