蓝图和视图

视图函数是为响应对应用程序的请求而编写的代码。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() <Flask.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 /register 和 register 视图函数。当 Flask 收到一个指向 /auth/register 的请求时就会调用 register 视图并把其返回值作为响应。

  2. 如果用户提交了表单,那么 request.method 将会是 'POST' 。这咱情况下 会开始验证用户的输入内容。

  3. request.form 是一个特殊类型的 dict ,其映射了提交表单的键和值。表单中,用户将会输入其 username 和 password 。

  4. 验证 usernamepassword 不是空的。

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

    • db.execute 使用以下语句进行SQL查询 ? 任何用户输入的占位符,以及要用来替换占位符的值的元组。数据库库将负责转义值,这样您就不会受到 SQL injection attack

    • 出于安全考虑,密码不应直接存储在数据库中。相反, generate_password_hash() 用于安全地对密码进行哈希处理,并存储该哈希。由于该查询修改数据, db.commit() 需要在之后调用以保存更改。

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

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

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

  8. 当用户最初访问 auth/register 时,或者注册出错时,应用显示一个注册表单。 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() <Blueprint.before_app_request> 注册在视图函数之前运行的函数,无论请求什么URL, load_logged_in_user 检查用户ID是否存储在`session` 从数据库中获取该用户的数据,并将其存储在`g.user <g>` 。g.user 的持续时间比请求要长。 如果没有用户 id,或者 id 不存在,那么 g.user 将会是 None 。

注销

注销的时候需要把用户 id 从 session 中移除。然后 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'``蓝图。

下面请阅读:templates .