(五)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

相關文章
相關標籤/搜索