Forms - Flask-WTF
Like most of the Flask extensions they start with Flask-
in this case we will talk about Flask-WTF
FlaskWTF or Flask-WTF is a simple integration of WTFroms for flask including Cross-Site Request Forgery or CSRF (pronounced "seasurf" , file upload, and reCAPTCHA.
Installation¶
Features¶
- Integration with WTForms.
- Secure Form with CSRF token.
- Global CSRF protection.
- reCAPTCHA support.
- File upload that works with Flask-Uploads.
- Internationalization using Flask-Babel.
Example¶
Following the separation of concerns
it is a good practice to keep the forms in a different file/module, in the following example we will consider an app that will ask customer to log in, this log in form will include:
- Username.
- Password.
- Remember me check box.
- Submit Button.
The LoginForm class¶
application/forms.py
From the code we got:
FlaskForm
is the base form class and all the clases we will create will inherit from it, this class is part offlask_wtf
.- The 4 type of field are represented as classes and they came form
wtforms
, since theFlask-WTF
extension does not provide customized versions. - Each instance or object of the field class has as first argument a label, and the second and optional argument will be the validators, in this case we use the validator
DataRequired()
Form Templates¶
We have the form, now 2 things are missing the template and the view function, in this section we will talk about the template.
We will follow the template inheritance and will will be using {% extend "base.html" %}
to extend inherit from the base template.
we will create a new template login.html
and it will be same in the template folder (in more complex application this might be different).
application/templates/login.html
From the script we have:
- The template expect a object instance of the
LoginForm
that object is refer asform
we will see in the view function were we need to pass that object and how we create the instance. - We use the tag
<form>
to contain the web form. - The
action
attribute of the form is used to tell the browser the URL that should be used when submitting the information the user entered in the form. When theaction
is set to an empty string the form is submitted to the URL that is currently in the address bar, which is the URL that rendered the form on the page. - The method is set as "post".
- The
novalidate
attribute is used to tell the web browser to not apply validation to the fields in this form, which effectively leaves this task to the Flask application running in the server This is optional, but we use it in this case so we can test the validation in the server-side. form.hidden_tag()
template argument generates a hidden field that includes a token that is used to protect the form against CSRF attacks. All you need to do to have the form protected is include this hidden field and have the SECRET_KEY variable defined in the Flask configuration.
IMPORTANT: if we check the template in detail we notice that there are not <input >
tags this is because he fields from the form object know how to render themselves as HTML.
1. All I needed to do was to include {{ form.<field_name>.label }}
where I wanted the field label, and {{ form.<field_name>() }}
where I wanted the field.
2. To modify the field we can use the attributes that will be in th <input>
example, size
argument.
Form Views¶
The last step need it is the form view, the view function that will render the template. This new view function can be add it to a routes module as follow
application/routes.py
What are we doin is importing the calls LoginForm
from the module forms.py
and we create and instant object of that class called form
later in render_template
we pass that object form
to the variable form
this variable is the required for to get the fields rendered
Now we can add a link to login in or base template
However if we click in submit button we will have
This is because we don't have the logic to handle the request using POST
and the form is using post top send the information
Receiving Form Data¶
In order to the the request or to be able to handle the request we need to add some extra code to the view function
application/routes.py
from the previous code:
- We add
methods
to the decorator, the default method in the decorator will beGET
so in order to support the post request done when the form is submitted we need to let the view function knows. - the
form.validate_on_submit()
method does all the form processing work, it work in to "phases", first, when the page request the form usingGET
the functionform.validate_on_submit()
returnfalse
so the if block is skip and the page render the form. Second, when user press submit the request is done withPOST
in which case the functionform.validate_on_submit()
, if the validation is correct, it will returnTrue
and the code in the if block will be executed. flash()
Is a function to show a message to the user, in this case since we dont have a database to compare the login credentials we will use it to send a confirmation that the form was submitted correctly, bellow we will modify the template to be able to display theflash()
message.redirect()
this function give instructions to the client web browser to automatically navigate to a different page, given as argument.
appplication/templates/base.html
as we mentioned before flash()
is a function use to give messages to the user, but to be able to see it we need to modify the templates to do it, in this case we are going to modify the base.html
template.
{% with messages = get_Flashed_messages() %}
thewith
is a construct that we are going to use to assign the return value ofget_flashed_messages()
to the variablemessages
, all in the context of this template.get_flashed_messages()
this function come from Flask and it returns a list of messages register toflash()
.{% if messages %}
is a condition to check if the variable has some content, if the variable has some content we will use aHTML
list and a jinja{% for message in messages %}
to loop and display all the messages in the list.
Improving the Field Validation¶
For the most part the validation and the form are working, although if we fail during the validation we don't know what is the filed that failed and we don't have any way to alert the user that there is a mistake or an error in the form.
We can create some messages in the login.html template so if the customer fail in some field we can give the feedback
application/templates/login.html
From the previous template we can see the changes, in this case we are using form.username.errors
and form.password.errors
to check if there is any error in this field, the validator attached to every field will be form.<filed_name>.errors)
Generating Links¶
For now the login is basically complete, but we have some hardcoded URLs whihc is not a goo practice, so far we have:
application/templates/base.html
andapplication/routes.html
The good practice will be, generate those URLs using url_for()
whihc generate the URLs bas in the internal mapping, The argument of url_for()
will be the name of the view function, so for example, url_for('login')
because the view function is called /login
will returns /login, and same for url_for('index')
return /index
.
So the previous links will be
application/templates/base.html
application/routes.html