Flask / WTF / SQLAlchemy:使用QuerySelectMultipleField和Form.populate_obj()

时间:2015-12-04 15:07:52

标签: python flask flask-sqlalchemy flask-wtforms

我有一个表格(WTForms via Flask-WTF),其中包含一个QuerySelectMultipleField,如下所示:

class EditDocumentForm(Form):
    # other fields omitted for brevity
    users = QuerySelectMultipleField('Select Users',
                             query_factory=User.query.all,
                             get_label=lambda u: u.username)

这很好用 - 我只是实例化表单并将其传递给我的模板进行渲染,并且所有正确的选择都在那里。

然而,当我回复表单并尝试用Form.populate_obj()填充数据时,我收到来自SQLAlchemy的愤怒消息:

InvalidRequestError: Object '<User at 0x10a4d33d0>' is already attached to session '1' (this is '3')

视图功能:

@app.route("/document/edit/<doc_id>", methods=['GET', 'POST'])
@login_required
def edit_document(doc_id):
    doc = Document.query.filter_by(id=doc_id).first()
    if (doc is not None) and (doc.user_id == current_user.id):
        form = EditDocumentForm(obj=doc)
        if request.method == "POST":
            if form.validate():
                form.populate_obj(doc)
                db.session.commit()
                return redirect('/')
            else:
                _flash_validation_errors(form)
        return render_template("edit.html", form=form)
    flash("The document you requested doesn't exist, or you don't have permission to access it.", "error")
    return(redirect('/'))

所以看起来在创建表单时会使用一个会话,而在我尝试填充模型对象时会使用另一个会话。这一切都在幕后发生,因为我依靠Flask-SQLAlchemy来为我做所有的会话。

Document模型中,user字段以这种方式声明:

users = db.relationship('User',
                       secondary=shares,
                       backref=db.backref('shared_docs', lazy='dynamic'))

(当然shares是多对多关系的SQLAlchemy.table实例。

所以:我做错了什么,或者问题是Form.populate_obj(),还是我可以责怪外星人?让我重新说一下:我做错了什么?

修改

解决方法this answer似乎解决了这个问题,即通过导入我的SQLAlchemy对象并显式使用其会话来更改我的query_factory

query_factory=lambda: db.session.query(User)
但是,我必须说,这对我来说有一种奇怪的气味。这真的是处理它的最好方法吗?

1 个答案:

答案 0 :(得分:0)

这完全取决于您的模型类如何绑定到会话。我的猜测是:您没有使用Flask-SQLAlchemy提供的基类:db.Model用于DocumentUser模型?

如您在编辑&#39;&#39;中所述,不使用User的{​​{1}}方法,并使用{{1}相反,您强制query使用您稍后将通过db.session.query调用提交的相同会话。也就是说,在执行populate_obj时,您仍然可能正在使用另一个会话,这很可能意味着您仍在使用2个数据库连接并且可以将其减少为一个。

总的来说,我建议你不要在你的模特身上使用db.session.commit方法(但那是因为我不喜欢神奇的东西;)),一定要使用Flask -SQLAlchemy的Document.query.filter_by如果可以并且深入了解你使用的框架/库是如何工作的,因为它是一个非常好的习惯,不需要花费很多时间,并且可以显着提高代码的质量和可维护性。