これは私のFlask-SQLAlchemy宣言型コードです:
from sqlalchemy.ext.associationproxy import association_proxy
from my_flask_project import db
tagging = db.Table('tagging',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id', ondelete='cascade'),
primary_key=True),
db.Column('role_id', db.Integer, db.ForeignKey('role.id', ondelete='cascade'),
primary_key=True)
)
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
def __init__(self, name=None):
self.name = name
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='cascade'))
user = db.relationship('User', backref=db.backref('roles', cascade='all',
lazy='dynamic'))
...
tags = db.relationship('Tag', secondary=tagging, cascade='all',
backref=db.backref('roles', cascade='all'))
tag_names = association_proxy('tags', 'name')
__table_args__ = (
db.UniqueConstraint('user_id', 'check_id'),
)
これはかなり標準的な多対多のタグ付けソリューションだと思います。ここで、役割のすべてのタグを取得し、新しいタグのセットを役割に設定したいと考えています。
最初のものはとても簡単です:
print role.tags
print role.tag_names
しかし、2 つ目のコードでは、1 日中 Python コードに出くわしました :-( 私はこれができると思いました:
role.tag_names[:] = ['red', 'blue', 'white']
...または少なくとも を使用して似たようなものですrole.tags[:] = ...
が、 SQLAlchemy が既存のタグがあるかどうかを確認せず、それらすべてを完全に新しいエンティティとして挿入しようとしたため、私が発明したものはすべて多くの整合性エラーを引き起こしました。
私の最終的な解決策は次のとおりです。
# cleanup input
tag_names = set(filter(None, tag_names))
# existings tags to be updated
to_update = [t for t in role.tags if t.name in tag_names]
# existing tags to be added
to_add = list(
Tag.query.filter(Tag.name.in_(tag_names - set(role.tag_names)))
)
# tags to be created
existing_tags = to_update + to_add
to_create = [Tag(name) for name in tag_names - set([t.name for t in existing_tags])]
# assign new tags
role.tags[:] = existing_tags + to_create
# omitted bonus: find a way how to get rid of orphan tags
問題は、これは本当に正しい解決策なのかということです。この些細な問題を解決するよりエレガントな方法はありますか? 全体の問題はこの質問に関連していると思います。たぶん私はばかげているだけかもしれませんし、物事を複雑にしすぎているのかもしれません... とにかく、提案をありがとう!