安装Flask-WTF
$ pip install flask-wtf
构建表单类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')
表单模板app/templates/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 %}
form.hidden_tag()
模板参数生成了一个隐藏字段,其中包含一个用于保护表单免受CSRF
攻击的token。 对于保护表单,你需要做的所有事情就是在模板中包括这个隐藏的字段,并在Flask配置中定义SECRET_KEY
变量,Flask-WTF
会完成剩下的工作。
HTML<form>
元素被用作Web表单的容器。 表单的action
属性告诉浏览器在提交用户在表单中输入的信息时应该请求的URL。 当action设置为空字符串时,表单将被提交给当前地址栏中的URL,即当前页面。 method
属性指定了将表单提交给服务器时应该使用的HTTP请求方法。 默认情况下是用GET请求发送,但几乎在所有情况下,使用POST请求会提供更好的用户体验,因为这种类型的请求可以在请求的主体中提交表单数据, GET请求将表单字段添加到URL,会使浏览器地址栏变得混乱。
渲染表单视图
from app import app
from flask import render_template
from app.forms import LoginForm
...
@app.route('/login')
def login():
form = LoginForm()
return render_template('login.html', title='Sign In', form=form)
现在运行出现Internal Server Error
查看日志
RuntimeError: A secret key is required to use CSRF.
需设定:app.config['SECRET_KEY']的值
设置SECRET_KEY
SECRET_KEY是Flask中比较重要的一个配置值。
SECRET_KEY 配置变量是通用密钥。
Session, Cookies以及一些第三方扩展都会用到SECRET_KEY值,这是一个比较重要的配置值,应该尽可能设置为一个很难猜到的值,随机值更佳。
app/__init__.py
import os
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
from app import routes
现在运行出现Method Not Allowed
因为之前的登录视图功能到目前为止只完成了一半的工作。 它可以在网页上显示表单,但没有逻辑来处理用户提交的数据。Flask-WTF可以轻松完成这部分工作, 以下是视图函数的更新版本,它接受和验证用户提交的数据。
修改routes.py
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参数。 它告诉Flask这个视图函数接受GET和POST请求.
form.validate_on_submit()
实例方法会执行form校验的工作。当浏览器发起GET请求的时候,它返回False,这样视图函数就会跳过if块中的代码,直接转到视图函数的最后一句来渲染模板。
当用户在浏览器点击提交按钮后,浏览器会发送POST请求。form.validate_on_submit()
就会获取到所有的数据,运行字段各自的验证器,全部通过之后就会返回True,这表示数据有效。
当form.validate_on_submit()
返回True时,登录视图函数调用从Flask导入的两个新函数。
flash()函数是向用户显示消息的有效途径。 许多应用使用这个技术来让用户知道某个动作是否成功。我将使用这种机制作为临时解决方案,因为我没有基础架构来真正地登录用户。 显示一条消息来确认应用已经收到登录认证凭据,我认为对当前来说已经足够了。
redirect()函数指引浏览器自动重定向到它的参数所关联的URL。当前视图函数使用它将用户重定向到应用的主页。
当你调用flash()函数后,Flask会存储这个消息,但是却不会奇迹般地直接出现在页面上。模板需要将消息渲染到基础模板中,才能让所有派生出来的模板都能显示出来。更新后的基础模板代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if title %}
<title>{{title}} - Idu</title>
{% else %}
<title>Welcome to IDU</title>
{% endif %}
</head>
<body>
<div>
Idublog:
<a href="/index">Home</a>
<a href="/login">Login</a>
</div>
<hr>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>
{{message}}
</li>
{% endfor %}
</ul>
{%endif%}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
with结构在当前模板的上下文中来将get_flashed_messages()
的结果赋值给变量messages。get_flashed_messages()
是Flask中的一个函数,它返回用flash()注册过的消息列表。接下来的条件结构用来检查变量messages是否包含元素,如果有,则在<ul
元素中,为每条消息用<li>
元素来包裹渲染。
闪现消息的一个有趣的属性是,一旦通过get_flashed_messages
函数请求了一次,它们就会从消息列表中移除,所以在调用flash()
函数后它们只会出现一次。
完善字段验证
{% 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)}}
{% for error in form.username.errors %}
<span style="color: red;">[{{error}}]</span>
{%%endfor}
</p>
<p>
{{form.password.label}}<br>
{{form.password(size=32)}}
{% 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 %}
唯一的改变是,在username和password字段之后添加for循环以便用红色字体来渲染验证器添加的错误信息。通常情况下,拥有验证器的字段都会用form.<field_name>.errors
来渲染错误信息。 一个字段的验证错误信息结果是一个列表,因为字段可以附加多个验证器,并且多个验证器都可能会提供错误消息以显示给用户。
如果你尝试在未填写username和password字段的情况下提交表单,就可以看到显眼的红色错误信息了。
管理这些链接 url_for
使用URL到视图函数的内部映射关系来生成URL。 例如,url_for('login')
返回/login
,url_for('index')
返回/index
。 url_for()的参数是endpoint名称,也就是视图函数的名字。
修改app/templates/base.html
<div>
Idu blog:
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('login') }}">Login</a>
</div>
修改app/routes.py
from flask import render_template, flash, redirect, url_for
...
@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(url_for('index'))
return render_template('login.html', title='Sign In', form=form)