(五)Flask 学习 —— 用户登陆

用户登陆

回顾

在上一章中,咱们已经建立了数据库以及学会了使用它来存储用户以及 blog,可是咱们并无把它融入咱们的应用程序中。在两章之前,咱们已经看到如何建立表单而且留下了一个彻底实现的登陆表单。html

在本章中咱们将会创建 web 表单和数据库的联系,而且编写咱们的登陆系统。在本章结尾的时候,咱们这个小型的应用程序将可以注册新用户而且可以登入和登出。python

咱们接下来说述的正是咱们上一章离开的地方,因此你可能要确保应用程序 microblog 正确地安装和工做。git

配置

像之前章节同样,咱们从配置将会使用到的 Flask 扩展开始入手。对于登陆系统,咱们将会使用到两个扩展,Flask-Login 和 Flask-OpenID。配置状况以下(文件 app/__init__.py):github

import os
from flask.ext.login import LoginManager
from flask.ext.openid import OpenID
from config import basedir

lm = LoginManager()
lm.init_app(app)

oid = OpenID(app, os.path.join(basedir, 'tmp'))

Flask-OpenID 扩展须要一个存储文件的临时文件夹的路径。对此,咱们提供了一个 tmp 文件夹的路径。web

重构用户模型

Flask-Login 扩展须要在咱们的 User 类中实现一些特定的方法。可是类如何去实现这些方法却没有什么要求。数据库

下面就是咱们为 Flask-Login 实现的 User 类(文件 app/models.py):flask

