Flask-WTF is a Flask extension that integrates the WTForms library, making form creation and validation easier in Flask applications. It provides a structured way to build forms, handle validation, and render them in HTML. In this article, we'll explore how Flask-WTF works by building a Signup form. Before diving in, let's go over its features, field types, and prerequisites.
- Secure Form Handling: Automatically manages CSRF protection to prevent unauthorized submissions.
- Easy Form Rendering: Supports various field types like text fields, checkboxes, and dropdowns for smooth HTML integration.
- Built-in Validation: Includes required fields, length constraints, pattern matching, and support for custom validation.
- File Uploads: Allows users to upload files through forms seamlessly.
Prerequisites: Knowledge of Python, HTML, CSS, Javascript
Installation
To use Flask-WTF, we first need to install it using pip and import it into our application.
pip install flask-wtf
In Flask-WTF, forms are defined as classes that extend the FlaskForm class. Fields are declared as class variables, making form creation simple and structured.
Common WTForms Field Types:
- StringField: Text input field for string data.
- PasswordField: Input field for password values.
- BooleanField: Checkbox for True/False selection.
- DecimalField: Input field for decimal values.
- RadioField: Group of radio buttons for single selection.
- SelectField: Dropdown list for single selection.
- TextAreaField: Multi-line text input field.
- FileField: File upload field.
Custom Validators in Flask-WTF
Flask-WTF provides built-in validators for common validation tasks, but custom validators can be created when application-specific validation rules are required. Custom validators help enforce additional constraints beyond the standard validation options.
Example: The following validator ensures that a username contains only alphabetic characters:
from wtforms import StringField
from wtforms.validators import ValidationError
def validate_username(form, field):
if not field.data.isalpha():
raise ValidationError(
'Username must contain only letters.'
)
class SignupForm(FlaskForm):
username = StringField(
'Username',
validators=[validate_username]
)
Explanation:
- Create a Validation Function: The custom validator receives the form and field objects as arguments.
- Apply Validation Logic: The function checks whether the submitted value satisfies the required condition.
- Raise ValidationError: If validation fails, a ValidationError is raised with an appropriate error message.
- Attach Validator to a Field: The custom validator is added to the field's validators list and is executed during form validation.
Flask - WTF Examples
Example 1
Building a sample form using the above field types
# Importing Libraries..
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField
from wtforms import DecimalField, RadioField, SelectField, TextAreaField, FileField
from wtforms.validators import InputRequired
from werkzeug.security import generate_password_hash
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secretkey'
class MyForm(FlaskForm):
name = StringField('Name', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()])
remember_me = BooleanField('Remember me')
salary = DecimalField('Salary', validators=[InputRequired()])
gender = RadioField('Gender', choices=[
('male', 'Male'), ('female', 'Female')])
country = SelectField('Country', choices=[('IN', 'India'), ('US', 'United States'),
('UK', 'United Kingdom')])
message = TextAreaField('Message', validators=[InputRequired()])
photo = FileField('Photo')
@app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
if form.validate_on_submit():
name = form.name.data
password = form.password.data
remember_me = form.remember_me.data
salary = form.salary.data
gender = form.gender.data
country = form.country.data
message = form.message.data
photo = (
form.photo.data.filename
if form.photo.data
else "No file uploaded"
)
return (
f'Name: {name} <br>'
f'Password: {generate_password_hash(password)} <br>'
f'Remember me: {remember_me} <br>'
f'Salary: {salary} <br>'
f'Gender: {gender} <br>'
f'Country: {country} <br>'
f'Message: {message} <br>'
f'Photo: {photo}'
)
return render_template('index.html', form=form)
if __name__ == '__main__':
app.run()
index.html
<!DOCTYPE html>
<html>
<head>
<title>My Form</title>
</head>
<body>
<h1>My Form</h1>
<form method="post" action="/" enctype="multipart/form-data">
{{ form.csrf_token }}
<p>{{ form.name.label }} {{ form.name() }}</p>
<p>{{ form.password.label }} {{ form.password() }}</p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.salary.label }} {{ form.salary() }}</p>
<p>{{ form.gender.label }} {{ form.gender() }}</p>
<p>{{ form.country.label }} {{ form.country() }}</p>
<p>{{ form.message.label }} {{ form.message() }}</p>
<p>{{ form.photo.label }} {{ form.photo() }}</p>
<p><input type="submit" value="Submit"></p>
</form>
</body>
</html>
Explanation:
- Created a Form Class: MyForm inherits from FlaskForm and includes various fields like StringField, PasswordField, BooleanField, etc., with appropriate validators.
- Passed Form to Template: In the index function, an instance of MyForm is created and sent to index.html.
- Handled Form Submission: The validate_on_submit method checks if the form is valid before processing data.
- Retrieved and Displayed Data: If valid, form data is extracted and shown in the browser.
- Secured Passwords: Used generate_password_hash to encrypt passwords for better security.
Output:
Example 2
Let's see another example of a simple Sign-up form using the Flask-WTF library where we use the field types mentioned above in our application.
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField ,SubmitField
from wtforms.validators import InputRequired, Length
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
# Using StringField, PasswordField andSubmitField from Flask-WTForms library
# for username,password fields and submit button..
class LoginForm(FlaskForm):
username = StringField('Username', validators=[InputRequired('Username required!'),
Length(min=5, max=25, message='Username must be in 5 to 25 characters')])
password = PasswordField('Password',validators=[InputRequired('Password required')])
submit = SubmitField('Submit')
@app.route('/Signup', methods=['GET', 'POST'])
def form():
form = LoginForm()
if form.validate_on_submit():
return '<h1>Hi {}!!. Your form is submitted successfully!!'.format(form.username.data)
return render_template('Signup.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
Signup.html code
<!DOCTYPE html>
<html>
<head>
<title>Flask Form</title>
</head>
<body>
<h1>Signup </h1>
<form method="POST" action="{{ url_for('form') }}">
{{ form.hidden_tag() }}
{{ form.username.label }}
{{ form.username }}
<br>
<br>
{{ form.password.label }}
{{ form.password }}
<br>
<br>
{{ form.submit }}
</form>
</body>
</html>
Explanation:
- {{ form.hidden_tag() }} renders all hidden fields automatically, including the CSRF token.
- {{ form.username.label }} renders an HTML label for the username field of the form.
- {{ form.username }} renders an HTML input element for the username field of the form.
- {{ form.password.label }} renders an HTML label for the password field of the form.
- {{ form.password }} renders an HTML input element for the password field of the form.
- {{ form. submit }} renders an HTML input element for the form's submit button.
The form is validated using validate_on_submit(). After successful validation, the submitted data is processed and a confirmation message is displayed with the username.
Output:
Common WTForms Validators
Flask-WTF provides several built-in validators that help ensure submitted data meets specific requirements before it is processed.
| Validator | Purpose |
|---|---|
| InputRequired() | Ensures a value is provided |
| Length() | Restricts input length |
| Email() | Validates email addresses |
| NumberRange() | Restricts numeric values |
| EqualTo() | Compares two fields |