how to instantiate wtforms.ext.sqlalchemy.fields.QuerySelectMultipleField with SQLAlchemy many to many relationship

StackOverflow https://stackoverflow.com/questions/18815379

Question

I am attempting to display a list of checkboxes using wtforms.ext.sqlalchemy QuerySelectMultipleField, but I cannot get the model data to display on the form on a GET.

Here is my models.py

user_permissions = db.Table('user_permissions',
    db.Column('permission_id', db.Integer, db.ForeignKey('permission.id')),
    db.Column('user_id', db.Integer, db.ForeignKey('user.id'))
)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), nullable=False, unique=True)
    email = db.Column(db.String(120), nullable=False, unique=True)
    password = db.Column(db.String(80), nullable=False)

    permissions = db.relationship('Permission', secondary=user_permissions, 
            backref=db.backref('permissions', lazy='dynamic'))

    def __init__(self, username, email, password):
        self.username = username
        self.email = email
        self.password = password

    def __repr__(self):
        return '<User %r>' % self.username


class Permission(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    desc = db.Column(db.String(80), nullable=False)

    def __init__(self, desc):
        self.desc = desc

    def __repr__(self):
        return '<Permission %r>' % self.desc

Here is my forms.py

class EditUserForm(Form):
    username = TextField('Name', validators=[DataRequired()])
    email = TextField('Email', validators=[DataRequired()])
    permissions = QuerySelectMultipleField(
            query_factory=Permission.query.all,
            get_label=lambda a: a.desc,
            widget=widgets.ListWidget(prefix_label=False),
            option_widget=widgets.CheckboxInput()
    )

Here is my views.py

@app.endpoint('edit_user')
@require_login()
@require_permission('edit_user')
def edit_user(user_id):
    user = User.query.filter_by(id=user_id).first()
    if not user:
        abort(404)
    data = {
        "username": user.username,
        "email": user.email,
        "permissions": user.permissions
    }
    form = EditUserForm(**data)

The problem, is that the WTForm doesn't display the selected values from user.permissions, which is a list of Permission models. It displays a list of empty checkboxes.

(Pdb) print user.permissions
[<Permission u'admin'>, <Permission u'create_user'>, <Permission u'edit_user'>]

I am fairly certain the issue lies within how the "permissions" data value is structured, but I have tried every possibility I can think of. Any help would be appreciated.

Some things I've tried...

"permissions": [1, 2]
"permissions": ['1', '2']
"permissions": ['permissions-1', 'permissions-2']
"permissions": [('permissions', 1), ('permissions', 2)]
"permissions": [('permissions', '1'), ('permissions', '2')]
Was it helpful?

Solution

A couple of issues.

1) The way you did the query_factory could potentially cause an issue, as it potentially associates with a single session/query factory. This would mean the objects you're comparing to are not from the same session as the objects in the query factory.

A safer way is to do this:

    query_factory=lambda: Permission.query.all()

2) I assume you're using Flask-WTF, since you didn't pass a formdata portion, but if you aren't (you're using wtforms.Form) then do this:

form = EditUserForm(request.form)

3) You passed a single-use iterator as a collection:

    "permissions": user.permissions

If you really need to customize the passed in data via a dict, you should do it like this:

    "permissions": list(user.permissions)

However, because of how basic your dict is, it's much preferred to simply pass the data as an object (and this should avoid the single-use iterator issue)

form = EditUserForm(obj=user)

Combining this all together, your view should look like this:

def edit_user(user_id):
    user = User.query.filter_by(id=user_id).first()
    if not user:
        abort(404)

    form = EditUserForm(request.form, user)
    [...]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top