class User(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    nickname = db.Column(db.String(64), unique = True)
    email = db.Column(db.String(120), unique = True)
    posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')

    def is_authenticated(self):
        return True

    def is_active(self):
        return True

    def is_anonymous(self):
        return False

    def get_id(self):
        try:
            return unicode(self.id)  # python 2
        except NameError:
            return str(self.id)  # python 3

    def __repr__(self):
        return '<User %r>' % (self.nickname)

is_authenticated 方法有一个具备迷惑性的名称。通常而言,这个方法应该只返回 True,除非表示用户的对象由于某些缘由不容许被认证。session

is_active 方法应该返回 True,除非是用户是无效的,好比由于他们的帐号是被禁止。app

is_anonymous 方法应该返回 True,除非是伪造的用户不容许登陆系统。异步

最后,get_id 方法应该返回一个用户惟一的标识符,以 unicode 格式。咱们使用数据库生成的惟一的 id。须要注意地是在 Python 2 和 3 之间因为 unicode 处理的方式的不一样咱们提供了相应的方式。

user_loader 回调

如今咱们已经准备好用 Flask-Login 和 Flask-OpenID 扩展来开始实现登陆系统。

首先,咱们必须编写一个函数用于从数据库加载用户。这个函数将会被 Flask-Login 使用(文件 app/views.py):

@lm.user_loa
derdef load_user(id):
    return User.query.get(int(id))

请注意在 Flask-Login 中的用户 ids 永远是 unicode 字符串,所以在咱们把 id 发送给 Flask-SQLAlchemy 以前,把 id 转成整型是必须的,不然会报错!

登陆视图函数

接下来咱们须要更新咱们的登陆视图函数(文件 app/views.py):

from flask import render_template, flash, redirect, session, url_for, request, g
from flask.ext.login import login_user, logout_user, current_user, login_required
from app import app, db, lm, oid
from .forms import LoginForm
from .models import User

@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
    if g.user is not None and g.user.is_authenticated():
        return redirect(url_for('index'))
    form = LoginForm()
    if form.validate_on_submit():
        session['remember_me'] = form.remember_me.data
        return oid.try_login(form.openid.data, ask_for=['nickname', 'email'])
    return render_template('login.html',
                           title='Sign In',
                           form=form,
                           providers=app.config['OPENID_PROVIDERS'])

注意咱们这里导入了很多新的模块,一些模块咱们将会在不久后使用到。

跟以前的版本的改动是很是小的。咱们在视图函数上添加一个新的装饰器。oid.loginhandle 告诉 Flask-OpenID 这是咱们的登陆视图函数。

在函数开始的时候,咱们检查 g.user 是否被设置成一个认证用户,若是是的话将会被重定向到首页。这里的想法是若是是一个已经登陆的用户的话,就不须要二次登陆了。

Flask 中的 g 全局变量是一个在请求生命周期中用来存储和共享数据。我敢确定你猜到了,咱们将登陆的用户存储在这里(g)。

咱们在 redirect 调用中使用的 url_for 函数是定义在 Flask 中,以一种干净的方式为一个给定的视图函数获取 URL。若是你想要重定向到首页你可能会常用 redirect(‘/index’),可是有不少 好理由 让 Flask 为你构建 URLs。

当咱们从登陆表单获取的数据后的处理代码也是新的。这里咱们作了两件事。首先,咱们把 remember_me 布尔值存储到 flask 的会话中,这里别与 Flask-SQLAlchemy 中的 db.session 弄混淆。以前咱们已经知道 flask.g 对象在请求整个生命周期中存储和共享数据。flask.session 提供了一个更加复杂的服务对于存储和共享数据。一旦数据存储在会话对象中,在来自同一客户端的如今和任何之后的请求都是可用的。数据保持在会话中直到会话被明确地删除。为了实现这个,Flask 为咱们应用程序中每个客户端设置不一样的会话文件。

在接下来的代码行中,oid.try_login 被调用是为了触发用户使用 Flask-OpenID 认证。该函数有两个参数,用户在 web 表单提供的 openid 以及咱们从 OpenID 提供商获得的数据项列表。由于咱们已经在用户模型类中定义了nickname 和 email,这也是咱们将要从 OpenID 提供商索取的。

OpenID 认证异步发生。若是认证成功的话,Flask-OpenID 将会调用一个注册了 oid.after_login 装饰器的函数。若是失败的话,用户将会回到登录页面。

Flask-OpenID 登陆回调

这里就是咱们的 after_login 函数的实现(文件 app/views.py):

@oid.after_logindef after_login(resp):
    if resp.email is None or resp.email == "":
        flash('Invalid login. Please try again.')
        return redirect(url_for('login'))
    user = User.query.filter_by(email=resp.email).first()
    if user is None:
        nickname = resp.nickname
        if nickname is None or nickname == "":
            nickname = resp.email.split('@')[0]
        user = User(nickname=nickname, email=resp.email)
        db.session.add(user)
        db.session.commit()
    remember_me = False
    if 'remember_me' in session:
        remember_me = session['remember_me']
        session.pop('remember_me', None)
    login_user(user, remember = remember_me)
    return redirect(request.args.get('next') or url_for('index'))

resp 参数传入给 after_login 函数,它包含了从 OpenID 提供商返回来的信息。

第一个 if 只是为了验证。咱们须要一个合法的邮箱地址,所以提供邮箱地址是不能登陆的。

接下来,咱们从数据库中搜索邮箱地址。若是邮箱地址不在数据库中,咱们认为是一个新用户,由于咱们会添加一个新用户到数据库。注意例子中咱们处理空的或者没有提供的 nickname 方式,由于一些 OpenID 提供商可能没有它的信息。

接着,咱们从 flask 会话中加载 remember_me 值,这是一个布尔值,咱们在登陆视图函数中存储的。

而后,为了注册这个有效的登陆,咱们调用 Flask-Login 的 login_user 函数。

最后,若是在 next 页没有提供的状况下,咱们会重定向到首页,不然会重定向到 next 页。

若是要让这些都起做用的话,Flask-Login 须要知道哪一个视图容许用户登陆。咱们在应用程序模块初始化中配置(文件 app/__init__.py):

lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'

全局变量 g.user

若是你观察仔细的话,你会记得在登陆视图函数中咱们检查 g.user 为了决定用户是否已经登陆。为了实现这个咱们用 Flask 的 before_request 装饰器。任何使用了 before_request 装饰器的函数在接收请求以前都会运行。 所以这就是咱们设置咱们 g.user 的地方(文件 app/views.py):

@app.before_request
def before_request():
    g.user = current_user

这就是全部须要作的。全局变量 current_user 是被 Flask-Login 设置的,所以咱们只须要把它赋给 g.user ,让访问起来更方便。有了这个,全部请求将会访问到登陆用户,即便在模版里。

首页视图

在前面的章节中,咱们的 index 视图函数使用了伪造的对象,由于那时候咱们并无用户或者 blog。好了,如今咱们有用户了,让咱们使用它:

@app.route('/')
@app.route('/index')
@login_required
def index():
    user = g.user
    posts = [
        {
            'author': { 'nickname': 'John' },
            'body': 'Beautiful day in Portland!'
        },
        {
            'author': { 'nickname': 'Susan' },
            'body': 'The Avengers movie was so cool!'
        }
    ]
    return render_template('index.html',
        title = 'Home',
        user = user,
        posts = posts)

上面仅仅只有两处变化。首先,咱们添加了 login_required 装饰器。这确保了这页只被已经登陆的用户看到。

另一个变化就是咱们把 g.user 传入给模版,代替以前使用的伪造对象。

这是运行应用程序最好的时候了!

登出

咱们已经实现了登陆,如今是时候增长登出的功能。

登出的视图函数是至关地简单(文件 app/views.py):

@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))

可是咱们尚未在模版中添加登出的连接。咱们将要把这个连接放在基础层中的导航栏里(文件 app/templates/base.html):

<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog:
        <a href="{{ url_for('index') }}">Home</a>
        {% if g.user.is_authenticated() %}
        | <a href="{{ url_for('logout') }}">Logout</a>
        {% endif %}
    </div>
    <hr>
    {% with messages = get_flashed_messages() %}
    {% if messages %}
    <ul>
    {% for message in messages %}
        <li>{{ message }} </li>
    {% endfor %}
    </ul>
    {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
  </body>
</html>

实现起来是否是很简单?咱们只须要检查有效的用户是否被设置到 g.user 以及是否咱们已经添加了登出连接。咱们也正好利用这个机会在模版中使用 url_for

结束语

咱们如今已经有一个彻底实现的登陆系统。在下一章中,咱们将会建立用户信息页以及将会显示用户头像。

若是你想要节省时间的话,你能够下载 microblog-0.5.zip

相关文章
相关标签/搜索