Form rendering, validation, and CSRF protection for Flask with WTForms.
72
Flask-integrated form processing with automatic request data binding, validation, and CSRF token management. Flask-WTF extends WTForms with Flask-specific features for seamless web form handling.
Enhanced WTForms Form class with Flask integration, providing automatic data binding from Flask request context and built-in CSRF protection.
class FlaskForm(Form):
def __init__(self, formdata=_Auto, **kwargs):
"""
Initialize Flask-integrated form.
Args:
formdata: Form data source (_Auto for automatic Flask request binding,
None to disable, or custom MultiDict)
**kwargs: Additional WTForms Form arguments
"""
def is_submitted(self) -> bool:
"""
Check if form was submitted via POST, PUT, PATCH, or DELETE request.
Returns:
True if current request method indicates form submission
"""
def validate_on_submit(self, extra_validators=None) -> bool:
"""
Validate form only if it was submitted.
Shortcut for: form.is_submitted() and form.validate()
Args:
extra_validators: Additional validators to run
Returns:
True if form was submitted and validation passed
"""
def hidden_tag(self, *fields) -> Markup:
"""
Render form's hidden fields including CSRF token.
Args:
*fields: Specific fields to render (renders all hidden fields if none specified)
Returns:
HTML markup for hidden fields
"""Re-exported WTForms Form class for cases where Flask integration is not needed.
class Form:
"""
Base WTForms Form class without Flask integration.
Use FlaskForm for Flask applications.
"""from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length
class ContactForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(), Length(min=2, max=50)])
email = StringField('Email', validators=[DataRequired(), Email()])
message = TextAreaField('Message', validators=[DataRequired(), Length(min=10)])
submit = SubmitField('Send Message')FlaskForm automatically binds to Flask request data:
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm() # Automatically binds to request.form, request.files, or request.json
if form.validate_on_submit():
# Form was submitted and validation passed
name = form.name.data
email = form.email.data
message = form.message.data
# Process form data
send_email(name, email, message)
flash('Message sent successfully!')
return redirect(url_for('contact'))
# GET request or validation failed
return render_template('contact.html', form=form)from werkzeug.datastructures import MultiDict
# Disable automatic binding
form = ContactForm(formdata=None)
# Custom data source
custom_data = MultiDict([('name', 'John'), ('email', 'john@example.com')])
form = ContactForm(formdata=custom_data)
# JSON data handling (automatic for requests with content-type: application/json)
@app.route('/api/contact', methods=['POST'])
def api_contact():
form = ContactForm() # Automatically binds to request.json if content-type is JSON
if form.validate():
return {'status': 'success'}
return {'status': 'error', 'errors': form.errors}, 400<!-- Basic form template -->
<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
</head>
<body>
<form method="POST">
{{ form.hidden_tag() }} <!-- Includes CSRF token and other hidden fields -->
<div>
{{ form.name.label(class="form-label") }}
{{ form.name(class="form-control") }}
{% for error in form.name.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
<div>
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
<div>
{{ form.message.label(class="form-label") }}
{{ form.message(class="form-control", rows="4") }}
{% for error in form.message.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
</body>
</html># CSRF is automatically enabled for FlaskForm
class MyForm(FlaskForm):
name = StringField('Name')
# CSRF can be disabled for specific forms
class Meta:
csrf = False
# Custom CSRF configuration per form
class SecureForm(FlaskForm):
data = StringField('Data')
class Meta:
csrf = True
csrf_secret = 'form-specific-secret'
csrf_time_limit = 1800 # 30 minutes
csrf_field_name = 'security_token'from flask_wtf.file import FileField
from wtforms import StringField
class UploadForm(FlaskForm):
title = StringField('Title', validators=[DataRequired()])
file = FileField('File')
@app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm() # Automatically handles request.files
if form.validate_on_submit():
title = form.title.data
file = form.file.data # Werkzeug FileStorage object
if file:
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('success'))
return render_template('upload.html', form=form)# Custom validation
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
confirm = PasswordField('Confirm Password', validators=[DataRequired()])
def validate_username(self, field):
"""Custom field validator (method naming convention)"""
if User.query.filter_by(username=field.data).first():
raise ValidationError('Username already exists.')
def validate(self, extra_validators=None):
"""Custom form-level validation"""
if not super().validate(extra_validators):
return False
if self.password.data != self.confirm.data:
self.confirm.errors.append('Passwords must match.')
return False
return True
# Using with extra validators
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
# Additional runtime validators
extra_validators = {'username': [Length(min=3, max=20)]}
if form.validate_on_submit(extra_validators):
# Registration logic
return redirect(url_for('login'))
return render_template('register.html', form=form)from wtforms.meta import DefaultMeta
class CustomForm(FlaskForm):
class Meta(DefaultMeta):
# CSRF settings
csrf = True
csrf_secret = 'custom-csrf-key'
csrf_field_name = 'csrf_token'
csrf_time_limit = 3600
# Localization
locales = ['en_US', 'en']
def get_translations(self, form):
# Custom translation logic
return super().get_translations(form)Flask-WTF automatically handles different request data sources:
request.form (application/x-www-form-urlencoded)request.files and request.form (multipart/form-data)request.json (application/json)The data source is automatically selected based on the request content type and HTTP method.
Install with Tessl CLI
npx tessl i tessl/pypi-flask-wtfevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10