我有一个表格(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)
但是,我必须说,这对我来说有一种奇怪的气味。这真的是处理它的最好方法吗?
答案 0 :(得分:0)
这完全取决于您的模型类如何绑定到会话。我的猜测是:您没有使用Flask-SQLAlchemy提供的基类:db.Model
用于Document
和User
模型?
如您在编辑&#39;&#39;中所述,不使用User
的{{1}}方法,并使用{{1}相反,您强制query
使用您稍后将通过db.session.query
调用提交的相同会话。也就是说,在执行populate_obj
时,您仍然可能正在使用另一个会话,这很可能意味着您仍在使用2个数据库连接并且可以将其减少为一个。
总的来说,我建议你不要在你的模特身上使用db.session.commit
方法(但那是因为我不喜欢神奇的东西;)),一定要使用Flask -SQLAlchemy的Document.query.filter_by
如果可以并且深入了解你使用的框架/库是如何工作的,因为它是一个非常好的习惯,不需要花费很多时间,并且可以显着提高代码的质量和可维护性。