蓝图和视图

视图函数是您编写的用于响应应用程序请求的代码。Flask 使用模式将传入的请求 URL 匹配到应该处理它的视图。视图返回 Flask 转换为传出响应的数据。Flask 也可以反过来,根据视图的名称和参数生成指向视图的 URL。

创建蓝图

Blueprint 是一种组织一组相关视图和其他代码的方式。视图和其他代码不是直接向应用程序注册,而是向蓝图注册。然后,当蓝图在工厂函数中可用时,将其注册到应用程序。

Flaskr 将有两个蓝图,一个用于身份验证功能,另一个用于博客文章功能。每个蓝图的代码都将放在一个单独的模块中。由于博客需要了解身份验证,您将首先编写身份验证蓝图。

flaskr/auth.py
import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash

from flaskr.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')

这将创建一个名为 'auth'Blueprint。与应用程序对象一样,蓝图需要知道它在哪里定义,因此将 __name__ 作为第二个参数传递。 url_prefix 将被添加到与蓝图关联的所有 URL 的前面。

使用 app.register_blueprint() 从工厂导入和注册蓝图。将新代码放在工厂函数的末尾,然后再返回应用程序。

flaskr/__init__.py
def create_app():
    app = ...
    # existing code omitted

    from . import auth
    app.register_blueprint(auth.bp)

    return app

身份验证蓝图将具有用于注册新用户以及登录和注销的视图。

第一个视图:注册

当用户访问 /auth/register URL 时,register 视图将返回 HTML,其中包含供他们填写的表单。当他们提交表单时,它将验证他们的输入,并显示带有错误消息的表单,或者创建新用户并转到登录页面。

现在,您将只编写视图代码。在下一页中,您将编写模板以生成 HTML 表单。

flaskr/auth.py
@bp.route('/register', methods=('GET', 'POST'))
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None

        if not username:
            error = 'Username is required.'
        elif not password:
            error = 'Password is required.'

        if error is None:
            try:
                db.execute(
                    "INSERT INTO user (username, password) VALUES (?, ?)",
                    (username, generate_password_hash(password)),
                )
                db.commit()
            except db.IntegrityError:
                error = f"User {username} is already registered."
            else:
                return redirect(url_for("auth.login"))

        flash(error)

    return render_template('auth/register.html')

以下是 register 视图函数正在执行的操作

  1. @bp.route 将 URL /registerregister 视图函数关联起来。当 Flask 收到对 /auth/register 的请求时,它将调用 register 视图,并将返回值用作响应。

  2. 如果用户提交了表单,则 request.method 将为 'POST'。在这种情况下,开始验证输入。

  3. request.form 是一种特殊类型的 dict,它映射提交的表单键和值。用户将输入他们的 usernamepassword

  4. 验证 usernamepassword 不为空。

  5. 如果验证成功,则将新用户数据插入数据库。

    • db.execute 接受带有 ? 占位符的 SQL 查询,用于任何用户输入,以及用于替换占位符的值的元组。数据库库将负责转义值,因此您不会受到SQL 注入攻击的攻击。

    • 为了安全起见,密码永远不应直接存储在数据库中。相反,generate_password_hash() 用于安全地哈希密码,并存储该哈希值。由于此查询修改数据,因此需要稍后调用 db.commit() 以保存更改。

    • 如果用户名已存在,将发生 sqlite3.IntegrityError,这应作为另一个验证错误向用户显示。

  6. 存储用户后,他们将被重定向到登录页面。 url_for() 根据登录视图的名称生成其 URL。这比直接编写 URL 更可取,因为它允许您稍后更改 URL,而无需更改链接到它的所有代码。 redirect() 生成到生成 URL 的重定向响应。

  7. 如果验证失败,则会向用户显示错误。 flash() 存储可在渲染模板时检索的消息。

  8. 当用户最初导航到 auth/register,或者存在验证错误时,应显示带有注册表单的 HTML 页面。 render_template() 将渲染包含 HTML 的模板,您将在本教程的下一步中编写它。

登录

此视图遵循与上面的 register 视图相同的模式。

flaskr/auth.py
@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None
        user = db.execute(
            'SELECT * FROM user WHERE username = ?', (username,)
        ).fetchone()

        if user is None:
            error = 'Incorrect username.'
        elif not check_password_hash(user['password'], password):
            error = 'Incorrect password.'

        if error is None:
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)

    return render_template('auth/login.html')

register 视图相比,有几个不同之处

  1. 首先查询用户并将其存储在变量中以供以后使用。

    fetchone() 从查询返回一行。如果查询没有返回任何结果,则返回 None。稍后,将使用 fetchall(),它返回所有结果的列表。

  2. check_password_hash() 以与存储的哈希值相同的方式哈希提交的密码,并安全地比较它们。如果它们匹配,则密码有效。

  3. session 是一个 dict,用于跨请求存储数据。当验证成功时,用户的 id 存储在新会话中。数据存储在发送到浏览器的cookie中,然后浏览器在后续请求中将其发回。 Flask 安全地签名数据,使其不会被篡改。

现在用户的 id 存储在 session 中,它将在后续请求中可用。在每个请求的开始,如果用户已登录,则应加载其信息并使其可用于其他视图。

flaskr/auth.py
@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = get_db().execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()

bp.before_app_request() 注册一个在视图函数之前运行的函数,无论请求的 URL 是什么。 load_logged_in_user 检查用户 ID 是否存储在 session 中,并从数据库中获取该用户的数据,将其存储在 g.user 上,这会在请求的生命周期内持续存在。如果没有用户 ID,或者 ID 不存在,则 g.user 将为 None

注销

要注销,您需要从 session 中删除用户 ID。然后 load_logged_in_user 将不会在后续请求中加载用户。

flaskr/auth.py
@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

在其他视图中要求身份验证

创建、编辑和删除博客文章将要求用户登录。装饰器可用于为应用它的每个视图检查这一点。

flaskr/auth.py
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view

此装饰器返回一个新的视图函数,该函数包装了应用它的原始视图。新函数检查是否加载了用户,否则重定向到登录页面。如果加载了用户,则调用原始视图并正常继续。在编写博客视图时,您将使用此装饰器。

端点和 URL

url_for() 函数根据名称和参数生成指向视图的 URL。与视图关联的名称也称为端点,默认情况下,它与视图函数的名称相同。

例如,本教程前面添加到应用程序工厂的 hello() 视图的名称为 'hello',可以使用 url_for('hello') 链接到它。如果它接受一个参数(您稍后会看到),则可以使用 url_for('hello', who='World') 链接到它。

当使用蓝图时,蓝图的名称会添加到函数名称的前面,因此您在上面编写的 login 函数的端点是 'auth.login',因为您将其添加到 'auth' 蓝图。

继续阅读 模板