我正在为博客制作标记系统。以下是创建Flask应用对象以及相关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
我使用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',)]
我理解为什么会发生这种情况:在第一种情况下,创建Tag
时,名为Test
的{{1}}已经在数据库中;在第二个中,post2
包含两个具有该名称的db.session.new
个对象,这些对象在提交时尚未保留。
我不知道的是如何修复它。我想过使用Tag
SQLAlchemy事件来合并before_flush
中的Tag
个对象,但我无法使其工作。我不确定这是否是正确的策略。
StackOverflow集体智慧是否有任何见解或建议?
答案 0 :(得分: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