FlaskチュートリアルをFlask-SQLAlchemyとFlask-Migrateを使って作ってみた

Flaskの勉強がてら、Flask公式のチュートリアル
Flask-SQLAlchemyFlask-Migrate を使って作ってみました。

リポジトリはこちら
(Flask-DebugToolbar も入れています)

スポンサードサーチ

Flask-SQLAlchemy

公式チュートリアルだと生SQLだったので、ラッパーを使いたくて導入。

設定方法

QuickStart に書いてあるけれど、SQLALCHEMY_DATABASE_URI を設定すればよさげ。
今回はconfig.pyに設定を書くことにしました。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object('config')

db = SQLAlchemy(app)
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/{db_name}?charset=utf8'.format(**{
      'user': "root",
      'password': "root",
      'host': "db_flaskr",
      'db_name': "flaskr"
})

DB設定の内容はdocker-compose.ymlに記載しています。

db_flaskr:
  image: mysql:latest
  restart: always
  environment:
    MYSQL_PORT: 3306
    MYSQL_ALLOW_EMPTY_PASSWORD: 1
    MYSQL_ROOT_PASSWORD: root
    MYSQL_DATABASE: flaskr
    MYSQL_USER: root
    MYSQL_PASSWORD:
    TZ: ${TZ:-Asia/Tokyo}

ちなみに flaskr っという名称は公式から持ってきてます。

このチュートリアルはFlaskrと呼ぶ基本的なブログのアプリケーションの作成を一通り行います。

モデルを設置

Simple Relationships を参考にしただけです。
公式のテーブル設計 はイケてないので若干内容を変更しています。

例1:
テーブル名を複数形に。
userテーブル → usersテーブル

例2:
userテーブルにusernameカラムがあるが、カラム名にもuserを付けるのは冗長なので。
users.username → users.name

from application import db
from datetime import datetime


class User(db.Model):

    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), unique=True, nullable=False)
    password = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
from application import db
from datetime import datetime


class Post(db.Model):

    __tablename__ = 'posts'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    title = db.Column(db.Text, nullable=False)
    body = db.Column(db.Text, nullable=False)

    users = db.relationship('User', backref=db.backref('posts', lazy=True))

SQL操作

基本的なSQL操作の書き方
Select, Insert, Delete を参照。
(公式なのにUpdateの書き方は載ってない?)

Select

例1:
usersテーブルのレコード取得

SELECT
    *
FROM
    users
from app.models import User

User.query.all()

例2:
postsテーブルとusersテーブルをJOINしposts.created_at でソートした結果
(modelにリレーションの記載が必要)

SELECT
    *
FROM
    posts
JOIN 
    users ON users.id = posts.user_id
ORDER BY
    posts.created_at
from app.models import Post

Post.query.join(Post.users).order_by(Post.created_at).all()

Insert

db.session.add() 後に
db.session.commit() を実行すれば良さそうです。

例:
投稿内容(タイトルと本文) の新規登録
(user_idはsessionから取得した users.id = 1 とします)

INSERT INTO 
    posts
    (user_id, title, body)
VALUES 
    (1, 'xxxxxx', 'xxxxxx')
from app.models import Post
from flask import Flask, request, session

title = request.form['title']
body = request.form['body']

new_post = Post(title=title, body=body, user_id=session.get('user_id'))
db.session.add(new_post)
db.session.commit()

Update

get_or_404 があるので、主キーによりレコードの存在チェックを行い、
db.session.commit() を実行すれば良さそうです。

例:
投稿内容(タイトルと本文) の更新
(posts.idは1とします)

UPDATE
    posts
SET
    title = 'xxxxxx'
AND body = 'xxxxxx'
AND updated_at = now()
WHERE
    id = 1
from app.models import Post
from datetime import datetime
from flask import Flask, request, session

post = Post.query.get_or_404(1)

title = request.form['title']
body = request.form['body']

post.title = title
post.body = body
post.updated_at = datetime.now()
db.session.commit()

Delete

get_or_404 で、主キーによりレコードの存在チェックを行い、
db.session.delete() 後に
db.session.commit() を実行すれば良さそうです。

別にドキュメントでいうme が担保されれば、get_or_404 でなくても良いと思う。
( me ってなんだ。。。)

Deleting records is very similar, instead of add() use delete():

>>> db.session.delete(me)
>>> db.session.commit()

例:
投稿内容(タイトルと本文) の削除
(posts.idは1とします)

DELETE
FROM
    posts
WHERE
    id = 1
from app.models import Post
from flask import Flask, session

post = Post.query.get_or_404(1)

db.session.delete(post)
db.session.commit()

Flask-Migrate

Migration管理がしたくて導入
(Flaskでもできる?)

設定方法

サンプル通り。
サンプルを見た感じだとFlask-SQLAlchemyをインストールしていないと使えないっぽい。
Flask-SQLAlchemy を設定したときのに下記2行を追加することで、
flask db コマンドが実行できるようになる。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
+ from flask_migrate import Migrate

app = Flask(__name__)
app.config.from_object('config')

db = SQLAlchemy(app)
+ migrate = Migrate(app, db)

初期化

$ flask db init

migrationsディレクトリが作成される。
※ 今回のリポジトリからcloneしてきた場合は、すでに作成されているので実行する必要はありません。

revisionファイル作成

$ flask db migrate

モデルとDBの差分を見て生成されるため、
テーブル設計を変更する場合、基本は
モデルファイルを編集 → flask db migrate 実行の順になる。

実行すると、以下のようなrevisionファイルが作成される。
※ 初回の場合はdowngrade先がないのでdown_revision = None となる。

"""empty message
Revision ID: 47224eeeced1
Revises: 
Create Date: 2020-xx-xx xx:xx:xx.xxxxxx
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '47224eeeced1'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    op.create_table('users',
    xxxxxx
    ),
    op.create_table('posts',
    xxxxxx
    )


def downgrade():
    op.drop_table('posts')
    op.drop_table('users')

もう一度実行するとdowngrade先が入る。

"""empty message
Revision ID: 050dddb4596b
Revises: 47224eeeced1
Create Date: 2020-xx-xx xx:xx:xx.xxxxxx
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '050dddb4596b'
down_revision = '47224eeeced1'
branch_labels = None
depends_on = None


def upgrade():
    xxxxxx

def downgrade():
    xxxxxx

migration実行

$ flask db upgrade

最新バージョンの状態になるように実行されます。

ダウングレード

$ flask db downgrade

現在のバージョンから1つ前のバージョンに戻ります。
どれが1つ前のバージョンかはrevisionファイルのdown_revision で管理している。
当然、revisionファイルにdef downgrade(): の記載がないと動作しない。

バージョン確認

$ flask db current

現在のバージョンを確認できる。
どれが現在のバージョンかはDBのalembic_version テーブルで管理しているっぽい。

mysql> select * from alembic_version;
+--------------+
| version_num  |
+--------------+
| 6b15f8547990 |
+--------------+
1 row in set (0.00 sec)

参考

Flask-SQLAlchemyに関しては、
(英語だけど) この動画がすごい参考になった。

コメントを残す

メールアドレスが公開されることはありません。