4. 表单
4.1 HTML表单
先看一个简单的HTML表单
<form method="post">
<label for="username">Username</label>
<input type="text" name="username" placeholder="Hector Rivera">
<br>
<label for="password">Username</label>
<input type="text" name="password" placeholder="19001130">
<br>
<input id="remember" name="remember" type="checkbox" checked>
<input type="submit" name="submit" value="Login in">
</form>
4.2 使用Flask-wtf处理表单
$ pip install flask-wtf
Flask-wtf默认为每个表单启用CSRF保护,他会为我们自动生成和验证CSRF令牌,默认情况下 我们需要在程序内设置秘钥
app.secret_key = "secret string"
4.2.1 定义WTForms表单类
- 常用的WTForms字段
字段类 | 说明 | 对应的HTML表示 |
---|---|---|
BooleanField | 复选框,值会被处理为True或False | <input type="checkbox"> |
DataField | 文本字段,值会被处理为datetime.date对象 | <input type="text"> |
DateTimeField | 文本字段,值会被处理为datetime.datetime对象 | <input type="text"> |
FielField | 文件上传字段 | <input type="file"> |
FloatField | 浮点数字段,值会被处理为浮点型 | <input type="text"> |
IntegerField | 整数字段,值会被处理为整型 | <input type="text"> |
RadioField | 一组单选按钮 | <input type="radio"> |
SelectField | 下拉列表 | <select> <option></option></select> |
SelectMultipleField | 多选下拉列表 | <select multiple> <option></option></select> |
SubmitFiled | 提交按钮 | <input type="submit"> |
StringField | 文本字段 | <input type="text"> |
HiddenField | 隐藏文本字段 | <input type="hidden"> |
PasswordFiled | 密码文本字段 | <input type="password"> |
TextAreaField | 多行文本字段 | <textarea></textarea> |
实例化字段类型常用参数
参数 | 说明 |
---|---|
label | 字段label的值 也就是渲染后显示在输入字段前的文字 |
render_kw | 一个字段 用来设置对应的HTML<INPUT>标签的属性, 比如传入{"placeholder": "Your Name"}, 渲染后的HTML代码或将<input>标签的placeholder属性设为Your Name |
validators | 一个列表 包含一些列的验证器 会在表单提交后逐一被调用验证表单数据 |
default | 字符串或可调用对象, 用来为表单字段设置默认值 |
常用的WTForms验证器
验证器 | 说明 |
---|---|
DataRequired(message=None) | 验证数据是否有效 |
Email(message=None) | 验证Email地址 |
EqualTo(filename, message=None) | 验证两个字段值是否相等 |
InputRequired(message=None) | 验证是否有数据 |
Length(min=-1, max=-1, messgae=None) | 验证输入值是否在给定范围内 |
NumberRange(min=None, max=None, message=None) | 验证输入数字是否在给定范围内 |
Optional(strip_whitespace=True) | 允许输入值为空,并跳过其他验证 |
Regexp(regex, flags=0, message=None) | 使用正则表达式验证输入值 |
URL(require_tld=True, messgae=None) | 验证URL |
AnyOf(value, message=None, values_formatter=None) | 确保输入值在可选列表中 |
NoneOf(value, message=None, values_formatter=None) | 确保输入值不在可选列表中 |
**下面我们来定义一个表单类 forms.py"
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms,validators import DataRequired, Length
class LoginForm(FlaskForm):
username = StringField("UserName", validators=[DataRequired])
password = PasswordField("Password", validators=[DataRequired(), Length(8, 128))
remember = BooleanField("Remember me")
submit = SubmitField("Log in")
4.2.2 输出HTML代码
form = LoginForm()
form.username
'<input id="username" name="username" type="text" value="">'
form.submit()
'<input id="submit" name="submit" type="submit" value="Submit">'
form.username.label()
<label for="username">Username</label>
form.submit.label()
<label for="submit">Submit</label>
1.使用render_kw属性
username = StringField("Username", render_kw={"placeholder": "Your Username"})
输出的label
<input type=text" id="username" name="username" placeholder="Your Username">
2.在调用字段时传入
form.username(style="width:200px;', class_="bar")
class 是python的保留关键字 我们使用class_来替代class 但是我们不能修改name属性值
4.2.3 在模板中渲染表单
from forms import LoginForm
@app.route("/basic")
def basic():
form = LoginForm()
return render_template("login.html", form=form)
basic.html
<form method="post">
{{ form.csrf_token }}<!-- 渲染CSRF令牌隐藏字段 -->
{{ form.username.label }}{{ form.username }}<br>
{{ form.password.label }}{{ form.password }}<br>
{{ form.remember.label }}{{ form.remember }}<br>
{{ form.submit }}<br>
</form>
渲染Bootstrap风格表单
<form method="post">
{{ form.csrf_token }}<!-- 渲染CSRF令牌隐藏字段 -->
<div class="form-group">
{{ form.username.label }}{{ form.username(class="form-control") }}
</div>
<div class="form-group">
{{ form.password.label }}{{ form.password(class="form-control") }}
</div>
<div class="form-check">
{{ form.remember.label }}{{ form.remember(class="form-check-input") }}
</div>
{{ form.submit }}<br>
</form>
处理表单数据
从获取数据到保存数据大致会经过以下步骤:
- 解析请求,获取表单数据
- 对数据进行必要的转换,比如将勾选框的值转换成Python的布尔值
- 验证数据是否符合要求,同时验证CSRF令牌
- 如果验证未通过则需要生产错误信息.并在模板中显示错误信息
- 如果通过验证, 就把数据保存到数据库或做进一步验证
4.3.1 提交表单
HTML表单中控制提交行为的树心
属性 | 默认值 | 说明 |
---|---|---|
action | 当前URL,即对应的Url | 表单提交时发送请求的目标url |
method | get | 提交表单的http请求方法,目前仅支持GET和POST方法 |
enctype | application/x-www-form-urlencoded | 表单数据的编码类型,当表单中包含文件上传字段时,需要设为mutipart/form-data,还可以设为纯文本类型text/plain |
4.3.2 验证表单数据
from flask import request
@app.route("/basic", methods=["GET", "POST"]
def basic():
form = LoginForm()
if request.method == "POST" and form.validate():
... # 处理POST请求
return render_template("forms/basic.html", form=form
flask-wtf提供了validate_on_submit()方法合并了两个操作 因此可以优化为
from flask import request
@app.route("/basic", methods=["GET", "POST"]
def basic():
form = LoginForm()
if form.validate_on_submit():
... # 处理POST请求
return render_template("forms/basic.html", form=form)
# 表单验证与数据获取
from flask import Flask, render_template, flash, redirect, url_for
...
@app.route("/basic", methods=["GET", "POST"]
def basic():
form = LoginForm()
if form.validate_on_submit():
username = form.username.data
flash("Welcome home, %s!" % username
return redirect(url_for("index"))
return render_template("forms/basic.html", form=form)
4.3.3 在模板中渲染错误信息
<form method="post">
{{ form.csrf_token }}<!-- 渲染CSRF令牌隐藏字段 -->
<div class="form-group">
{{ form.username.label }}{{ form.username(class="form-control") }}
{% for message in form.username.errors %}
<small class="error">{{messgae""</small><br>
{% endfor %}
</div>
<div class="form-group">
{{ form.password.label }}{{ form.password(class="form-control") }}
{% for message in form.password.errors %}
<small class="error">{{messgae""</small><br>
{% endfor %}
</div>
<div class="form-check">
{{ form.remember.label }}{{ form.remember(class="form-check-input") }}
</div>
{{ form.submit }}<br>
</form>
4.4 表单进阶实践
4.4.1 设置错误信息语言
from flask_wtf import FlaskForm
app = Flask(__name__)
app.config["WTF_I18N_ENABLED'] = False
class MyBaseForm(FlaskForm):
class Meta:
locales = ["zh"]
class HelloForm(MyBaseForm):
name = StringField("Name", validtors=[DataRequired()])
submit = SubmitField("提交")
我们需要将配置变量WTF_I18N_ENABLED设为False, 这会让Flask-WTF使用WTForms内置的错误信息翻译. 然后我们需要在自定义基类定义元类Meta.并在locales列表中加入中文。也可以在实例化表单类时通过meta关键字传入locales,比如:
form = MyForm(meta={"locales":["en_US", "en"]})
4.4.2 使用宏渲染表单
{% macro from_field(field) %}
{{ field.label }}<br>
{{ field(**kwargs) }}<br>
{% if field.errors %}
{% for error in errors %}
<small class="error">{{ error }}</small>
{% endfor %}
{% endif %}
{% endmacro %}
宏调用
{ % from 'macros.html' import form_field %}
...
<form method="post">
{{ form.csrf_token }}
{{ form_field(form.username) }}
{{ form_field(form.password }}
4.4.3 自定义验证器
- 1.行内验证器
from wtforms import IntegerField, SubmitField
from wtforms.validators import ValidationError
class FortywoForm(FlaskForm):
answer = IntegerField("The Number")
submit = SubmitField(“提交")
def validate_answer(form, field):
if field.data != 42:
raise ValidationError("Must be 42.')
当表单类包含以”validate_字段属性名"形式命名的方法时,在验证字段数据时会同时调用这个方法来验证对应的字段,这也是为什么表单类的属性名不能以validate开头.验证方法接收两个位置参数,依次为form和field,前者为表单类实例,后者为字段对象,我们可以通过field.data获取字段数据,这2个参数将在验证表单时被调用传入. 验证出错时抛出从wtforms.validators模块导入的ValidationError异常,传入错误消息作为参数.因此这种方法仅用来验证特定的表单类字段,所以又称为行内验证器(in-line validator)。
- 2.全局验证器
from wtforms.validators import ValidationError
def is_42(form, field):
if field.data != 42:
raise ValidationError("Must be 42")
class FortywoForm(FlaskForm):
answer = IntegerField("The Number", validators=[is_42])
submit = SubmitField(“提交")
我们通常需要让验证器支持传入参数来对验证器进行函数调用。这里我们使用闭包实现
def is_42(message=None):
if message is None:
message = "Must be 42."
def _is_42(form, field)
if field.data != 42:
raise ValidationError(message)
return _is_42
class FortywoForm(FlaskForm):
answer = IntegerField("The Number", validators=[is_42()])
submit = SubmitField(“提交")
4.4.4 文件上传
出于安全考虑.除了常规的CSRF防范,我们还需要重点注意下面的问题
- 验证文件类型
- 验证文件大小
- 过滤文件名
1.定义上传表单
from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask_wtf import FlaskForm
class UploadForm(FlaskForm):
photo = FileField("Upload Image", validators=[FileRequired(), FileAllowed(['jpg', 'jpeg', 'png', 'gif'])])
submit = SubmitField()
验证器 | 说明 |
---|---|
FileRequired(message=None) | 验证是否包含文件对象 |
FileAllowed(upload_set, meaasge=None) | 用来验证文件类型,upload_set参数用来传入包含允许的文件后缀名列表 |
除了验证文件的类型 我们通常还需要对文件大小进行验证 通过FLASK的内置的配置变量MAX_CONTENT_LENGTH我们可以限制请求报文的最大长度 单位为字节(byte) 比如我们将最大长度限制为3M。
app.config["MAX_CONTENT_LENGTH"] = 3 * 1024 * 1024