蓝图和视图(Blueprint 和 View)
视图函数是为响应应用程序请求而编写的代码。Flask 使用模式将传入的请求 URL 与应该处理它的视图相匹配。Flask 将视图返回的数据转换为传出响应。Flask 也可以重定向,根据名称和参数生成一个指向视图的 URL。
创建蓝图
蓝图是一种组织一组相关视图和其他代码的方法。它们不是直接向应用程序注册视图和其他代码,而是向蓝图注册。然后,当蓝图在工厂函数中可用时,它将在应用程序中注册。
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() 注册蓝图。将新代码放在工厂函数的末尾,返回 app 之前。
# flaskr/__init__.py
def create_app():
app = ...
# existing code omitted
from . import auth
app.register_blueprint(auth.bp)
return app
复制
鉴权蓝图将具有新用户注册、登录和注销的视图。
第一个视图:Register
当用户访问 /auth/register URL 时,register 视图将返回 HTML,此 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 是一种特殊类型的字典映射,包含表单键和值。用户将输入他们的 username 和 password。
4. 验证 username 和 password 是否为空。
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 的模板,在本教程的后面将编写该模板。
Login
此视图与上面的 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。
Logout
要注销,需要从 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
复制
此装饰器返回一个新的视图函数,该函数包装它应用的原始视图。新函数检查用户是否已加载,否则将重定向到登录页面。如果加载了用户,则调用原始视图并正常继续下去。在编写博客视图时,将使用此装饰器。
端点(Endpoint)和 URL
url_for() 函数根据名称和参数生成视图的 URL。与视图关联的名称也称为端点,默认情况下,它与视图函数的名称相同。
例如,在本教程前面添加到应用程序工厂的 hello() 视图名为 'hello',可以通过 url_for('hello') 构造 URL。如果它有一个参数,可以如此构造 URL: url_for('hello', who='World')。
使用蓝图时,蓝图的名称加在函数名之前,因此上面编写的 login 函数的端点是 'auth.login',因为已将其添加到 'auth' 蓝图中。
原文:
https://flask.palletsprojects.com/en/2.0.x/tutorial/views/