Flask/WTForms Create and Customize Form From Model

I’ve been spending a lot of time working on a project using Flask. I banged my head against incomplete examples and docs for how to create a Form directly from a Model. Since I wasn’t able to find a complete example, I thought I’d write one up now that I’ve figured it out.

Problem: You have a large model and want to make a form to create or edit the model, but don’t want to have to create every form field in WTFform.

Context: I’m using this under Flask, using the WTForm extension. I’m also using Flask-Bootstrap for layout and want to be able to use its helper method to render the entire form.

In my app I need to create a new user/member for a club. You don’t need to know the specifics, just know there is a User object that includes not only all the standard authentication info, but also member kinds of information like address (A whole other blog post in itself), phone, date joined, rank, etc. Also every user can be part of multiple Clubs and has one Role defined via foreign key to a Role model.
Multiple Forms Page
I also have two ways to add users to a club. You can create the user manually via form, or you can upload a csv file and I’ll create the users from there and add it to the club. I decided to put the forms for both of these methods on the same page and allow the user to pick which one they wanted to use. But I wanted individual Routes/View methods to handle each form, which means there is one view method that creates both forms, but each form’s POST call goes to a different route.

I’m writing this post after implementing the manual entry form.

Solution:

A quick Google search will bring you to this snippet (Automatically create a WTForms Form from model) on the Flask site. Without it I wouldn’t even have know where to start, but it left a number of things to be desired.

As with most things in Flask, the first thing you need to do is create the view method in your views.py.

@main.route('/member/update/', methods=['GET', 'POST'])
@permission_required(Permission.MANAGE_CLUB)
def add_member_from_form():
    member_edit_form = AddMemberForm()
    if member_edit_form.validate_on_submit():
        new_user = User()
        member_edit_form.populate_obj(new_user)
    db.session.add(new_user)
    db.session.commit()
    return render_template('club_add_members.html',
        club=None,
        member_edit_form=member_edit_form)

First I created the route to the object. Since we’re going to be processing a form I added the POST method, though ultimately I’ll be removing it. Then I added the decorator to make sure only users with the proper permissions can do the add at all.

In the method I need to create the form. If you looked at the example post above, the form class is created right here, but in my app all the forms classes are created in a forms.py. So what you see here is normal usage of a form in a view method.

It is how we created this form that is what this post is all about, so let’s go over to forms.py and see what we had to do to make AddMemberForm().

First the imports, because too many tutorials leave that part out.

from flask.ext.wtf import Form
from wtforms.validators import DataRequired, Length
from wtforms.ext.sqlalchemy.orm import model_form
from ..models.user import User
from .. import db

You need to import the Form base class from the WTForm for later use, and you need to import that validators you want to use in your new form. You also need to import model_form from whatever ORM you are using, in my case that’s SQLAlchemy. model_form is where the magic happens.

Of course if you are going to create a Form from a Model you kind of need the model, in my case User.

You are also going to need your apps database session, called db in my case, if you need any foreign key based fields, which I did.

Note your import “paths” for these last two will vary depending on the location of forms.py and your model modules.

Now to create a form all you need to do is this:

AddMemberForm = model_form(model=User,
        base_class=Form,
        db_session=db.session)

That will create the AddMemberForm class and you can import it into your views.py with this line, just like any other form.

from .forms import NewClubNumberForm, AddMemberForm

Now to add it to my template so it shows up.

{% import "bootstrap/wtf.html" as wtf %}
... import the WTForm support from flask-Bootstrap ...
{{ wtf.quick_form(form=member_edit_form,
action=url_for('.add_member_from_form')) }}

One thing to note about this snippet. I didn’t want the form to just post back to the same view method. I wanted it to got to a specific other method on submit. I accomplished that by setting the action= value to point to the route I wanted the form to go to. This parameter is mentioned in the quick_form docs in the parameter list, but not in the documentation just below it.

If you were following along right now, you’d have run into a problem with your form. It wouldn’t have a submit button. Yep, model_form doesn’t add a submit button, but you can fix that by adding this line of code after your create the form.

AddMemberForm.submit = SubmitField('Create')

Which also shows you that you could add fields to your model form to do anything you wanted. They just wouldn’t be interpreted by the model.

Just following the Flask snippet example I was able to create a form on the page, but it had fields like password_hash, date_added etc. Fields I didn’t want the user to edit, so I had to hide those.

You can modify what fields model_form generates using the exclude_properties= parameter. Just create a simple list with the names of the fields you don’t want to see.

I submitted my form at this point and SQLAlchemy threw an exception because some of my required database fields hadn’t been set when I clicked submit. I guess it would me nice if model_form was smart enough to know when something was required and add a validator to enforce it, but it’s not.

You can add arguments to any field of the model with a dictionary via the field_args= parameter. Below is the complete code for creating the form with validators.

exclude_properties = ['password_hash', 'confirmed', 'date_added', 'full_address']
field_args = {
    'role': {'get_label': 'name'},
    'email': {'validators': [DataRequired()]},
    'username': {'validators': [DataRequired()]}
}
AddMemberForm = model_form(model=User,
        base_class=Form,
        db_session=db.session,
        exclude=exclude_properties,
        field_args=field_args)
AddMemberForm.submit = SubmitField('Create')

The role field_are value is also something interesting it took me a long time to figure out so I should mention it. When I first displayed the field for my one to many relationship between User and Role the form had a popup menu with the values ”, ”, ”. What that meant was WTForms or Bootstrap-WTF was using the __repr__ value for the name. This was not right. Digging into the code it is calling a method called get_label to return what it should use for the label. Well get_label looked a number of places for the label and finally use asks the object for its representation. Turns out you can set what get_label uses. That’s what we do here to get it to use the name field of Role.

That’s where I am right now and just wanted to write it up while still fresh on my mind. I expect there will be more to come. For one thing I need to let them create a password, which will involve adding a PasswordField to the form and then handling it by hashing the password and putting it in the password_hash.

Leave a Reply

Your email address will not be published. Required fields are marked *