蓝图和视图

视图函数是您编写以响应应用程序请求的代码。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 检查 session 中是否存储了用户 ID,并从数据库中获取该用户的数据,将其存储在 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' 蓝图。

继续到 模板