2020年7月22日水曜日

Flask-Migrate で外部キー制約のあるテーブルを作成してみる

概要

過去に Flask-Migrate を使ってテーブルを作成してみました
今回は外部キー制約のあるテーブルを作成する方法を紹介します
なおファイルはこちらの記事を元にリファクタリングしているものを使います

環境

  • macOS 10.15.5
  • MySQL 8.0.19
  • Python 3.8.3
  • Flask-Migrate 2.5.3
  • Flask-SQLAlchemy 2.5.3

サンプルコード

とりあえずサンプルコードです
Item というテーブルを追加して User テーブルの id カラムを外部キー制約として定義します
なので User テーブルに存在しない id を Item テーブルの user_id に登録しようとするとエラーになります

  • vim my_app/database.py
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

db = SQLAlchemy()
ma = Marshmallow()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    age = db.Column(db.Integer)
    __table_args__ = (
        db.UniqueConstraint('name', 'age', name='unique_name_age'),
    )
    # item テーブルに user という名前で参照させてあげることを宣言
    item = db.relationship("Item", backref="user")

    def __repr__(self):
        return "%s,%s,%i" % (self.id, self.name, self.age)

class Item(db.Model):
    # 必ず primary_key となる id がないとエラーになるので定義
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    name = db.Column(db.String(20))

class UserSchema(ma.Schema):
    class Meta:
        fields = ("id", "name", "age")

class ItemSchema(ma.Schema):
    class Meta:
        fields = ("user_id", "name", "user")
    user = ma.Nested(UserSchema, many=False)

参照させる側のテーブルで db.relationship を定義します
backref=user とすることで User テーブルを user. で参照できるようにさせます
外部キー制約を付与したいカラムに db.ForeignKey("user.id") という感じで定義すれば制約を付与することができます
今回は Item に対して User は一意に決まるので many=False にすることで配列にならないようにしています

Flask-Migrate か Flask-SQLAlchemy の制約で primary_key が必須なようなので Item テーブルに id カラムを定義しています

ItemSchema について

これもポイントです
外部キーの参照先がある場合 sqlalchemy は自動で引っ張ってきてくれます
そのためのフィールドを追加で定義してあげています
また外部のテーブルは ma.Nested を使って階層化することで同じフィールドでも問題なくシリアライズできるようにしています

マイグレート

テーブルを作成しましょう

  • FLASK_APP=my_app pipenv run flask db migrate -m "Create item table"
  • FLASK_APP=my_app pipenv run flask upgrade

データは適当に入れておきます

  • mysql -u root test -e "select * from user;"
+----+------+------+
| id | name | age  |
+----+------+------+
|  1 | a    |   10 |
|  2 | b    |   10 |
|  3 | c    |   20 |
+----+------+------+
  • mysql -u root test -e "select * from item;"
+----+---------+-------+
| id | user_id | name  |
+----+---------+-------+
|  2 |       1 | apple |
|  3 |       1 | grape |
+----+---------+-------+

データを取得してみる

実際に Flask からデータを取得してみましょう
まずは Item テーブルからデータを取得する処理を作成します

  • vim my_app/lib/__init__.py
from my_app.database import db, Item, ItemSchema

class ItemCRUD():
    def select(self, id):
        item = Item.query.get(id)
        # print(item.user)
        return ItemSchema(many=False).dump(item)

そしてそれを Flask アプリでそれを使います

  • vim my_app/__init__.py
from flask import Flask
from flask_migrate import Migrate
from my_app.database import db, ma
from my_app.lib import ItemCRUD

app = Flask(__name__)
app.debug = True
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+mysqldb://root:@localhost/test?charset=utf8"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

db.init_app(app)
migrate = Migrate(app, db)
ma.init_app(app)

@app.route('/<id>')
def select(id=None):
    crud = ItemCRUD()
    item = crud.select(id)
    return item

動作確認

  • FLASK_APP=my_app pipenv run flask run
  • curl localhost:5000/2

=> {"name":"apple","user":{"age":10,"id":1,"name":"a"},"user_id":1}

こんな感じで外部キーの参照先の User テーブルの情報も階層化されて取得されるのが確認できると思います

最後に

Flask-Migrate で外部キー制約を付与してみました
今回は One-to-Many 構成で外部キー制約を付与しましたが One-to-Many や Many-to- Many でも制約を付与することができます

作成したテーブルはマイグレートしたあとでちゃんと show create table などで確認したほうが良いでしょう

参考サイト

0 件のコメント:

コメントを投稿