说明
用户的认证,有登录、登出、注册,但是编辑留言板需要确保是登录状态。遇到大的项目,很多地方都需要是登录状态,每次先判断是否登录再执行相关操作会很麻烦。因此需要一个登录要求的函数,作为装饰器。
通常,用户登录后会把登录信息记录到session中,再次登录就可以从session中查找是否有此用户,这样可以降低服务器的压力。那么问题又来了,在请求视图前要求,还是到达服务器之前就要求呢,这里就要用到Flask的4个钩子函数中的一个。
分析完后,就清楚,这个auth.py文件需要用到的函数是
- register() 用户注册
- login() 用户登录
- load_logged_in_user() 判断用户登录信息是否在session中
- logout() 用户登出
- login_required() 装饰器函数,要求编辑前是登录状态
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")
@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 register.".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('/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')
@bp.before_app_request
def load_logged_in_user():
""" 客户端请求服务器之前,需要先运行这个函数,判断用户是否再session中"""
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.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
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
总结
Blueprint在上节已做说明,不再细讲。
register函数,先判断http的方法是否是post,即是否有点击register按钮。若是,先保留用户名密码,判断是否为空,不为空则验证用户名是否在数据库中。error是一个标志,前几步都OK,error会是None,则把注册的用户名密码插入到user表中,再跳转到登录页面。当然,也有的项目会要求,正常注册后就已经是登录状态,那么redirect函数可以重定向到index。
login函数,上同,表中有username,重新刷新session后就跳转到index页面。
带有钩子函数装饰器的load_logged_in_user()函数,表示客户端请求视图前需要的操作。下篇重点介绍应用钩子函数、Blueprint钩子函数。
logout函数, 退出时清理session即可,下次登录时,session中没有user_id,就需要重新输入用户名密码。
login_required函数,起到装饰器的作用。比如在delete函数上添加@login_required
, 即当执行delete函数前会先执行login_required,如果未登录就跳转到auth.login
,要求用户登录。如果没有functools.wraps(view)
函数的函数名会被装饰器改变,当一个函数被两个装饰器装饰时,由于函数名不一致,会报错。而functools.wraps则把装饰器改过的函数名再改会原函数名。
无function.wraps的代码如下:
def description(f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
@description
def num1():
print(num1.__name__)
@description
def num2():
print(num2.__name__)
if __name__ == '__main__':
num1()
num2()
输出结果为:
wrapper
wrapper
添加functools.wraps后:
import functools
def description(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
@description
def num1():
print(num1.__name__)
@description
def num2():
print(num2.__name__)
if __name__ == '__main__':
num1()
num2()
输出结果为:
num1
num2