6

背景:Flask / Flask-SQLAlchemy / Flask-WTF,使用声明性和作用域会话

操作简单POST

@tas.route('/order_add', methods=['GET', 'POST'])  
def tas_order_add():  
    if request.method == 'POST':
        order_form = OrderForm()
        if order_form.validate_on_submit():
            order = Order()
            order_form.populate_obj(order)
            db_session.add(order)
            db_session.commit()

现在尝试运行它我得到一个错误:

InvalidRequestError:对象“”已附加到会话“1”(这是“2”)

将 add 更改为 merge 可以解决问题,但是:

  • 我不知道为什么我刚启动它时必须合并一个对象
  • 如果我确实更改添加以合并并尝试定义其中一个属性

    order = Order()
    order_form.populate_obj(order)
    order.order_status = OrderStatus.query.filter(OrderStatus.code=='PLACED').first()
    db_session.merge(order)
    db_session.commit()
    

    我得到了同样的错误,就在 OrderStatus 对象上

    InvalidRequestError:对象“”已附加到会话“2”(这是“1”)

有人可以指出我做错了什么,因为它让我发疯。我确实有一些使用 SQLAlchemy 的经​​验,但这是我第一次看到这种行为,我无法查明问题所在。

搜索我发现的所有问题都是双数据库会话初始化的问题,但我不相信是这种情况。

编辑

db_session 在单独的文件 database.py 中定义,内容如下

from sqlalchemy.engine import create_engine
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker

engine = create_engine('sqlite:///fundmanager_devel.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
4

3 回答 3

6

似乎是会话混淆的问题,我按如下方式使用它并且它有效:

from <project> import db
from <project.models import Category

category = QuerySelectField('category', query_factory=lambda: db.session.query(Category), get_pk=lambda a: a.id, get_label=lambda a: a.name)
于 2013-12-20T14:37:32.097 回答
5

我对 QuerySelectField 也有类似的问题:

表格.py:

class SurgeryForm(Form):
    study = QuerySelectField('Study',
                             query_factory=StudyGroup.query.all,
                             get_label='name')

模型.py

class Animal(db.Model):
    study_id = db.Column(db.Integer, db.ForeignKey('study_group.id'))

class StudyGroup(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    animals = db.relationship('Animal', backref='group', lazy='dynamic')

视图.py:

def do_surgery():
    form = SurgeryForm(request.form)

    if form.validate_on_submit():
        a = models.Animal()
        form.populate_obj(a)  # Gather the easy stuff automagically
        #a.group = form.data['study']  #FAILS!
        a.study_id = form.data['study'].id  #WORKS!

似乎 SQLAlchemy(或者可能是 Flask-SQLAlchemy 或 Flask-WTF)使用一个会话来收集 QuerySelectField 中的值,并使用另一个会话来创建新的 Animal 对象。

如果您尝试使用 backref (Animal.group) 将 StudyGroup 对象附加到 Animal,您将遇到此问题(因为对象与不同的会话相关联)。我正在使用的解决方法是直接设置外键(Animal.study_id)。

显然,我的这个答案有点晚了,但我希望它对某人有所帮助!

于 2014-04-11T17:09:38.177 回答
4

这很奇怪。如果您使用的是flask-sqlalchemy,为什么要显式创建引擎和声明性基础?这很可能是你的问题所在。看起来您有两个引擎和会话同时运行,这就是您收到错误的原因。

而不是显式创建引擎,您应该只使用:

from flask.ext.sqlalchemy import SQLAlchemy

db = SQLAlchemy()

在您的应用工厂内部:

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///fundmanager_devel.db'
db.init_app(app) 

然后你的基本声明模型是db.Model,你的会话在db.session,你应该让烧瓶请求上下文管理会话创建。

检查 Flask-SQLAlchemy 文档中的最小应用程序示例:

http://pythonhosted.org/Flask-SQLAlchemy/quickstart.html#a-minimal-application

这是 SQLAlchemy 推荐的方式:

大多数 Web 框架都包括用于建立与请求相关联的单个 Session 的基础设施,该 Session 会在请求结束时正确构建和拆除相应的拆除。此类基础设施部分包括 Flask-SQLAlchemy 等产品,用于与 Flask Web 框架结合使用,以及 Zope-SQLAlchemy,用于与 Pyramid 和 Zope 框架结合使用。SQLAlchemy 强烈建议尽可能使用这些产品。

http://docs.sqlalchemy.org/en/rel_0_9/orm/session.html

于 2013-11-04T15:30:12.800 回答