#TL;DL
Udemyの下記講座の受講記録
Python+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~
https://www.udemy.com/course/flaskpythonweb/
この記事ではFlaskのView側(Model側)について記載する。
View(Template)側については以下の記事を参照
https://qiita.com/kekosh/items/e4b5d1e3272a20d1c966
##Model(モデル)
モデルとはデータベースのテーブルの内容を定義するもの。
SQLAlchemy
pythonで用いられるORマッパーのライブラリ。要はDBを操作するために利用するライブラリ
マイグレーション
Modelの変更をデータベースのテーブルに反映すること。
###1.テーブルの作成
インストール
pip install flask-sqlalchemy
pip install flask-migrate
モデル
#model.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
base_dir = os.path.dirname(__file__)
app = Flask(__name__)
# sqliteのファイル保存先を指定
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
os.path.join(base_dir, 'data.sqlite')
# モデルに変更があった場合にシグナル送出の有無を設定する
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# appに設定した内容でDBに接続する
db = SQLAlchemy(app)
# モデルクラスの定義にはdb.Modelクラスを継承する必要がある。
class Person(db.Model):
# テーブル名を設定(テーブル名はクラス名の複数形が一般的)
__tablename__ = 'persons'
# 作成するテーブルのカラムを定義
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
age = db.Column(db.Integer)
モデルからテーブルを作成し、データを追加する。
#crud.py
from model import db, Person
# モデルからテーブルを作成する(インポートされているモデルすべてが対象)
db.create_all()
* モデルで定義したデータクラスのインスタンスを作成する。
man1 = Person('Taro', 18)
man2 = Person('Jiro', 18)
man3 = Person('Saburo', 18)
# 作成したテーブルにを追加する(add:追加 add_all:リスト形式の一括追加)
db.session.add_all([man1, man2])
db.session.add(man3)
# テーブルへの変更をcommitで確定する。
db.session.commit()
print(man1, man2, man3)
crud.pyファイルを実行後、Modelフォルダ内にsqliteのデータファイルが作成されていることを確認
###2.テーブルのマイグレーション
マイグレーションとは・・・
データベースにモデルファイルに定義したテーブルの更新情報を反映し、その情報を履歴として管理しておくこと。これにより、別環境などに同内容のDBを構築したい場合に履歴に従ってDBを更新していくことで上手く同じ内容のDBを作成することができる。
注意点は、前の項のようにコントロールファイル内で「db.create_all()」メソッドを実行してテーブルを作成した場合はマイグレーションを利用するのに適していないこと。
from model import db, Person
# この場合はマイグレーションによる管理は適さない。
db.create_all()
マイグレーション管理する場合のモデル
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
base_dir = os.path.dirname(__file__)
app = Flask(__name__)
# sqliteのファイル保存先を指定
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
os.path.join(base_dir, 'migrate_data.sqlite')
# sqlalchemyのデータに変更があった場合にトレースする設定のONOFF
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# DBのインスタンス作成(接続)
db = SQLAlchemy(app)
# マイグレーションの準備(引数はFlaskAppとSQLAlchemyのインスタンス)
# 2021-01-10:
# 初期化はinit_appメソッドでも可能(Flask拡張機能標準メソッド)
# [Migrationインスタンス].init_app([flaskアプリ名],[db名]
# 例:
# migrate = Migrate()
# migrate.init_app(app, db)
Migrate(app, db)
# db.Modelクラスを継承することでテーブルを定義できるようにする
class Person(db.Model):
# テーブル名を設定(テーブル名はクラス名の複数形が一般的)
__tablename__ = 'persons'
# 作成するテーブルのカラムを定義
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
gender = db.Column(db.Text)
age = db.Column(db.Integer)
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return "id={},name={},age={}".format(self.id, self.name, self.age)
マイグレーションの実行
1.環境変数を設定する
command : export FLASK_APP = [Flaskインスタンスを作成しているファイル]
"Flaskインスタンスを作成しているファイル"とは、"app=Flask()"が実行されているファイルのこと。(書かれているところではなくて実行されているところ)
(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# export FLASK_APP=migrate_model.py
2.DBを初期化する。これにより、マイグレーションの設定情報を保存するMigratinフォルダが作成される。
command : flask db init
(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# flask db init Creating directory /home/venv/flaskenv/Model/migrations ... done
Creating directory /home/venv/flaskenv/Model/migrations/versions ... done
Generating /home/venv/flaskenv/Model/migrations/README ... done
Generating /home/venv/flaskenv/Model/migrations/alembic.ini ... done
Generating /home/venv/flaskenv/Model/migrations/env.py ... done
Generating /home/venv/flaskenv/Model/migrations/script.py.mako ... done
Please edit configuration/connection/logging settings in
'/home/venv/flaskenv/Model/migrations/alembic.ini' before proceeding.
3.DBにモデルの変更内容を反映するためのマイグレーションファイルを作成する。
command : flask db migrate -m 'コメント'
(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# flask db migrate -m 'add Person'
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'persons'
Generating /home/venv/flaskenv/Model/migrations/versions/aa0223688c58_add_person.py
... done
4.マイグレーションファイルの内容をDBに反映する。
command : flask db upgrade
(flaskenv) (base) root@e8cf64ce12e9:/home/venv/flaskenv/Model# flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade aa0223688c58 -> c881e626270e, persons db add new column "gender"
マイグレーションを実行することで、modelファイル上でのテーブルに対する変更内容がDBに反映される。
また、マイグレーションに伴い、DBに「alembic_version」テーブルが自動的に追加される。このテーブルは、マイグレーションのバージョンを管理するために追加されるテーブルで、右クリックのShow_Tableメニューから内容を確認すると、適用されたマイグレーションのバージョン情報が確認できる。
###3.制約の提示、インデックス作成
オプション | 制約 | コード |
---|---|---|
primary_key | 主キー制約 | db.Column(db.Integer, primary_key = True) |
unique | ユニーク制約 | db.Column(db.Integer,unique = True) |
nullable | NOT NULL制約 | db.Column(db.Integer,nullable = False) |
index | インデックスを作成 | db.Column(db.Text,Index = True) |
db.Index | クラスの外でインデックスを定義することが可能。第1引数にインデックス名、第2引数にインデックスの関数を定義する | db.Index('some_index',func_lower(Person.name)) |
server_default | カラムのデフォルト値を設定 | db.Column(db.Text, server_default='xxxx') |
CheckConstraint ※ | チェック条件を自由に設定 | __table_args__=(CheckConstraint('update_at > create_at'),) |
※CheckConstraintモジュールををsqlalchemyからインポートする必要がある。
db.config['SQLALCHEMY_ECHO'] = Trueの場合
以下のように実行されたSQLの内容がコンソールに表示される。
###4.SQLAlchemyの基本操作
コード | 概要 |
---|---|
db.session.add(object) | レコードの挿入 |
db.session.add_all([List]) | 複数レコード一括追加 |
db.session.delete() Table.query.filter_by(条件).delete() | レコードの削除 |
Table.query.get(primary_key) | 主キーで抽出 |
Table.query.all() | データをリスト化してすべて抽出 |
Table.query.first() | データの最初の要素だけ取得 |
Table.query.filter_by(name='A') | 絞り込み |
Table.query.filter(Table.name.startswith('A')) | 前方一致検索 |
Table.query.filter(Table.name.endswith('z')) | 後方一致検索 |
Table.query.limit(1) | 件数を指定して抽出 |
Table.query.update({'column': 'value'}) | アップデート |
- 複数レコードを取得した場合、個別のレコードを参照するためにはfor文で一つずつ取得する必要がある。
その他参考
https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query%20limit#sqlalchemy.orm.Query.limit
###5.外部キー
複数テーブルのカラム間で紐付けする。
外部参照キーを設定しているテーブルのデータをSELECTした場合、結果に参照先テーブルの紐づくデータも一緒に取得されるようになる。
概要 | テンプレート | サンプル |
---|---|---|
参照先設定 | db.relationship(参照先モデル名, backref='外部参照時テーブル名' | projects = db.relationship('Project', backref='employees', [layzy=xxxx]) |
被参照設定 | db.Column(データ型, db.ForeignKey(キー項目) | employee_id = db.Column(db.Integer, db.ForeignKey('employees.id')) |
○lazyオプション
テーブルを紐付けるときの処理方式を設定する。
オプション | 概要 |
---|---|
select | デフォルトの設定。都度SELECT文を実施する。 |
joined | JOIN句でテーブルを紐付ける |
subquery | サブクエリでテーブルを紐付ける |
dynamic | 紐付けたテーブルでクエリ実行用オブジェクトを作成する。 |
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
base_dir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = \
'sqlite:///' + os.path.join(base_dir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
class Employee(db.Model):
__tablename__ = 'employees'
"""カラム定義"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
# 外部キーの宣言
# One to Many
projects = db.relationship('Project', backref='employees')
# One to One
company = db.relationship('Company', backref='emoployees', uselist=False)
def __init__(self, name):
self.name = name
def __str__(self):
if self.company:
return f'Employee name {self.name} company is {self.company.name}'
else:
return f'Employee name = {self.name}, has no company'
def show_projects(self):
for project in self.projects:
print(project)
class Project(db.Model):
__tablename__ = 'project'
"""カラム定義"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
class Company(db.Model):
__tabelname__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
# テーブル作成
db.create_all()
###6.トランザクション
- トランザクションとは、DBに対する処理を1つの単位としてまとめたもの。
- コミットするまでを1トランザクションとするため、トランザクション内でエラーが発生した場合は、トランザクション内の処理すべてがロールバックされる。
- 一連の処理結果がすべて完了するかエラーですべてなかったことになるかのどちらかになるので、トランザクションを利用することで、処理の原子性が保たれる。
# 構文
with db.sessino.begin(subtransactions=True):
DB処理
db.session.commit()
# データ更新処理
# 対象レコード取得
record = Model.query.get(1)
# 更新したい項目に代入
record.name = '更新'
# db.sessionに更新後のレコードオブジェクトを追加
db.session.add(record)
# コミット処理
db.session.commit()