Skip to content

Flask - WTF

Web 应用程序高度依赖表单来收集用户输入。虽然 HTML 提供了 <form> 标签和各种输入元素 (<input>, <textarea>, <select> 等),但在服务器端安全高效地处理表单数据、验证和渲染可能非常复杂。

手动处理表单涉及几个挑战:

  • 重复定义: 表单结构通常需要在 HTML 中定义用于展示,并在服务器端代码中再次定义用于处理和验证。
  • 动态渲染: 使用纯 HTML 根据数据或上下文动态创建表单可能很麻烦。
  • 验证: 实现强大的输入验证需要大量的自定义代码。
  • 安全性: 防范常见的 Web 安全漏洞,例如跨站请求伪造(CSRF),需要仔细实现。

Flask-WTF 是一个 Flask 扩展,它集成了功能强大的 WTForms 库,显著简化了表单处理。它允许您在 Python 中定义表单结构和验证规则,在 HTML 模板中渲染表单,并安全地处理提交的数据。

使用 Flask-WTF 的主要优势包括:

  • 将表单定义为 Python 类。
  • 从这些 Python 类渲染 HTML 表单。
  • 根据定义的规则验证提交的表单数据。
  • 内置的 CSRF 保护。

首先,您需要安装 Flask-WTF 扩展:

pip install Flask-WTF

注意:Flask-WTF 依赖于 WTForms,WTForms 将自动安装。

要创建一个表单,您需要定义一个类,该类继承自 Flask-WTF 提供的 FlaskForm。表单字段定义为类变量,使用从 wtforms 库导入的字段类型。

以下是一些常用的 WTForms 字段类型:

序号字段类型描述与 HTML 表示
1StringField渲染为 <input type='text'>。用于一般的文本输入。
2TextAreaField渲染为 <textarea>。用于多行文本输入。
3PasswordField渲染为 <input type='password'>。隐藏输入内容。
4BooleanField渲染为 <input type='checkbox'>。用于布尔值。
5IntegerField渲染为 <input type='text'>(或 number)。验证整数输入。
6DecimalField渲染为 <input type='text'>(或 number)。验证十进制数输入。
7DateField渲染为 <input type='text'>(或 date)。验证日期输入。
8RadioField渲染为一组 <input type='radio'>。用于选择一个选项。
9SelectField渲染为 <select>。用于从下拉列表中选择。
10SubmitField渲染为 <input type='submit'>。用于提交表单。

例如,一个带有姓名字段的简单联系表单可以这样定义:

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class ContactForm(FlaskForm):
name = StringField('Name Of Student', validators=[DataRequired()])
submit = SubmitField('Submit')

注意 validators=[DataRequired()] 参数。这确保字段不能为空。WTForms 提供了各种内置的验证器(validator)。

Flask-WTF 会自动向您的表单添加一个包含 CSRF token 的隐藏字段。您需要在模板中使用 form.hidden_tag() 或 form.csrf_token 来渲染此字段。此 token 对于保护您的应用程序免受跨站请求伪造(Cross-Site Request Forgery)攻击至关重要,恶意网站可能会尝试诱骗用户向您的应用程序提交非预期的有害数据。

在 HTML 模板中渲染时(下面会解释),name 字段会生成类似于此的 HTML 代码(以及隐藏的 CSRF token 字段):

<label for="name">Name Of Student</label>
<input id="name" name="name" required type="text" value="">

要在 Flask 应用程序中使用此表单,您需要在视图函数中实例化它,并将其传递给模板。

from flask import Flask, render_template, request, flash
from forms import ContactForm # Assuming forms.py contains your form class
app = Flask(__name__)
# A secret key is required for CSRF protection and session management
# Use a strong, random, and secret value in production!
app.config['SECRET_KEY'] = 'a-very-secret-key'
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if form.validate_on_submit():
# This block executes on POST request if validation passes
name = form.name.data
# Process the data (e.g., save to database, send email)
flash(f'Message received from {name}!', 'success')
# Redirect or render a success page
return render_template('success.html')
# On GET request or if validation fails on POST, render the form template
return render_template('contact.html', form=form)
if __name__ == '__main__':
app.run(debug=True)

form.validate_on_submit() 方法是一个便捷的辅助方法。如果请求方法是 POST 且所有字段验证都通过,则返回 True;否则,返回 False。

WTForms 包含一个 validators 模块,其中有各种常见的验证规则。您需要将验证器列表传递给字段定义。

序号常用验证器描述
1DataRequired检查字段是否包含数据。在大多数情况下使用此验证器而非 InputRequired。
2InputRequired检查是否为该字段提供了输入。用于可选字段,其中空值与缺失值含义不同。
3Email验证输入看起来像一个电子邮件地址。
4EqualTo将输入与另一个字段的值进行比较(例如,密码确认)。
5IPAddress验证 IPv4 或 IPv6 地址。
6Length验证输入字符串的长度在指定范围内。
7NumberRange验证数字在指定范围内。
8URL验证 URL。

包含多个验证器的示例:

from wtforms.validators import DataRequired, Email, Length
email = StringField('Email', validators=[
DataRequired(),
Email(message='Invalid email address.'),
Length(max=120)
])

