2016-11-13 10 views
1

私はブログ用のタグ付けシステムで作業しています。 Flaskアプリオブジェクトと関連するPostTagモデルを作成するコードを取り除いたものです。多対多関係:取得または作成

from flask import Flask 
from flask_sqlalchemy import SQLAlchemy 
from sqlalchemy.ext.associationproxy import association_proxy 

app = Flask(__name__) 
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.sqlite' 
db = SQLAlchemy(app) 

post_tags = db.Table('post_tags', 
        db.Column('post_id', db.Integer, 
           db.ForeignKey('posts.id'), 
           nullable=False), 
        db.Column('tag_id', db.Integer, 
           db.ForeignKey('tags.id'), 
           nullable=False), 
        db.PrimaryKeyConstraint('post_id', 'tag_id')) 

class Tag(db.Model): 
    __tablename__ = 'tags' 

    id = db.Column(db.Integer, primary_key=True) 
    name = db.Column(db.String(30), nullable=False, unique=True) 

    @classmethod 
    def get_or_create(cls, name): 
     return cls.query.filter_by(name=name).scalar() or cls(name=name) 

class Post(db.Model): 
    __tablename__ = 'posts' 

    id = db.Column(db.Integer, primary_key=True) 
    title = db.Column(db.String(80), nullable=False) 
    content = db.Column(db.Text, nullable=False) 

    _tags = db.relationship('Tag', secondary=post_tags) 
    tags = association_proxy('_tags', 'name', creator=Tag.get_or_create) 

    def __init__(self, title, content, tags=None): 
     self.title = title 
     self.content = content 
     self.tags = tags 

私は文字列のリストを渡すと、それはTagオブジェクトのリストに変換する必要があり使用できるようにassociation_proxyを使用しています。文字列Tagの変換は、Postオブジェクトにtagsプロパティが設定されているときに発生します(たとえば、Postオブジェクトがインスタンス化された時点)。

、Pythonのコンソールで次の作品以上のモジュールからすべてをインポートした後:

>>> app.app_context().push() 
>>> db.create_all() 
>>> post1 = Post('Test', 'A test post', tags=['Test', 'Foo']) 
>>> db.session.add(post1) 
>>> db.session.commit() 
>>> post2 = Post('A second test', 'Another test post', tags=['Test']) 
>>> db.session.add(post2) 
>>> db.session.commit() 

を次のように、しかし、失敗します。

>>> app.app_context().push() 
>>> db.create_all() 
>>> post1 = Post('Test', 'A test post', tags=['Test', 'Foo']) 
>>> post2 = Post('A second test', 'Another test post', tags=['Test']) 
>>> db.session.add(post1) 
>>> db.session.add(post2) 
>>> db.session.commit() 

最後の行は、上のUNIQUE制約と文句を言いTag.nameが失敗する:

sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: 
    tag.name [SQL: 'INSERT INTO tag (name) VALUES (?)'] [parameters: ('Test',)] 

なぜこのようなことが起こるのですか?最初のケースではTestという名前のTagpost2が作成された時点ですでにデータベースにあります。 2番目の場合、db.session.newには、その名前のオブジェクトがコミット時に保持されていない2つのオブジェクトが含まれています。

私はそれを修正する方法がわかりません。 before_flush SQLAlchemyイベントを使用してオブジェクトをdb.session.newに統合しようと思ったが、動作させることができなかった。それが正しい戦略であるかどうかはわかりません。

StackOverflow集合知は洞察力や推奨事項を持っていますか?

答えて

1

get_or_createは、作成されたタグをセッションに追加する必要があります。そのため、後でそれを呼び出すと、セッション内のコミットされていないタグ・インスタンスを見つけて同じインスタンスを返すことができます。

@classmethod 
def get_or_create(cls, name): 
    tag = cls.query.filter_by(name=name).scalar() 
    if not tag: 
     tag = cls(name=name) 
     db.session.add(tag) 
    return tag 
+0

とても簡単です!ありがとう! –