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

太简单了!谢谢! –