当调用 form.validate_on_submit() 或 form.validate() 时,如果某个字段验证失败,该字段对象上会附加一个 errors 属性(一个错误消息列表)。您可以在模板中显示这些错误。

{# Example showing how to display errors for the 'name' field #}
{{ form.name.label }}<br>
{{ form.name(size=30) }}
{% if form.name.errors %}
<ul class="errors">
{% for error in form.name.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}

让我们构建一个更完整的示例。在 forms.py 中定义以下表单:

forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, IntegerField, SelectField, RadioField, SubmitField
from wtforms.validators import DataRequired, Email, NumberRange
class ContactForm(FlaskForm):
name = StringField('Name Of Student', validators=[
DataRequired(message="Please enter your name.")
])
gender = RadioField('Gender', choices=[('M', 'Male'), ('F', 'Female')], validators=[
DataRequired(message="Please select your gender.")
])
address = TextAreaField('Address', validators=[
DataRequired(message="Please enter your address.")
])
email = StringField('Email', validators=[
DataRequired(message="Please enter your email address."),
Email(message="Please enter a valid email address.")
])
age = IntegerField('Age', validators=[
DataRequired(message="Please enter your age."),
NumberRange(min=1, max=120, message="Age must be between 1 and 120.")
])
language = SelectField('Favorite Language', choices=[
('', '-- Select Language --'), # 添加一个占位符
('py', 'Python'),
('js', 'JavaScript'),
('cpp', 'C++')
], validators=[
DataRequired(message="Please select a language.")
])
submit = SubmitField('Send')

以下是更新后的 Flask 应用程序脚本 (app.py):

app.py
from flask import Flask, render_template, request, flash, redirect, url_for
from forms import ContactForm
app = Flask(__name__)
# IMPORTANT: Use a real secret key, ideally from environment variables
app.config['SECRET_KEY'] = 'keep-this-a-secret-please'
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if form.validate_on_submit():
# Form is valid, process the data
name = form.name.data
email = form.email.data
# ... process other fields ...
print(f"Received data: Name={name}, Email={email}") # 示例处理
flash('Form submitted successfully!', 'success')
return render_template('success.html') # 显示成功页面
# Else (GET request or validation failed), render the form again.
# Errors will be available in the form object for the template to display.
if request.method == 'POST' and not form.validate():
flash('Please correct the errors in the form.', 'danger')
return render_template('contact.html', form=form)
# Add a simple success route/template
@app.route('/success')
def success():
return render_template('success.html')
if __name__ == '__main__':
app.run(debug=True)

以及用于渲染表单和显示错误的模板 (templates/contact.html):

<!doctype html>
<html>
<head>
<title>Contact Form</title>
<style>
.error { color: red; font-size: 0.8em; }
label { font-weight: bold; }
div { margin-bottom: 10px; }
</style>
</head>
<body>
<h2>Contact Form</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul>
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<form method="post" novalidate>
{{ form.hidden_tag() }} {# Renders CSRF token and any other hidden fields #}
<div>
{{ form.name.label }}<br>
{{ form.name(size=30) }}
{% if form.name.errors %}
{% for error in form.name.errors %}<span class="error">[{{ error }}]</span>{% endfor %}
{% endif %}
</div>
<div>
{{ form.gender.label }}<br>
{% for subfield in form.gender %}
{{ subfield }} {{ subfield.label }} &nbsp;
{% endfor %}
{% if form.gender.errors %}
{% for error in form.gender.errors %}<span class="error">[{{ error }}]</span>{% endfor %}
{% endif %}
</div>
<div>
{{ form.address.label }}<br>
{{ form.address(rows=4, cols=30) }}
{% if form.address.errors %}
{% for error in form.address.errors %}<span class="error">[{{ error }}]</span>{% endfor %}
{% endif %}
</div>
<div>
{{ form.email.label }}<br>
{{ form.email(size=30) }}
{% if form.email.errors %}
{% for error in form.email.errors %}<span class="error">[{{ error }}]</span>{% endfor %}
{% endif %}
</div>
<div>
{{ form.age.label }}<br>
{{ form.age(size=5) }}
{% if form.age.errors %}
{% for error in form.age.errors %}<span class="error">[{{ error }}]</span>{% endfor %}
{% endif %}
</div>
<div>
{{ form.language.label }}<br>
{{ form.language() }}
{% if form.language.errors %}
{% for error in form.language.errors %}<span class="error">[{{ error }}]</span>{% endfor %}
{% endif %}
</div>
<div>
{{ form.submit() }}
</div>
</form>
</body>
</html>

创建一个简单的 templates/success.html:

<!doctype html>
<html>
<head><title>Success</title></head>
<body>
<h2>Form Submitted Successfully!</h2>
<p><a href="{{ url_for('contact') }}">Go back to the form</a></p>
</body>
</html>

运行 app.py 并访问 http://localhost:5000/contact。您将看到渲染后的表单。如果您未填写必填字段或输入无效数据后提交,表单将重新渲染,并在相关字段旁边显示错误消息,这得益于模板中的验证规则和错误渲染逻辑。

如果您正确填写表单并提交,您将被重定向到成功页面,并且运行 Flask 应用程序的控制台将打印一条消息。

有关更高级的表单处理,请查阅 WTForms 和 Flask-WTF 的文档。主题包括自定义验证器、文件上传、高级字段类型等。