蓝图和视图
视图函数的作用是响应应用中的请求,flask使用patterns来匹配传过来的请求url,视图函数可以返回用于响应的数据,也可以根据视图名字和参数来生成导向其他视图的url。
创建一个蓝图
蓝图是组织有联系的视图的一种方式,这些有联系的视图并非直接伴随着一个应用而生,而是在蓝图中进行登记。当蓝图在工厂函数中可以调用时,便会在应用中对其进行登记。
本项目将会有两个蓝图,一个蓝图用于用户的认证,另一个蓝图用于博客发帖。每个蓝图的代码将会被置于独立的模块中。
首先,我们来写认证的功能。
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”的蓝图,和应用对象一样,蓝图也必须得知道它是在哪里定义的,因此__name__便传递作为它的第二个参数,url_prefix()将会返回与蓝图相联系的整个url。
从工厂中导入以及注册蓝图要使用app.register_blueprint(),
def create_app():
app = ...
# existing code omitted
from . import auth
app.register_blueprint(auth.bp)
return app
认证蓝图将会有视图注册新用户以及登录和登出。
第一个视图:登录
当用户访问/auth/register 这个url时,register视图会返回一个带有着由其填写成的表单的HTML页面(这句不怎么会翻译,return HTML with a form for them to fill out.),当表单被提交时,视图会判断输入是否合理。如果不合理的话会返回一个错误的信息,合理的话则会创建一个新用户并会跳转到登录的界面。
到目前你只需要写视图函数的代码,在后面的部分将会写用来生成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.'
elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None:
error = 'User {} is already registered.'.format(username)
if error is None:
db.execute(
'INSERT INTO user (username, password) VALUES (?, ?)',
(username, generate_password_hash(password))
)
db.commit()
return redirect(url_for('auth.login'))
flash(error)
return render_template('auth/register.html')
注册函数到底干了什么呢?
- @bp.route将url(/register) 和register视图函数连接起来,当flask收到一个发向/auth/register的请求时,它便会调用register视图函数,并且会使用返回值作为响应。
- 如果用户提交了表单,request.method将会以POST的方式来传送数据。在上面的代码中,我们一开始便验证方法是否是POST
- request.form是一个特殊的字典类型,其描述了所提交的字符串,用户将会输入他们的用户名和密码。
- 并且验证用户名和密码是否为空
- 通过查询数据库的中的用户名来判断这个用户名是否被注册过。数据库会顾及溢出的值,因此你很难受到sql语句的注入式攻击。fetchone():会从查询中返回一行,如果查询没有结果,返回为空。fetchall(): 会返回一个结果的列表。
- 如果验证成功的话,会将新的用户数据插入到数据库中。但是为了安全,密码不应该直接被存储到数据库。存储密码之前会用generate_password_hash()将原始密码变成哈希值,并将这个哈希值存储。因为在查询的过程中会有数据的更改,所以db.commit()需要在之后被调用以便于保存最终的数据。
- 在将用户的数据存储之后,页面会被重新定向到login页面。url_for()会基于登录视图的名字来生成转向登录界面的url,这要比直接写url更好,因为它可以不用更改全部的代码就可以改变url。redirect()会生成一个跳转到给定的url的响应。
- 如果认证失败的话错误会通过flash来显现。flash()会存储一些信息,这些信息可以在渲染模板是被重新的取回。
- 当一开始用户导向/auth/register界面时,实际上会有一个错误:一个含有注册表单的页面应该会被显现。render_template()将会渲染一个HTML页面,这个HTML会在再教程的下下一步被说明。
Login
这个视图函数的模式和上面一个是相同的。
flaskr/auth.py
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')
上面的代码与注册时有几点不同之处。
- user信息会先被存储到一个变量中。
- check_password_hash()会以相同的方式散列处哈希值,并与数据库中同一user的密码哈希作对比。匹配则说明合理。
- session是一个存储数据的字典
。当验证成功时,用户的id会被存储到一个新的session中,数据会被存储到cookie中,这个cookie会被发送个浏览器端,浏览器会将cookie以及之后的请求返回。flask会标注数据以免其被干扰。
目前用户的id被存储到了session中,在之后的请求中它都可以被获取到。在每个请求的开始,如果用户已经登录,他们的信息应该被加载并且可以被其他的视图使用。
flaskr/auth.py
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,g.user将不会存在。
logout
要想登出的话,需要将用户的id从session中移除。移除之后load_logged_in_user在之后的请求中将不会加载用户数据。
flaskr/auth.py
@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
在其他的视图中获取认证
创建、编辑、删除博客都需要用户登录。flask中的一个装饰器对于每个采用此装饰器的视图能够检查这一点。
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”)
在使用蓝图时,蓝图的名字是先于函数的名字被考虑的,因此对于登录函数而言你在上面写的端点的名字是auth.login因为你将login这个视图加到了auth这个蓝图中。