Skip to content
thesarfo

Note

Forms with Flask-WTF

Building forms with WT-Forms, setting a secret key, handling GET/POST and validate_on_submit, flashing messages, and redirecting after submit.

views 0
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length, Email

There are extensions out there that help you work with forms in flask. The most common one is called WT-Forms.

Then you need to create a file where you can put these forms. eg: “forms.py”

How this works is that you will write Python classes that will be representative of the forms and they will be automatically be converted into HTML forms in your template

class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email', validators=[DataRequired(), Email])
password = PasswordField('Password', validators=[DataRequired()])

this creates a RegistrationForm class and the first thing we want the user to enter into the form is a username, which is entered inside a StringField. The StringField takes the argument of what you want the user to type in(this is going to be used as a label in your template), as well as validators.

Validators are simply a list of limitations to what we don’t want the user to do when it comes to the username. eg: we want that field to be required*, and we dont want them to type a username less than 2 or more than 20 characters. All of the fields you are going to use have to be imported from the wtforms module. check the top of the file for reference

Below is a sample login class

class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')

Now when you use these forms, you need to set a secret key for your application. A secret key will protect against modifying cookies, and cross-site requests, forgery attacks etc. You just need to to set a secret key at the top of your app using the “app.config” keyword. see Below

app.config['SECRET_KEY'] = 'your-secret-key-here'

Usually your secret key has to be random values, and you can generate such values using the secrets module. To get a random string of characters use the secrets.token_hex(16) to get them. The 16 there is just the number of bytes. See Below

import secrets
secrets.token_hex(16)
# '57752jk62j26bjhkjbkjb2kj4b2b' (example output)

After all that is done you need to import the Forms you’ve created in your application, and create routes for them. Inside the route function, you need to create an instance of the form that you’re going to send to your application. see Below

@app.route('/register')
def register():
form = RegistrationForm()
return render_template('register.html', title='Register', form=form)

now you can pass this form to a template.


now when you set up everything in your form and decide to submit. you get an error that says “Method Not Allowed”. This is because it is submitting a post request back to the same register route with your form data but you currently dont accept post requests. So in order to accept post requests you need to add a list of allowed methods in your route.

This can be done by following the Below

@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
return render_template('register.html', title='Register', form=form)

This adds both get and post requests to your route and when you submit the form the error doesnt appear. But it just redirects you back to the register page. So you cannot tell if the form validated properly or not.

So before you render the template in your route you need to put a check in place that checks whether you have post data and that whether that data is valid. You can use the validate_on_submit() method to do that. right after you create your form but before you render the template. See below:

@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
flash(f'Account Created for {form.username.data}!', 'success') #the flash keyword basically creates some popup message. import it at the top of your application.
return render_template('register.html', title='Register', form=form)

The flash function takes a second argument called category. We will pass in the name of the bootstrap class that we want the function to inherit. bootstrap has different styling for successes and failures.

Now that the user has been registered, you have to redirect them to a different page obviously. So you have to redirect them to the homepage. import the redirect function. the redirect function takes one argument, thats the name of the function for that route you want to redirect the user to. see below:

@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
flash(f'Account Created for {form.username.data}!')
return redirect(url_for('home'))
return render_template('register.html', title='Register', form=form)

You then need to update your templates to show that flash messages. in this case i did that in my parent template

{% with messages = get_flashed_messages(with categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{category}}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}

the above is just an example


Another tip

To allow users to update username or email address, you need to create a form to do that. So open your forms.py file. And create a new form for your update account class. You can use the same format as the registration form but you wont need the password and confirm password options.

from flask_wtf.file import FileField, FileAllowed #...import these from flask_wtf to help upload a file

Add the below to your form

picture = FileField('Update Profile Photo', validators=[FileAllowed(['jpg', 'png'])])

And now make sure that the field is rendered in your template