flask入門的教程-用戶登陸 User Logins

文章轉自 :https://github.com/WapeYang/The-Flask-Mega-Tutorial/blob/master/userlogin.rst

感謝原作者的付出

轉載時間爲:2014-05-06



用戶登錄

回顧

在上一章中,我們已經創建了數據庫以及學會了使用它來存儲用戶以及 blog,但是我們並沒有把它融入我們的應用程序中。在兩章以前,我們已經看到如何創建表單並且留下了一個完全實現的登錄表單。

在本章中我們將會建立 web 表單和數據庫的聯繫,並且編寫我們的登錄系統。在本章結尾的時候,我們這個小型的應用程序將能夠註冊新用戶並且能夠登入和登出。

我們接下來講述的正是我們上一章離開的地方,所以你可能要確保應用程序 microblog 正確地安裝和工作。

配置

像以前章節一樣,我們從配置將會使用到的 Flask 擴展開始入手。對於登錄系統,我們將會使用到兩個擴展,Flask-Login 和 Flask-OpenID。配置情況如下(文件 app__init__.py):

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 文件夾的路徑。

重構用戶模型

Flask-Login 擴展需要在我們的 User 類中實現一些特定的方法。但是類如何去實現這些方法卻沒有什麼要求。

下面就是我們爲 Flask-Login 實現的 User 類(文件 app/models.py):

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)
    role = db.Column(db.SmallInteger, default = ROLE_USER)
    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):
        return unicode(self.id)

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

is_authenticated 方法有一個具有迷惑性的名稱。一般而言,這個方法應該只返回 True,除非表示用戶的對象因爲某些原因不允許被認證。

is_active 方法應該返回 True,除非是用戶是無效的,比如因爲他們的賬號是被禁止。

is_anonymous 方法應該返回 True,除非是僞造的用戶不允許登錄系統。

最後,get_id 方法應該返回一個用戶唯一的標識符,以 unicode 格式。我們使用數據庫生成的唯一的 id。

user_loader 回調

現在我們已經準備好用 Flask-Login 和 Flask-OpenID 擴展來開始實現登錄系統。

首先,我們必須編寫一個函數用於從數據庫加載用戶。這個函數將會被 Flask-Login 使用(文件 app/views.py):

@lm.user_loader
def 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, ROLE_USER, ROLE_ADMIN

@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_login
def 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, role = ROLE_USER)
        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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章