Операции с таблицами через Flask-SQLAlchemy
На предыдущем занятии мы с вами создали две таблицы, используя механизм SQLAlchemy, и объявили функцию-представления для регистрации новых пользователей. И теперь пришло время разобраться с чтением данных из таблиц.
Выборка записей из таблиц
Как вы помните, у нас в БД две таблицы, причем, они связаны между собой через внешний ключ user_id таблицы profiles:
Наша задача сделать выборку по пользователям, в которой бы фигурировали данные из обеих таблиц. Но для начала посмотрим, как вообще осуществляется получение данных. Для демонстрации я перейду в консоль Python и выполню команду:
from app import db, Users, Profiles
то есть, из нашего текущего модуля app импортируем переменную db и классы Users, Profiles. Далее, чтобы выбрать все записи, например, из таблицы users, следует выполнить метод all объекта query:
и на выходе получим список объектов, которые отображаются в соответствии с определением магического метода __repr__ в классе Users:
Здесь объект query берется из базового класса db.Model, от которого образованы классы Users и Profiles. Благодаря концепции наследования в ООП, мы автоматически получаем полный функционал для работы с таблицами БД.
Давайте теперь сохраним возвращаемый список в переменной res:
и посмотрим на ее содержимое. Мы видим, что это коллекция объектов и каждый объект содержит атрибуты: id, email, psw, date. Как раз те, что мы определяли в классе Users и те, что были прочитаны из соответствующей таблицы БД. То есть, мы можем обратиться к любому элементу и вывести нужное нам свойство, например, так:
Получим email из первой записи.
По аналогии работает метод first, только он возвращает первую запись, соответствующего запроса (или значение None, если ничего нет):
Далее, для выбора записей по определенному критерию можно воспользоваться методы filter_by и filter:
Users.query.filter_by(id = 1).all() Users.query.filter(Users.id == 1).all()
Разница между этими методами в том, что в filter_by передаются именованные параметры, а в filter прописывается логическое выражение. Поэтому последний метод обладает большей гибкостью, например, можно вывести все записи с id>1:
Users.query.filter(Users.id > 1).all()
Также можно делать ограничение на максимальное число возвращаемых записей:
Выполнять сортировку по определенному полю:
Users.query.order_by(Users.email).all() Users.query.order_by(Users.email.desc()).all()
Или, просто получать пользователя по значению первичного ключа:
Разумеется, все эти методы можно комбинировать и создавать более сложные запросы.
Выборка из нескольких таблиц
Ну хорошо, мы увидели как можно выбирать записи из одной конкретной таблицы. Но как объединить данные, например, из двух наших таблиц и сформировать одну общую выборку? Для этого нужно соединить записи таблиц по внешнему ключу user_id, следующим образом:
res = db.session.query(Users, Profiles).join(Profiles, Users.id == Profiles.user_id).all()
Здесь вначале в методе query указываются таблицы, формирующие выборку. Затем, используется метод join, в котором прописывается условие связывания записей этих двух таблиц. И в конце, метод all возвращает все записи, удовлетворяющие запросу.
На выходе переменная res будет ссылаться на список, содержащий выбранные данные. К ним можно обратиться, используя следующую конструкцию:
Однако, SQLAlchemy предоставляет нам еще один довольно удобный механизм связывания таблиц. Если мы наперед знаем, что необходимо выбирать для каждого пользователя информацию из таблиц users и profiles, то в классе Users, как таблицы с «первичными данными», к которой подбираются соответствующие записи из «вторичной таблицы» profiles, можно прописать специальную переменную:
pr = db.relationship('Profiles', backref='users', uselist=False)
Через эту переменную будет устанавливаться связь с таблицей Profiles по внешнему ключу user_id. Параметр backref указывает таблицу, к которой присоединять записи из таблицы profiles. Последнее значение uselist=False указывает, что одной записи из users должна соответствовать одна запись из profiles, что, в общем-то, и должно быть.
Теперь, выполняя простую команду:
в объектах списка будет присутствовать атрибут pr, который ссылается на объект Profiles с соответствующими данными:
Как видите, все довольно удобно. Мы воспользуемся этим механизмом и отобразим на главной странице сайта список зарегистрированных пользователей:
@app.route("/") def index(): info = [] try: info = Users.query.all() except: print("Ошибка чтения из БД") return render_template("index.html", title="Главная", list=info)
{% extends 'layout.html' %} {% block content %} ul> {% for u in list %} li>id: {{u.id}}, email: {{u.email}}/p> ul> li>Имя: {{u.pr.name}}/li> li>Возраст: {{u.pr.old}}/li> li>Город: {{u.pr.city}}/li> /ul> /li> {% endfor %} /ul> {% endblock %}
Все, теперь на главной странице видим информацию о пользователях из обеих таблиц.
Заключение
На этом мы завершим серию занятий по микрофреймворку Flask. Конечно, коснуться всех деталей в рамках видеоуроков просто нереально. В частности, модуль SQLAlchemy нами был рассмотрен лишь обзорно, чтобы дать основные представления об этом весьма полезном расширении, которое повсеместно используется при работе с БД. И, если вы задумали создать сайт на Flask, то обязательно используйте его (или какой-либо подобный) для работы с таблицами БД. Это избавит вас в будущем от большого количества проблем, и, кроме того, при трудоустройстве по этому профилю знание SQLAlchemy будет весьма кстати. Хорошей отправной точкой в его изучении будет страница документации на русском языке:
Видео по теме
Flask #1: Что это такое? Простое WSGI-приложение
Flask #2: Использование шаблонов страниц сайта
Flask #3: Контекст приложения и контекст запроса
Flask #4: Функция url_for и переменные URL-адреса
Flask #5: Подключение внешних ресурсов и работа с формами
Flask #6: Мгновенные сообщения — flash, get_flashed_messages
Flask #7: Декоратор errorhandler, функции redirect и abort
Flask #8: Создание БД, установление и разрыв соединения при запросах
Flask #9: Добавление и отображение статей из БД
Flask #10: Способ представления полноценных HTML-страниц на сервере
Flask #11: Формирование ответа сервера, декораторы перехвата запроса
Flask #12: Порядок работы с cookies (куками)
Flask #13: Порядок работы с сессиями (session)
Flask #14: Регистрация пользователей и шифрование паролей
Flask #15: Авторизация пользователей на сайте через Flask-Login
Flask #16: Улучшение процесса авторизации (Flask-Login)
Flask #17: Загрузка файлов на сервер и сохранение в БД
Flask #18: Применение WTForms для работы с формами сайта
Flask #19: Обработка ошибок во Flask-WTF
Flask #20: Blueprint — что это такое, где и как использовать
Flask #21: Blueprint — подключение к БД и работа с ней
Flask #22: Flask-SQLAlchemy — установка, создание таблиц, добавление записей
Flask #23: Операции с таблицами через Flask-SQLAlchemy
© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта
Quickstart¶
Flask-SQLAlchemy is fun to use, incredibly easy for basic applications, and readily extends for larger applications. For the complete guide, checkout the API documentation on the SQLAlchemy class.
Installation¶
$ pip install -U Flask-SQLAlchemy
A Minimal Application¶
For the common case of having one Flask application all you have to do is to create your Flask application, load the configuration of choice and then create the SQLAlchemy object by passing it the application.
Once created, that object then contains all the functions and helpers from both sqlalchemy and sqlalchemy.orm . Furthermore it provides a class called Model that is a declarative base which can be used to declare models:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db' db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) def __repr__(self): return ' %r>' % self.username
To create the initial database, just import the db object from an interactive Python shell and run the SQLAlchemy.create_all() method to create the tables and database:
>>> from yourapplication import db >>> db.create_all()
Boom, and there is your database. Now to create some users:
>>> from yourapplication import User >>> admin = User(username='admin', email='admin@example.com') >>> guest = User(username='guest', email='guest@example.com')
But they are not yet in the database, so let’s make sure they are:
>>> db.session.add(admin) >>> db.session.add(guest) >>> db.session.commit()
Accessing the data in database is easy as a pie:
>>> User.query.all() [, ] >>> User.query.filter_by(username=‘admin’).first()
Note how we never defined a __init__ method on the User class? That’s because SQLAlchemy adds an implicit constructor to all model classes which accepts keyword arguments for all its columns and relationships. If you decide to override the constructor for any reason, make sure to keep accepting **kwargs and call the super constructor with those **kwargs to preserve this behavior:
class Foo(db.Model): # . def __init__(self, **kwargs): super(Foo, self).__init__(**kwargs) # do custom stuff
Simple Relationships¶
SQLAlchemy connects to relational databases and what relational databases are really good at are relations. As such, we shall have an example of an application that uses two tables that have a relationship to each other:
from datetime import datetime class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(80), nullable=False) body = db.Column(db.Text, nullable=False) pub_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False) category = db.relationship('Category', backref=db.backref('posts', lazy=True)) def __repr__(self): return ' %r>' % self.title class Category(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False) def __repr__(self): return ' %r>' % self.name
First let’s create some objects:
>>> py = Category(name='Python') >>> Post(title='Hello Python!', body='Python is pretty cool', category=py) >>> p = Post(title='Snakes', body='Ssssssss') >>> py.posts.append(p) >>> db.session.add(py)
As you can see, there is no need to add the Post objects to the session. Since the Category is part of the session all objects associated with it through relationships will be added too. It does not matter whether db.session.add() is called before or after creating these objects. The association can also be done on either side of the relationship — so a post can be created with a category or it can be added to the list of posts of the category.
Let’s look at the posts. Accessing them will load them from the database since the relationship is lazy-loaded, but you will probably not notice the difference — loading a list is quite fast:
While lazy-loading a relationship is fast, it can easily become a major bottleneck when you end up triggering extra queries in a loop for more than a few objects. For this case, SQLAlchemy lets you override the loading strategy on the query level. If you wanted a single query to load all categories and their posts, you could do it like this:
>>> from sqlalchemy.orm import joinedload >>> query = Category.query.options(joinedload('posts')) >>> for category in query: . print category, category.posts [, ]
If you want to get a query object for that relationship, you can do so using with_parent() . Let’s exclude that post about Snakes for example:
>>> Post.query.with_parent(py).filter(Post.title != 'Snakes').all() []
Road to Enlightenment¶
The only things you need to know compared to plain SQLAlchemy are:
- SQLAlchemy gives you access to the following things:
- all the functions and classes from sqlalchemy and sqlalchemy.orm
- a preconfigured scoped session called session
- the metadata
- the engine
- a SQLAlchemy.create_all() and SQLAlchemy.drop_all() methods to create and drop tables according to the models.
- a Model baseclass that is a configured declarative base.
- The Model declarative base class behaves like a regular Python class but has a query attribute attached that can be used to query the model. ( Model and BaseQuery )
- You have to commit the session, but you don’t have to remove it at the end of the request, Flask-SQLAlchemy does that for you.