Flask官方文档中的留言板项目解说之auth.py说明(4)

说明

用户的认证,有登录、登出、注册,但是编辑留言板需要确保是登录状态。遇到大的项目,很多地方都需要是登录状态,每次先判断是否登录再执行相关操作会很麻烦。因此需要一个登录要求的函数,作为装饰器。

通常,用户登录后会把登录信息记录到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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355