0
# Form Handling
1
2
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.
3
4
## Capabilities
5
6
### FlaskForm Class
7
8
Enhanced WTForms Form class with Flask integration, providing automatic data binding from Flask request context and built-in CSRF protection.
9
10
```python { .api }
11
class FlaskForm(Form):
12
def __init__(self, formdata=_Auto, **kwargs):
13
"""
14
Initialize Flask-integrated form.
15
16
Args:
17
formdata: Form data source (_Auto for automatic Flask request binding,
18
None to disable, or custom MultiDict)
19
**kwargs: Additional WTForms Form arguments
20
"""
21
22
def is_submitted(self) -> bool:
23
"""
24
Check if form was submitted via POST, PUT, PATCH, or DELETE request.
25
26
Returns:
27
True if current request method indicates form submission
28
"""
29
30
def validate_on_submit(self, extra_validators=None) -> bool:
31
"""
32
Validate form only if it was submitted.
33
Shortcut for: form.is_submitted() and form.validate()
34
35
Args:
36
extra_validators: Additional validators to run
37
38
Returns:
39
True if form was submitted and validation passed
40
"""
41
42
def hidden_tag(self, *fields) -> Markup:
43
"""
44
Render form's hidden fields including CSRF token.
45
46
Args:
47
*fields: Specific fields to render (renders all hidden fields if none specified)
48
49
Returns:
50
HTML markup for hidden fields
51
"""
52
```
53
54
### Form Base Class
55
56
Re-exported WTForms Form class for cases where Flask integration is not needed.
57
58
```python { .api }
59
class Form:
60
"""
61
Base WTForms Form class without Flask integration.
62
Use FlaskForm for Flask applications.
63
"""
64
```
65
66
## Usage Examples
67
68
### Basic Form Creation
69
70
```python
71
from flask_wtf import FlaskForm
72
from wtforms import StringField, TextAreaField, SubmitField
73
from wtforms.validators import DataRequired, Length
74
75
class ContactForm(FlaskForm):
76
name = StringField('Name', validators=[DataRequired(), Length(min=2, max=50)])
77
email = StringField('Email', validators=[DataRequired(), Email()])
78
message = TextAreaField('Message', validators=[DataRequired(), Length(min=10)])
79
submit = SubmitField('Send Message')
80
```
81
82
### Automatic Data Binding
83
84
FlaskForm automatically binds to Flask request data:
85
86
```python
87
@app.route('/contact', methods=['GET', 'POST'])
88
def contact():
89
form = ContactForm() # Automatically binds to request.form, request.files, or request.json
90
91
if form.validate_on_submit():
92
# Form was submitted and validation passed
93
name = form.name.data
94
email = form.email.data
95
message = form.message.data
96
97
# Process form data
98
send_email(name, email, message)
99
flash('Message sent successfully!')
100
return redirect(url_for('contact'))
101
102
# GET request or validation failed
103
return render_template('contact.html', form=form)
104
```
105
106
### Custom Data Sources
107
108
```python
109
from werkzeug.datastructures import MultiDict
110
111
# Disable automatic binding
112
form = ContactForm(formdata=None)
113
114
# Custom data source
115
custom_data = MultiDict([('name', 'John'), ('email', 'john@example.com')])
116
form = ContactForm(formdata=custom_data)
117
118
# JSON data handling (automatic for requests with content-type: application/json)
119
@app.route('/api/contact', methods=['POST'])
120
def api_contact():
121
form = ContactForm() # Automatically binds to request.json if content-type is JSON
122
if form.validate():
123
return {'status': 'success'}
124
return {'status': 'error', 'errors': form.errors}, 400
125
```
126
127
### Template Integration
128
129
```html
130
<!-- Basic form template -->
131
<!DOCTYPE html>
132
<html>
133
<head>
134
<title>Contact Form</title>
135
</head>
136
<body>
137
<form method="POST">
138
{{ form.hidden_tag() }} <!-- Includes CSRF token and other hidden fields -->
139
140
<div>
141
{{ form.name.label(class="form-label") }}
142
{{ form.name(class="form-control") }}
143
{% for error in form.name.errors %}
144
<div class="error">{{ error }}</div>
145
{% endfor %}
146
</div>
147
148
<div>
149
{{ form.email.label(class="form-label") }}
150
{{ form.email(class="form-control") }}
151
{% for error in form.email.errors %}
152
<div class="error">{{ error }}</div>
153
{% endfor %}
154
</div>
155
156
<div>
157
{{ form.message.label(class="form-label") }}
158
{{ form.message(class="form-control", rows="4") }}
159
{% for error in form.message.errors %}
160
<div class="error">{{ error }}</div>
161
{% endfor %}
162
</div>
163
164
{{ form.submit(class="btn btn-primary") }}
165
</form>
166
</body>
167
</html>
168
```
169
170
### CSRF Token Management
171
172
```python
173
# CSRF is automatically enabled for FlaskForm
174
class MyForm(FlaskForm):
175
name = StringField('Name')
176
177
# CSRF can be disabled for specific forms
178
class Meta:
179
csrf = False
180
181
# Custom CSRF configuration per form
182
class SecureForm(FlaskForm):
183
data = StringField('Data')
184
185
class Meta:
186
csrf = True
187
csrf_secret = 'form-specific-secret'
188
csrf_time_limit = 1800 # 30 minutes
189
csrf_field_name = 'security_token'
190
```
191
192
### Multi-part Forms (File Uploads)
193
194
```python
195
from flask_wtf.file import FileField
196
from wtforms import StringField
197
198
class UploadForm(FlaskForm):
199
title = StringField('Title', validators=[DataRequired()])
200
file = FileField('File')
201
202
@app.route('/upload', methods=['GET', 'POST'])
203
def upload():
204
form = UploadForm() # Automatically handles request.files
205
206
if form.validate_on_submit():
207
title = form.title.data
208
file = form.file.data # Werkzeug FileStorage object
209
210
if file:
211
filename = secure_filename(file.filename)
212
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
213
214
return redirect(url_for('success'))
215
216
return render_template('upload.html', form=form)
217
```
218
219
### Form Validation Patterns
220
221
```python
222
# Custom validation
223
class RegistrationForm(FlaskForm):
224
username = StringField('Username', validators=[DataRequired()])
225
password = PasswordField('Password', validators=[DataRequired()])
226
confirm = PasswordField('Confirm Password', validators=[DataRequired()])
227
228
def validate_username(self, field):
229
"""Custom field validator (method naming convention)"""
230
if User.query.filter_by(username=field.data).first():
231
raise ValidationError('Username already exists.')
232
233
def validate(self, extra_validators=None):
234
"""Custom form-level validation"""
235
if not super().validate(extra_validators):
236
return False
237
238
if self.password.data != self.confirm.data:
239
self.confirm.errors.append('Passwords must match.')
240
return False
241
242
return True
243
244
# Using with extra validators
245
@app.route('/register', methods=['GET', 'POST'])
246
def register():
247
form = RegistrationForm()
248
249
# Additional runtime validators
250
extra_validators = {'username': [Length(min=3, max=20)]}
251
252
if form.validate_on_submit(extra_validators):
253
# Registration logic
254
return redirect(url_for('login'))
255
256
return render_template('register.html', form=form)
257
```
258
259
## Configuration
260
261
### Form Meta Configuration
262
263
```python
264
from wtforms.meta import DefaultMeta
265
266
class CustomForm(FlaskForm):
267
class Meta(DefaultMeta):
268
# CSRF settings
269
csrf = True
270
csrf_secret = 'custom-csrf-key'
271
csrf_field_name = 'csrf_token'
272
csrf_time_limit = 3600
273
274
# Localization
275
locales = ['en_US', 'en']
276
277
def get_translations(self, form):
278
# Custom translation logic
279
return super().get_translations(form)
280
```
281
282
### Request Data Handling
283
284
Flask-WTF automatically handles different request data sources:
285
286
1. **Form Data**: `request.form` (application/x-www-form-urlencoded)
287
2. **File Data**: Combined `request.files` and `request.form` (multipart/form-data)
288
3. **JSON Data**: `request.json` (application/json)
289
290
The data source is automatically selected based on the request content type and HTTP method.