python - 多对多关系 : get-or-create

标签 python sqlalchemy many-to-many flask-sqlalchemy

我正在为博客开发标签系统。下面是创建 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 的转换发生在 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()

最后一行提示 Tag.name 上的 UNIQUE 约束失败:

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集体智慧有什么见解或建议吗?

最佳答案

您的 get_or_create 需要将创建的标签添加到 session 中,以便后续调用它可以找到 session 中未提交的标签实例并返回相同的实例。

@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

关于python - 多对多关系 : get-or-create,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40569676/

相关文章:

python - 如何完全关闭 Flask 和 SQLAlchemy 上的 postgresql 事务空闲?

python - peewee:过滤从多对多关系中选择查询结果

java - 是否可以将两个实体绑定(bind)为 ManyToMany 而无需额外的类?

python - Pandas:时间戳转换为 YYYY-MM-DD

python - 引导操作成功后,节点预配器中的EMR从引导失败

python - InternetCrackUrlW 不会填充结构化类中指针后面的字符串值

mapping - Entity Framework 代码优先 - 多对多外键问题

python - 如何理解和解析默认的 Python 对象表示

python - 如何在Python中使用SQLalchemy过滤SQL表中的日期时间列?

python - SQLAlchemy jsonb 列 - 如何根据键上的 id 列表执行过滤