Flask Form Rendering
Forms are a fundamental part of web applications, enabling users to submit data to your application. Flask provides elegant ways to handle forms through templates. In this tutorial, you'll learn how to create, render, and process forms in Flask applications.
Introduction to Forms in Flask
In web applications, forms serve as the primary interface for user input. Whether it's a login form, a registration form, or a simple contact form, handling these inputs properly is crucial for a good user experience.
Flask doesn't come with built-in form handling capabilities, but it offers several approaches:
- Manual form handling using request objects
- Using the Flask-WTF extension (a Flask integration for WTForms)
We'll explore both approaches, starting with the simpler manual approach and then moving to the more powerful Flask-WTF.
Basic Form Handling in Flask
Creating a Simple HTML Form
Let's start by creating a simple form in an HTML template:
<!-- templates/basic_form.html -->
<!DOCTYPE html>
<html>
<head>
<title>Basic Form Example</title>
</head>
<body>
<h1>Contact Form</h1>
<form method="POST" action="{{ url_for('submit_form') }}">
<div>
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="message">Message:</label>
<textarea id="message" name="message" rows="4" required></textarea>
</div>
<button type="submit">Send Message</button>
</form>
</body>
</html>
Processing Form Submission in Flask
Now, let's handle this form's submission in our Flask application:
from flask import Flask, request, render_template, redirect, url_for, flash
app = Flask(__name__)
app.secret_key = 'your_secret_key' # Required for flashing messages
@app.route('/contact', methods=['GET'])
def contact_form():
return render_template('basic_form.html')
@app.route('/submit_form', methods=['POST'])
def submit_form():
name = request.form.get('name')
email = request.form.get('email')
message = request.form.get('message')
# Here you would typically save to a database or send an email
# For demonstration, let's just print the data
print(f"Form submitted: {name} ({email}) - {message}")
# Flash a success message
flash('Your message has been sent successfully!')
# Redirect to prevent form resubmission on page refresh
return redirect(url_for('contact_form'))
if __name__ == '__main__':
app.run(debug=True)
Showing Flash Messages in Template
To display the flash messages in our template, we need to modify it slightly:
<!-- templates/basic_form.html -->
<!DOCTYPE html>
<html>
<head>
<title>Basic Form Example</title>
<style>
.flash-message {
padding: 10px;
background-color: #dff0d8;
color: #3c763d;
border: 1px solid #d6e9c6;
border-radius: 4px;
margin-bottom: 15px;
}
</style>
</head>
<body>
<h1>Contact Form</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash-message">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('submit_form') }}">
<!-- Form fields remain the same -->
</form>
</body>
</html>
Advanced Form Handling with Flask-WTF
For more complex forms, validation, and security features like CSRF protection, Flask-WTF is the recommended approach.
Installing Flask-WTF
First, install Flask-WTF:
pip install flask-wtf
Creating a Form Class
With Flask-WTF, you define your form as a Python class:
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, EmailField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Email, Length
class ContactForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(), Length(min=2, max=100)])
email = EmailField('Email', validators=[DataRequired(), Email()])
message = TextAreaField('Message', validators=[DataRequired(), Length(min=10)])
submit = SubmitField('Send Message')
Rendering the Form in a Template
Now let's use this form in our Flask application:
# app.py
from flask import Flask, render_template, redirect, url_for, flash
from forms import ContactForm
app = Flask(__name__)
app.secret_key = 'your_secret_key'
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if form.validate_on_submit():
# Process valid form data
print(f"Form submitted: {form.name.data} ({form.email.data}) - {form.message.data}")
flash('Your message has been sent successfully!')
return redirect(url_for('contact'))
return render_template('contact_form.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
And our template:
<!-- templates/contact_form.html -->
<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
<style>
.flash-message {
padding: 10px;
background-color: #dff0d8;
color: #3c763d;
border: 1px solid #d6e9c6;
border-radius: 4px;
margin-bottom: 15px;
}
.form-error {
color: #a94442;
font-size: 0.9em;
}
.form-field {
margin-bottom: 15px;
}
</style>
</head>
<body>
<h1>Contact Us</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash-message">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" novalidate>
{{ form.hidden_tag() }}
<div class="form-field">
{{ form.name.label }}
{{ form.name() }}
{% if form.name.errors %}
<div class="form-error">
{% for error in form.name.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-field">
{{ form.email.label }}
{{ form.email() }}
{% if form.email.errors %}
<div class="form-error">
{% for error in form.email.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-field">
{{ form.message.label }}
{{ form.message() }}
{% if form.message.errors %}
<div class="form-error">
{% for error in form.message.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{{ form.submit() }}
</form>
</body>
</html>
Customizing Form Fields and Styling
You can customize the form fields by passing HTML attributes:
{{ form.name(class="form-control", placeholder="Enter your name") }}
For a complete form with Bootstrap styling:
<!-- templates/bootstrap_form.html -->
<!DOCTYPE html>
<html>
<head>
<title>Bootstrap Form Example</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1>Contact Us</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-success">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" class="mt-4" novalidate>
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.name.label(class="form-label") }}
{{ form.name(class="form-control", placeholder="Your full name") }}
{% if form.name.errors %}
<div class="text-danger">
{% for error in form.name.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control", placeholder="[email protected]") }}
{% if form.email.errors %}
<div class="text-danger">
{% for error in form.email.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.message.label(class="form-label") }}
{{ form.message(class="form-control", rows=5, placeholder="Your message here...") }}
{% if form.message.errors %}
<div class="text-danger">
{% for error in form.message.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
</div>
</body>
</html>
Real-World Example: Registration Form
Let's create a more practical example - a user registration form with validation:
Form Class
# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
import re
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[
DataRequired(),
Length(min=4, max=20)
])
email = StringField('Email', validators=[
DataRequired(),
Email()
])
password = PasswordField('Password', validators=[
DataRequired(),
Length(min=8)
])
confirm_password = PasswordField('Confirm Password', validators=[
DataRequired(),
EqualTo('password', message='Passwords must match')
])
agree_terms = BooleanField('I agree to the Terms and Conditions', validators=[
DataRequired(message='You must agree to the terms to register')
])
submit = SubmitField('Register')
def validate_password(self, password):
# Check for at least one uppercase letter, one lowercase letter,
# one number, and one special character
pattern = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$'
if not re.match(pattern, password.data):
raise ValidationError(
'Password must contain at least 8 characters, including uppercase, '
'lowercase, number and special character'
)
Flask Route
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# In a real application, you would hash the password and save to database
print(f"Registration success: {form.username.data} ({form.email.data})")
flash('Registration successful! You can now log in.')
return redirect(url_for('login')) # Redirect to login page
return render_template('register.html', form=form)
Template
<!-- templates/register.html -->
<!DOCTYPE html>
<html>
<head>
<title>Register</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="mb-0">Register</h3>
</div>
<div class="card-body">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-success">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" novalidate>
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control", placeholder="Choose a username") }}
{% if form.username.errors %}
<div class="text-danger">
{% for error in form.username.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control", placeholder="Your email address") }}
{% if form.email.errors %}
<div class="text-danger">
{% for error in form.email.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
{% if form.password.errors %}
<div class="text-danger">
{% for error in form.password.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.confirm_password.label(class="form-label") }}
{{ form.confirm_password(class="form-control") }}
{% if form.confirm_password.errors %}
<div class="text-danger">
{% for error in form.confirm_password.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
<div class="mb-3 form-check">
{{ form.agree_terms(class="form-check-input") }}
{{ form.agree_terms.label(class="form-check-label") }}
{% if form.agree_terms.errors %}
<div class="text-danger">
{% for error in form.agree_terms.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>
{{ form.submit(class="btn btn-primary w-100") }}
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Best Practices for Form Handling
- Always use CSRF protection - Flask-WTF provides this by default with the
form.hidden_tag()
method - Validate on the server - Even if you have client-side validation, always validate on the server as well
- Use the POST-Redirect-GET pattern - After a successful form submission, redirect to avoid form resubmission
- Provide clear error messages - Help users understand what they need to fix
- Add appropriate HTML5 input types - Use types like
email
,number
,date
for better mobile experience - Use flash messages - To provide feedback to users after form processing
- Sanitize inputs - Always clean and sanitize user inputs before processing or storing them
Summary
In this tutorial, you've learned how to:
- Create basic HTML forms in Flask templates
- Process form submissions using Flask's request object
- Use Flask-WTF to create more robust forms with validation
- Display validation errors in templates
- Style forms using Bootstrap
- Implement a real-world registration form with advanced validation
- Apply best practices for form handling
Forms are a critical component of web applications, and Flask provides flexible options for handling them. Starting with simple manual forms and progressing to more robust solutions with Flask-WTF allows you to choose the right approach for your specific needs.
Additional Resources
- Official Flask-WTF Documentation
- WTForms Documentation
- Flask Flash Messages Documentation
- CSRF Protection in Flask
Exercises
- Create a login form that validates email and password, with a "Remember me" checkbox.
- Build a multi-page form (wizard) where data is stored in the session between pages.
- Create a form that includes file upload functionality using Flask-WTF.
- Implement a form with dynamic fields that change based on user selections.
- Build a search form with multiple optional filter criteria.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)