在 WEB 应用当中,表单是和用户交互的最常见的方式之一,学习好表单,是非常重要的,用户登录注册、撰写文章等等操作都离不开表单的功能。表单的处理并不简单,除了要创建表单,还需要做相关的验证,还有错误提示等等。这些操作如果都从头开始编写,那么就太复杂了,不过幸运的是,我们有强大的 WTForms 帮助我们解决。
在 HTML 表单中,可以通过 <form>
标签来创建,通过 <input>
来定义字段。
<form method="post"> <!-- 指定提交方法为 POST -->
<label for="name">用户名</label>
<input type="text" name="name" id="name"><br> <!-- 文本输入框 -->
<label for="occupation">手机号</label>
<input type="text" name="occupation" id="occupation"><br> <!-- 文本输入框 -->
<input type="submit" name="submit" value="登录"> <!-- 提交按钮 -->
</form>
编写表单的 HTML 代码有下面几点需要注意:
form
标签里使用method
属性将提交表单数据的 HTTP 请求方法指定为 POST。如果不指定,则会默认使用 GET 方法,这会将表单数据通过 URL 提交,容易导致数据泄露,而且不适用于包含大量数据的情况。input
元素必须要指定name
属性,否则无法提交数据,在服务器端,我们也需要通过这个name
属性值来获取对应字段的数据。当然,编写 HTML 代码并不是我们的主要工作,所以我们可以通过 Flask 的相关插件来自动生成这部分 HTML 代码。
WTForms 支持在 Python 中使用类定义表单,然后直接通过类定义生成对应的 HTML 代码,这种方式更加方便,而且也更易于重用。因此,在一般的情况下,我们都不会直接使用 HTML 编写表单,使用 WTForms 是我们的第一选择。
扩展 Flask-WTF 集成了 WTForms,使用它可以在 Flask 中方便的使用 WTForms。Flask-WTF 将帮助我们更加方便的处理表单,包括表单的生成、解析、CSRF等等。
安装 Flask-WTF 还是一样的,直接通过 pip 安装
pip install flask-wtf
因为 Flask-WTF 默认会为每一个表单启用 CSRF 保护,Flask-WTF 默认情况下使用程序密钥来对 CSRF 令牌进行签名,所以我们需要进行如下设置
app.secret = 'my hard secret'
一个表单由若干个输入字段组成,这些字段分别用表单的类属性来表示。下面我们来编写一个登录类
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectMultipleField, SelectField
from wtforms.validators import DataRequired, EqualTo, ValidationError
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
我们定义了一个 LoginForm 类,该类中又定义了三个字段,就是后面我们在 web 页面上会看到的表单字段。 如下是一些常用的 WTForms 类字段
字段类 | 说明 | 对应的 HTML |
---|---|---|
StringField | 文本字段 | <input type="text"> |
SubmitField | 提交按钮 | <input type="submit"> |
PasswordField | 密码文本字段 | <input type="password"> |
FileField | 文件上传字段 | <input type="file"> |
SelectField | 下拉列表 | <select></select> |
在 WTForms 中,验证器(validator)是一系列用于验证字段数据的类,我们在实例化字段类时使用 validators 关键字来指定附加验证器列表。
如下是常用的验证器
验证器 | 说明 |
---|---|
DataRequired | 验证数据是否存在 |
验证 email 地址 | |
EqualTo | 验证两个字段是否一致 |
为了能够在模板中渲染表单,我们需要把表单实例传入模板。首先实例化表单类 LoginForm,然后在 render_template() 函数中传入模板,于是我们修改 login 试图函数如下
@app.route('/login/')
def login():
form = LoginForm()
return render_template('login.html', form=form)
接着我们再创建一个 login.html 文件,写入如下
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}My Web - Login{% endblock %}
{% block page_content %}
{{ wtf.quick_form(form) }}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% endblock %}
这样,我们再刷新我们的项目页面,可以看到如下效果
一般来说,从获取表单数据到保存表单数据大致需要以下几步:
在 HTML 中,当表单类型为 submit 的字段被点击时,就会创建一个提交表单的 HTTP 请求,请求中会包含表单中的各个字段。
由于 Flask 为路由默认设置的监听的 HTTP 请求为 GET,而表单往往都是 POST 请求,所以我们需要手动给试图函数绑定 POST 请求
@app.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm()
return render_template('login.html', form=form)
对于数据的验证,我们可以使用函数 validate_on_submit(),如果返回 True,则代表验证通过。
@app.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
username = form.username.data
session['username'] = username
flash("登录成功,%s!" % username)
return redirect(url_for('index'))
return render_template('login.html', form=form)
在这里,我们通过 form.username.data 来获取表单中的用户名,并通过 session 来保存,然后再重定向到 index 视图函数 下面我们再来看看 index 视视图函数
@app.route('/')
def index():
user = session.get('username')
return render_template('index.html', user=user)
这个就相对简单了,从 session 中拿到用户名,然后传递给 index.html 模板,而 index.html 模板则与前面我们做的类似,就不再赘述了。
如果函数 validate_on_submit() 返回 false,那么说明表单提交的数据验证不通过,WTForms 会把错误消息添加到表单类的 error 属性中,我们可以在模板中轻松的取出。
在 loging.html 中添加如下代码
{% for message in form.username.errors %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% for message in form.password.errors %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
效果如下:
对于文件上传,其实我们有许多安全的问题需要考虑:
下面我们来看一看 WTForms 能帮助我们做些什么
首先定义一个文件上传的表单类,一个图片上传的表单
class UploadForm(FlaskForm):
photo = FileField('Upload Image', validators=[file_required(), file_allowed(upload_set='.jpg')])
submit = SubmitField('Upload')
在这里,我们定义了用于上传文件的表单,并且限制了只能上传 jpg 格式的文件类型 下面我们编写上传图片的视图函数 upload
@app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
return render_template('upload.html', form=form)
这里其实与登录的视图函数是类似的写法 接下来就是 upload.html 文件的编写
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}My Web - Upload{% endblock %}
{% block page_content %}
{{ wtf.quick_form(form) }}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% endblock %}
我们重新刷新页面,得到如下效果
对于上传的文件,我们在服务器端需要做一定的处理,例如保存、校验等等。
下面我们继续编写 upload 视图函数
app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')
@app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
if form.validate_on_submit():
f = form.photo.data
filename = f.filename
f.save(os.path.join(app.config['ULOAD_PATH'], filename))
flash('上传图片文件成功!')
session['filename'] = filename
return redirect(url_for('show_images'))
return render_template('upload.html', form=form)
我们通过 f.filename 来获取文件的名称,并保存上传的文件到指定目录 下面就是编写展示图片的视图函数了
@app.route('/uploads/<path:filename>')
def get_file(filename):
return send_from_directory(app.config['UPLOAD_PATH'], filename)
@app.route('/uploaded-images')
def show_images():
return render_template('uploaded.html')
在上传好图片后,我们的程序会跳转至另外的页面,用于展示当前的图片
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}My Web - Upload{% endblock %}
{% block page_content %}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% if session.filename %}
<a href="{{ url_for('get_file', filename=session.filename) }}" target="_blank">
<img src="{{ url_for('get_file', filename=session.filename) }}">
</a>
{% endif %}
{% endblock %}
当然对于表单,还有很多其他的高级应用,比如富文本编辑器等,这些我们留到后面再进行讨论!
这部分的完整代码,可以检出4a
本节我们一起学习了 WEB 表单相关的知识,在后面的学习当中,我们还会多次使用,一定要好好消化这部分哦!
最后的最后,如果觉得文章给了你一些启发或者帮助,还请帮忙点个赞,给辛苦码字的我一点小小鼓励,谢谢!!