Python 多对多关系:获取或创建

Python 多对多关系:获取或创建,python,sqlalchemy,many-to-many,flask-sqlalchemy,Python,Sqlalchemy,Many To Many,Flask Sqlalchemy,我正在为一个博客开发一个标签系统。下面是创建Flask app对象和相关的Post和Tag模型的精简版代码 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

我正在为一个博客开发一个标签系统。下面是创建Flask app对象和相关的
Post
Tag
模型的精简版代码

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
我正在使用
关联\u代理
来传递字符串列表,并将其转换为
标记
对象列表。请注意,字符串到-
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()
最后一行抱怨对
标记.name
唯一
约束失败:

sqlalchemy.exc.IntegrityError:(sqlite3.IntegrityError)唯一约束失败:
tag.name[SQL:'插入标记(名称)值(?)][参数:('Test',)]
我理解为什么会发生这种情况:在第一种情况下,当创建
post2
时,数据库中已经存在一个名为
Test
Tag
;在第二个文件中,
db.session.new
包含两个具有该名称的
Tag
对象,它们在提交时未被持久化

我不知道怎么修理它。我曾想过使用
before\u flush
SQLAlchemy事件在
db.session.new
中合并
Tag
对象,但我无法使其工作。我不确定这是否是正确的策略


StackOverflow集体智慧是否有任何见解或建议?

您的get\u或\u 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

太简单了!谢谢