简体   繁体   中英

Flask/WTF/SQLAlchemy: using QuerySelectMultipleField with Form.populate_obj()

I have a form (WTForms via Flask-WTF) that includes a QuerySelectMultipleField, something like this:

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

This works great—I just instantiate the form and pass it to my template for rendering, and all the right choices are there.

However, when I POST the form back and try to suck up the data with Form.populate_obj() , I get an angry message from SQLAlchemy:

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

The view function:

@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('/'))

So it looks like there's one session used when the form is created, and another when I'm trying to populate my model object. This is all happening under the hood, as I'm relying on Flask-SQLAlchemy to do all the session stuff for me.

In the Document model, the user field is declared this way:

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

(where of course shares is an instance of SQLAlchemy.table for a many-to-many relationship).

So: am I doing something wrong, or is Form.populate_obj() the problem, or perhaps I can blame aliens? Let me rephrase: What am I doing wrong?

Edit

The workaround this answer seems to fix the problem, namely changing my query_factory by importing my SQLAlchemy object and explicitly using its session:

query_factory=lambda: db.session.query(User)

I have to say, though, this has a weird smell to me. Is this really the best way to handle it?

It all depends on how your models classes are bound to a session. My guess is: you're not using the base class provided by Flask-SQLAlchemy: db.Model for your Document and User models ?

As stated in your ''edit'', by not using User 's query method, and using db.session.query instead, you are forcing populate_obj to use the same session that you will commit later with your db.session.commit call. That said, you are still probably using another session when doing Document.query.filter_by which most likely means you are still using 2 DB connections and could reduce it to one.

Overall, I would advise you to stay away from using the query method on your models (but that's because I don't like magic stuff ;) ), make sure to use Flask-SQLAlchemy's db.Model if you can and read in-depth how the framework / libraries you use work, as it's a very good habit to take, does not take a lot of time, and can significantly improve the quality and maintainability of your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM