重訪我們的用戶模型
Flask-Login擴(kuò)展需要在我們的User類里實(shí)現(xiàn)一些方法。除了這些方法以外参袱,類沒有被要求實(shí)現(xiàn)其它方法善炫。
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')
#is_authenticated方法是一個(gè)誤導(dǎo)性的名字的方法,通常這個(gè)方法應(yīng)該返回True隘击,除非對(duì)象代表一個(gè)由于某種原因沒有被認(rèn)證的用戶瘟仿。
def is_authenticated(self):
return True
#is_active方法應(yīng)該為用戶返回True除非用戶不是激活的箱锐,例如,他們已經(jīng)被禁了劳较。
def is_active(self):
return True
#is_anonymous方法應(yīng)該為那些不被獲準(zhǔn)登錄的用戶返回True
def is_anonymous(self):
return False
#get_id方法為用戶返回唯一的unicode標(biāo)識(shí)符驹止。我們用數(shù)據(jù)庫(kù)層生成唯一的id浩聋。
def get_id(self):
return unicode(self.id)
def __repr__(self):
return '<User %r>' % (self.name)
用戶加載回調(diào)
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
函數(shù)原型
flask.ext.login.login_user(user, remember=False, force=False, fresh=True)
記住Flask-Login里的user id一直是unicode類型的,所以在我們把id傳遞給Flask-SQLAlchemy時(shí)臊恋,有必要把它轉(zhuǎn)化成integer類型.
登錄視圖函數(shù)
from flask import render_template, flash, redirect, session, url_for, request, g
from flaskext.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'])
給視圖函數(shù)添加了一個(gè)新的裝飾器:oid.loginhandler衣洁。它告訴Flask-OpenID這是我們的登錄視圖函數(shù)。
在方法體的開頭抖仅,我們檢測(cè)是是否用戶是已經(jīng)經(jīng)過登錄認(rèn)證的闸与,如果是就重定向到index頁面。這兒的思路是如果一個(gè)用戶已經(jīng)登錄了岸售,那么我們不會(huì)讓它做二次登錄。
全局變量g是Flask設(shè)置的厂画,在一個(gè)request生命周期中凸丸,用來存儲(chǔ)和共享數(shù)據(jù)的變量。所以我猜你已經(jīng)想到了袱院,我們將把已經(jīng)登錄的用戶放到g變量里屎慢。
我們?cè)谡{(diào)用redirect()時(shí)使用的url_for()方法是Flask定義的從給定的view方法獲取url。如果你想重定向到index頁面忽洛,你h很可能使用redirect('/index')腻惠,但是我們有很好的理由讓Flask為你構(gòu)造url。
當(dāng)我們從登錄表單得到返回?cái)?shù)據(jù)欲虚,接下來要運(yùn)行的代碼也是新寫的集灌。這兒我們做兩件事。首先我們保存remember_me的布爾值到Flask的session中复哆,別和Flask-SQLAlchemy的db.session混淆了欣喧。我們已經(jīng)知道在一個(gè)request的生命周期中用Flask的g對(duì)象來保存和共享數(shù)據(jù)。沿著這條線路Flask的session提供了更多梯找,更復(fù)雜的服務(wù)唆阿。一旦數(shù)據(jù)被保存到session中,它將在同一客戶端發(fā)起的這次請(qǐng)求和這次以后的請(qǐng)求中永存而不會(huì)消亡锈锤。數(shù)據(jù)將保持在session中直到被明確的移除驯鳖。為了做到這些,F(xiàn)lask為每個(gè)客戶端建立各自的session久免。
下面的oid.try_login是通過Flask-OpenID來執(zhí)行用戶認(rèn)證浅辙。這個(gè)方法有兩個(gè)參數(shù),web表單提供的openid和OpenID provider提供的我們想要的list數(shù)據(jù)項(xiàng)阎姥。由于我們定義了包含nickname和email的User類摔握,所以我們要從找nickname和email這些項(xiàng)。
基于OpenID的認(rèn)證是異步的丁寄。如果認(rèn)證成功氨淌,F(xiàn)lask-OpenID將調(diào)用有由oid.after_login裝飾器注冊(cè)的方法泊愧。如果認(rèn)證失敗那么用戶會(huì)被重定向到login頁面。
Flask-OpenID登錄回調(diào)
@oid.after_login
def after_login(resp):
if resp.email is None or resp.email == "":
flash('Invalid login. Please try again.')
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'))
傳給after_login方法的resp參數(shù)包含了OpenID provider返回的一些信息盛正。
第一個(gè)if聲明僅僅是為了驗(yàn)證删咱。我們要求一個(gè)有效的email,所以一個(gè)沒有沒提供的email我們是沒法讓他登錄的豪筝。
全局變量g.user
如果你注意力很集中痰滋,那么你應(yīng)該記得在login view方法中我們通過檢查g.user來判斷一個(gè)用戶是否登錄了。為了實(shí)現(xiàn)這個(gè)我們將使用Flask提供的before_request事件续崖。任何一個(gè)被before_request裝飾器裝飾的方法將會(huì)在每次request請(qǐng)求被收到時(shí)提前與view方法執(zhí)行敲街。所以在這兒來設(shè)置我們的g.user變量
@app.before_request
def before_request():
g.user = current_user
index視圖
@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)
在這個(gè)方法中只有兩處變動(dòng)。首先严望,我們?cè)黾恿薼ogin_required裝飾器多艇。這樣表明了這個(gè)頁面只有登錄用戶才能訪問。
另一個(gè)改動(dòng)是把g.user傳給了模板像吻,替換了之間的假對(duì)象峻黍。
注銷登錄
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
<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>
***碰到的問題:
if g.user is not None and g.user.is_authenticated():
TypeError: 'bool' object is not callable
原因居然是:
https://plus.google.com/+MiguelGrinberg/posts/9o9idDfaYhK,尼瑪拨匆,Miguel Grinberg這貨說Flask-Login的0.3版本有問題姆涩,等他有時(shí)間再改,先用0.2.11惭每。
好骨饿,果斷換回舊版本:
pip uninstall Flask-Login
pip install -v Flask-Login==0.2.11