介绍Flask-WTF
为了在应用中使用表单,我会用到Flask-WTF,这是Flask的一个拓展,整合了WTForms包。Flask还有很多其他拓展。Flask-WTF的安装很简单,执行下面的命令:
(venv) $ pip install flask-wtf
配置文件
一个应用可能有很多需要个性化配置的内容,比如数据库信息等,把这些内容写在单独的文件而不是源代码内会更安全,因为源代码可能在github上分享出来,而我们并不希望分享密码等信息。
所以我们在项目目录新建一个config.py
文件。
import os
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
这里我们有了一个SECRET_KEY
配置项,这是让应用作为加密密钥的,以对网页表单的数据传输进行加密,避免SCRF攻击。下一步,我们需要把密钥应用在应用中。修改app/__init__.py
文件。
from flask import Flask
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
from app import routes
编写网页登录表单
编写表单类
Flask-WTF拓展用Python类来定义表单。这里我们新建一个app/forms.py,在其中定义登录表单,包括用户名输入框、密码输入框、记住我的选择框,以及一个提交按钮。
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
编写表单模板
现在我们编写表单模板,好在LoginForm
类知道如何渲染自己,所以这非常简单。我们新建app/template/login.html文件如下:
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
</p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
和教程2类似,我们继承了base.html
,并通过{% block content %}
拓展了内容。以后编写html页面都是类似的。这个模板接受一个LoginForm
对象,模板中叫做form
。
HTML代码中的<form>
元素定义了表单的容器。action
属性定义了接受表单提交的数据的页面。action=""
表示用当前的url来处理接收的数据。method
属性定义了HTTP请求方法,默认是GET
方法,但几乎所有表单数据都会使用POST
方法。novalidate
属性告诉浏览器不要检查表单数据,而让应用来处理。
{{ form.hidden_tag() }}
产生了一个隐藏的数据来防止CSRF攻击。
表单视图函数
最后我们需要编写表单视图函数,来渲染前面编写的模板。我们在app/routes.py中增加以下代码:
from flask import render_template
from app import app
from app.forms import LoginForm
# ...
@app.route('/login')
def login():
form = LoginForm()
return render_template('login.html', title='Sign In', form=form)
为了方便找到loging页面,我们修改app/templates/base.html,在导航增加login链接:
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
<hr>
{% block content %}{% endblock %}
</body>
</html>
接收数据
现在,如果你运行应用,并提交登录表单,网页会显示Method Not Allowed
错误。这是因为我们的视图函数还不能处理post请求。所以我们需要修改app/routes.py文件中的login函数如下:
from flask import render_template, flash, redirect
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
flash('Login requested for user {}, remember_me={}'.format(
form.username.data, form.remember_me.data))
return redirect('/index')
return render_template('login.html', title='Sign In', form=form)
我们可以看到函数装饰器中增加了methods
变量,这使得视图函数可以处理GET,POST请求。另外form.validate_on_submit()
函数可以判断请求是否包括表单数据。如果是GET请求,这个函数返回False,直接渲染空的页面。如果是POST请求,这个方法会收集所有表单数据,进行数据检查,如果一切正常,就返回True,表明表单数据是合法的,可以继续处理。如果填写了不合法的数据,也是返回False。后面我会在数据不合法时增加报错信息。
当form.validate_on_submit()
函数返回True,redirect()
函数可以让页面跳转到其他页面。我用了flash()
函数,这是网页上显示一些信息的有用机制。当flash()
被调用的时候,Flask会存储这条信息,但网页并没有直接显示,我们需要修改base.html模板文件,来处理flask()
函数推送出来的信息。
<html>
<head>
{% if title %}
<title>{{ title }} - microblog</title>
{% else %}
<title>microblog</title>
{% endif %}
</head>
<body>
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
<hr>
{% with messages = get_flashed_messages() %} {# messages 变量存储flash推送的信息 #}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
现在你可以运行应用,尝试填入登录信息。你也可以看看如果表单内容为空会如何。
改善数据校验
如果你表单内容为空,页面没有报错,而是空白的表单,这对用户并不友好,我们需要给每个表单域给一个失败提醒。事实上,表单对象的validators可以自动处理这些报错逻辑,所以我们仅需要修改模板文件即可。我们修改login.html文件如下:
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %} {# 报错的处理逻辑 #}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
产生链接
目前位置,我们在模板中是这样定义链接的:
<div>
Microblog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
在视图函数的redirect()
函数中,我们是这样定义链接的:
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# ...
return redirect('/index')
# ...
为了避免这种绝对化的链接地址,以及支持动态的url,我们可以用url_for()
函数,比如url_for('login')
就返回/login
。函数接收的是视图函数的函数名称,flask会匹配到视图函数的超链接。
所以后续定义链接,我都会使用url_for()
函数。所以base.html修改如下:
<div>
Microblog:
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('login') }}">Login</a>
</div>
而login()视图函数修改为:
from flask import render_template, flash, redirect, url_for
# ...
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# ...
return redirect(url_for('index'))
# ...
说明
本文是我的Flask学习笔记,