2021年12月2日木曜日

SqlAlchemy で外部キーとして参照しているテーブルの逆参照する方法

SqlAlchemy で外部キーとして参照しているテーブルの逆参照する方法

概要

db.relationship で外部キーの参照ができます
外部キーとして紐付いている先から参照元のデータを取得したケースがあると思います

今回はその方法を紹介します

環境

  • macOS 11.6.1
  • Python 3.8.3

準備

  • pipenv install Flask-Migrate Flask-SQLAlchemy Flask-Marshmallow Flask mysqlclient

my_app/database.py

Item は必ずどれかの User に紐付いています
User は Item に紐付いていないケースがあります

item = ma.Nested(ItemSchema, many=True) で参照元を参照します

item の部分の変数名は item = db.relationship("Item", backref="user") で使用している変数名と同じにする必要があります
同じにしないと SqlAlchemy が参照元を辿れず値がうまく取得できません

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 = db.relationship("Item", backref="user")

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

class Item(db.Model):
    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 ItemSchema(ma.Schema):
    class Meta:
        fields = ("id", "name")

class UserSchema(ma.Schema):
    class Meta:
        fields = ("id", "name", "age", "item")
    item = ma.Nested(ItemSchema, many=True)

my_app/__init__.py

User の情報を取得するためのアプリです

from flask import Flask, jsonify
from flask_migrate import Migrate
from my_app.database import db, ma
from my_app.lib import UserCRUD

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 = UserCRUD()
    user = crud.select(id)
    return jsonify(user)

my_app/lib/__init__.py

User テーブルを操作するためのクラスです

from my_app.database import db, User, UserSchema

class UserCRUD():
    def select(self, id):
        user = User.query.get(id)
        return UserSchema(many=False).dump(user)

DB マイグレート

  • mysql -u root -p -e "create database test;"
  • FLASK_APP=my_app pipenv run flask db init
  • FLASK_APP=my_app pipenv run flask db migrate -m “Create item table”
  • FLASK_APP=my_app pipenv run flask db upgrade
insert into user values (1, 'hawk', 10);
insert into user values (2, 'snowlog', 20);
insert into item values (1, 1, 'apple');
insert into item values (2, 1, 'grape');
insert into item values (3, 1, 'banana');

動作確認

  • FLASK_APP=my_app pipenv run flask run
  • curl localhost:5000/1
{"age":10,"id":1,"item":[{"id":1,"name":"apple"},{"id":2,"name":"grape"},{"id":3,"name":"banana"}],"name":"hawk"}
  • curl localhost:5000/2
{"age":20,"id":2,"item":[],"name":"snowlog"}

こんな感じで User から Item を参照することができます

Item クラスの特定のフィールドだけ返したい場合

post_dump を使うといいかなと思います

from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import post_dump

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 = db.relationship("Item", backref="user")

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

class Item(db.Model):
    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 ItemSchema(ma.Schema):
    class Meta:
        fields = ("id", "name")

class UserSchema(ma.Schema):
    class Meta:
        fields = ("id", "name", "age", "item")
    item = ma.Nested(ItemSchema, many=True)
    best_item_name = ma.String(default="")

    @post_dump
    def set_best_item(self, data, many, **kwargs):
        if data["item"]:
            data["best_item_name"] = data["item"][0]["name"]
        del data["item"]
        return data

最後に

User.query.get(id) の結果から item を参照することはできるのですが Schema で逆参照を使う場合に少し工夫が必要です

参考サイト

0 件のコメント:

コメントを投稿