Flask课程3-登录表单Flask-WTF

738 阅读6分钟

安装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')返回/loginurl_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)