2016-11-13 135 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 

我使用的是association_proxy能夠使用傳遞的字符串列表,並把它轉化爲Tag對象的列表。請注意,字符串至Tag轉換髮生在tags屬性設置爲Post對象時(例如,在實例化對象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',)] 

我明白爲什麼會發生這種情況:在第一種情況下,當post2被創建時,名稱爲TestTag已經在數據庫中;在第二個中,db.session.new包含兩個Tag對象,該對象在提交時沒有被保存。

我不知道的是如何解決它。我想過使用before_flush SQLAlchemy事件來整合db.session.new中的Tag對象,但我無法使其工作。我不確定這是否是正確的策略。

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

太簡單了!謝謝! –