在上一章中,咱們已經建立了數據庫以及學會了使用它來存儲用戶以及 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 處理的方式的不一樣咱們提供了相應的方式。
如今咱們已經準備好用 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 裝飾器的函數。若是失敗的話,用戶將會回到登錄頁面。
這裏就是咱們的 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 爲了決定用戶是否已經登陸。爲了實現這個咱們用 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。