Базы данных
Базы данных используются приложениями для хранения в организованном виде. Приложение может выполнять запросы к базе данных для извлечения необходимых порций данных. Наиболее часто в веб-приложениях используются базы данных, основанные на реляционной модели, которые также называются базами данных SQL (Structured Query Language – язык структурированных запросов).
Управление базой данных с помощью Flask-SQLAlchemy
Flask-SQLAlchemy – это расширение для Flask, упрощающее использование SQLAlchemy внутри приложений на основе Flask. SQLAlchemy – мощный фреймворк для работы с реляционными базами данных, поддерживающий множество разных баз данных. Он предлагает высокоуровневые функции объектно-реляционного отображения инизкоуровневый доступ на языке SQL. Подобно большинству других расширений, Flask-SQLAlchemy можно установить с помощью утилиты pip:
(venv) $ pip install flask-sqlalchemy
В расширении Flask-SQLAlchemy база данных определяется адресом URL. В табл. 5.1 перечислены форматы URL для трех наиболее популярных баз данных. Компонент hostname
в этих URL определяет сервер, где располагается база данных MySQL, которая может быть именем локального (localhost
) или удаленного сервера. На сервере одновременно может находиться несколько баз данных, поэтому компонент database
определяет имя используемой базы данных. Для баз данных, требующих аутентификации, в URL включаются компоненты username
и password
.
Адрес URL базы данных должен быть определен как ключ SQLALCHEMY_DATABASE_URI
в объекте с настройками приложения. Еще одним интересным параметром настройки является ключ SQLALCHEMY_COMMIT_ON_TEARDOWN
, который можно установить в значение True
, чтобы разрешить автоматическое подтверждение изменений в базе данных в конце обработки каждого запроса. В следующем примере демонстрируется, как инициализировать и настроить простую базу данных SQLite.
from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__fi le__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
В переменной db
сохраняется экземпляр класса SQLAlchemy
, представляющий базу данных и обеспечивающий доступ ко всем функциональным возможностям Flask-SQLAlchemy.
Определение модели
Термин модель используется для ссылки на хранимые сущности, которые используются приложением. В контексте ORM моделью обычно является класс на языке Python с атрибутами, соответствующими столбцам в таблице.
Экземпляр базы данных из расширения Flask-SQLAlchemy предоставляет базовый класс для определения моделей, а также ряд вспомогательных классов и функций, которые можно использовать для определения структуры данных. Таблицы roles
и users
можно определить как модели Role
и User
следующим образом:
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
def __repr__(self):
return '<User %r>' % self.username
Переменная класса __tablename__
определяет имя таблицы в базе данных. Расширение Flask-SQLAlchemy может автоматически присвоить переменной __tablename__
имя таблицы по умолчанию, но при выборе имен по умолчанию расширение не следует соглашению об использовании множественного числа, поэтому лучше указывать имена таблиц явно. Остальные переменные класса, объявленные как экземпляры класса db.Column
, – это атрибуты модели. В первом аргументе конструктору db.Column
передается тип столбца и атрибута модели. Перечислим некоторые из поддерживаемых типов столбцов и соответствующие им типы в языке Python, используемые в модели:
- Integer (int): Обычное целое число, обычно 32-битное;
- SmallInteger (int): Короткое целое, обычно 16-битное;
- BigInteger (int или long): Целое число неограниченной точности;
- Float (float): Вещественное число;
- Numeric (decimal.Decimal): Число с фиксированной точкой;
- String (str): Строка переменной длины;
- Text (str) Строка переменной длины, оптимизированная для хранения больших строк;
- Unicode (unicode) Строка переменной длины с символами Юникода;
- UnicodeText (unicode) Строка переменной длины с символами Юникода, оптимизированная для хранения больших строк;
- Boolean (bool) Логическое значение;
- Date (datetime.date) Дата;
- Time (datetime.time) Время;
- DateTime (datetime.datetime) Дата и время;
- Interval (datetime.timedelta) Интервал времени;
- Enum (str) Список строк;
- PickleType (Любой объект на языке Python) Автоматически сериализуется с помощью модуля Pickle;
- LargeBinary (str) Двоичный объект.
Остальные аргументы db.Column
определяют дополнительные настройки атрибутов. Перечислим некоторые из поддерживаемых настроек:
- primary_key: Если установлена в True, столбец таблицы будет использоваться как первичный ключ;
- unique: Если установлена в True, для этого столбца будет запрещено сохранять неуникальные значения;
- index: Если установлена в True, для столбца таблицы будет создан индекс, что обеспечит более эффективное выполнение запросов;
- nullable: Если установлена в True, в этом столбце будет разрешено сохранять пустые значения. Если установлена в False, в этом столбце будет запрещено сохранять пустые значения;
- default: Определяет значение по умолчанию для столбца.
Отношения
Связи между строками из разных таблиц в реляционных базах данных устанавливаются с помощью отношений. Реляционная схема отражает простое отношение между пользователями и их ролями. Это отношение называется «один ко многим», потому что одна и та же роль может быть назначена многим пользователям, а каждому конкретному пользователю может быть присвоена только одна роль. Пример показывает, как определить отношение «один ко многим» в классах модели.
class Role(db.Model):
# ...
users = db.relationship('User', backref='role')
class User(db.Model):
# ...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
Отношение связывает две строки посредством внешнего ключа в таблице users
. Здесь столбец role_id
, добавленный в модель User
, определяется как внешний ключ, устанавливающий отношение. Аргумент 'roles.id'
в вызове db.ForeignKey()
указывает, что значение данного столбца должно интерпретироваться как значение столбца id
строк из таблицы roles
.
Атрибут users
, добавленный в модель Role
, – это объектно-ориентированное представление отношения. В данном случае при обращении к атрибуту users
будет возвращаться список пользователей, которым присвоена данная роль. Первый аргумент в вызове db.relationship()
определяет модель на другом конце отношения. Имя модели можно определить в виде строки, если класс к этому моменту еще не определен.
Аргумент backref
в вызове db.relationship()
определяет обратную ссылку отношения – атрибут role
в модели User
. Данный атрибут можно использовать вместо role_id
для доступа к модели Role
как к объекту.
Помимо отношения «один ко многим», существуют и другие типы отношений. Отношение «один к одному» выражается точно так же, как «один ко многим», но в параметре uselist
методу db.relationship()
передается значение False
. Отношение «многие к одному» тоже можно выразить как «один ко многим», если поменять таблицы местами или создать внешний ключ и определить отношение db.relationship()
на стороне «многие». Самый сложный тип отношений – «многие ко многим». Для определения отношения этого типа требуется дополнительная таблица, которую называют ассоциативной.
Операции с базами данных
Теперь модели готовы к использованию. Для изучения приемов работы с моделями лучше всего воспользоваться интерактивной оболочкой Python. В следующих разделах рассказывается о наиболее типичных операциях с базами данных.
Создание таблиц
Самое первое, что следует сделать, – дать команду расширению Flask-SQLAlchemy создать базу данных на основе классов моделей. Эту операцию выполняет функция db.create_all()
:
(venv) $ python hello.py shell
>>> from hello import db
>>> db.create_all()
Если теперь проверить каталог приложения, можно увидеть новый файл с именем data.sqlite
, которое было указано в настройках базы данных SQLite. Функция db.create_all()
не будет пытаться повторно создать или обновить таблицы, если они уже существуют в базе данных. Это не совсем удобно на этапе разработки, когда модели могут изменяться довольно часто и желательно, чтобы эти изменения отражались на базе данных. Самый простой и грубый способ обновить существующие таблицы – предварительно удалить их:
>>> db.drop_all()
>>> db.create_all()
Вставка строк
Следующий пример создает несколько ролей и пользователей:
>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)
Конструкторы моделей принимают начальные значения атрибутов в виде именованных аргументов. Обратите внимание, что можно использовать и атрибут role
, даже при том, что он является не столбцом в базе данных, а высокоуровневым представлением отношения «один ко многим». Атрибут id
в новых объектах не требуется устанавливать явно: управление первичными ключами осуществляется самим расширением Flask-SQLAlchemy. Объекты существуют пока только в программе на языке Python; они еще не были записаны в базу данных. Как следствие их атрибуты id пока не имеют значений:
>>> print(admin_role.id)
None
>>> print(mod_role.id)
None
>>> print(user_role.id)
None
Изменения в базу данных вносятся с помощью сеанса базы данных, который в расширении Flask-SQLAlchemy доступен в виде объекта db.session
. Чтобы подготовить объекты к записи в базу данных, их следует добавить в сеанс:
>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)
То же самое можно записать более кратко:
>>> db.session.add_all([admin_role, mod_role, user_role,
... user_john, user_susan, user_david])
Чтобы сохранить объекты в базе данных, нужно подтвердить изменения в сеансе вызовом метода commit()
:
>>> db.session.commit()
Изменение строк
Метод add()
сеанса базы данных можно также использовать для обновления моделей. Продолжая тот же сеанс интерактивной оболочки, следующий пример переименовывает роль “Admin
” в “Administrator
”:
>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
Удаление строк
Сеанс базы данных имеет также метод delete()
. Следующий пример удаляет роль “Moderator
” из базы данных:
>>> db.session.delete(mod_role)
>>> db.session.commit()
Извлечение строк
Для каждого класса-модели расширение Flask-SQLAlchemy создает объект запроса query
. В самом простом случае объект запроса возвращает все содержимое соответствующей таблицы:
>>> Role.query.all()
[<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]
Объект запроса можно настроить на выполнение более специализированного поиска с применением фильтров. Следующий пример выполняет поиск всех пользователей, которым присвоена роль “User
”:
>>> User.query.fi lter_by(role=user_role).all()
[<User u'susan'>, <User u'david'>]
Фильтры, такие как filter_by()
, возвращают новый запрос. Поддерживается возможность применять целые последовательности фильтров для получения требуемого запроса.
Далее перечислены некоторые часто используемые фильтры:
- filter(): Возвращает новый объект запроса, добавляющий в оригинальный запрос новый фильтр;
- filter_by(): Возвращает новый объект запроса, добавляющий новый фильтр проверки на равенство;
- limit(): Возвращает новый объект запроса, ограничивающий число результатов заданным количеством;
- offset(): Возвращает новый объект запроса, осуществляющий смещение относительно начала результатов, возвращаемых оригинальным запросом;
- order_by(): Возвращает новый объект запроса, выполняющий сортировку результатов в соответствии с указанными критериями;
- group_by(): Возвращает новый объект запроса, группирующий результаты оригинального запроса в соответствии с указанными критериями.
Операции с базой данных в функциях представления
Операции с базами данных, описанные в предыдущих разделах, можно выполнять непосредственно внутри функций представления. Далее демонстрируется новая версия маршрута главной страницы, которая записывает в базу данных имена, введенные пользователями.
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.fi lter_by(username=form.name.data).fi rst()
if user is None:
user = User(username = form.name.data)
db.session.add(user)
session['known'] = False
else:
session['known'] = True
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('index'))
return render_template('index.html',
form = form, name = session.get('name'),
known = session.get('known', False))
Эта измененная версия приложения проверяет присутствие введенного пользователем имени в базе данных с помощью фильтра filter_by()
запроса и устанавливает переменную known
в сеансе пользователя, чтобы после переадресации эту информацию можно было передать в шаблон, где она используется при конструировании текста приветствия. Обратите внимание, что для нормальной работы приложения необходимо заранее создать таблицы в базе данных, как это было показано выше.
Новая версия соответствующего шаблона представлена далее. По значению аргумента known
этот шаблон определяет, какой текст вставить во вторую строку приветствия.
{% extends "base.html" %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{% endblock %}
## Вопросы для самопроверки
- Как создать модель с помощью расширения Flask-SQLAlchemy?
- С помощью какой команды можно создать базу данных?
- Для чего применяется объект
db.session
? - Как осуществить выборку данных из таблицы?
- Перечислите основные методы фильтррации данных.
## Задачи
- Создайте базу данных, в которой будет таблица со следующими столбцами:
- Идентификатор (первичный ключ);
- Имя;
- Телефон;
- E-mail.
- Вставьте в таблицу несколько записей
- Осуществите выборку из таблицы, используя различные фильтры