HTML 表单
在 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
WTForms 支持在 Python 中使用类定义表单,然后直接通过类定义生成对应的 HTML 代码,这种方式更加方便,而且也更易于重用。因此,在一般的情况下,我们都不会直接使用 HTML 编写表单,使用 WTForms 是我们的第一选择。
使用Flask-WTF 处理表单
扩展 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'
定义WTForms表单类
一个表单由若干个输入字段组成,这些字段分别用表单的类属性来表示。下面我们来编写一个登录类
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 表单相关的知识,在后面的学习当中,我们还会多次使用,一定要好好消化这部分哦!