重访我们的用户模型
Flask-Login扩展需要在我们的User类里实现一些方法。除了这些方法以外,类没有被要求实现其它方法。
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方法是一个误导性的名字的方法,通常这个方法应该返回True,除非对象代表一个由于某种原因没有被认证的用户。
def is_authenticated(self):
return True
#is_active方法应该为用户返回True除非用户不是激活的,例如,他们已经被禁了。
def is_active(self):
return True
#is_anonymous方法应该为那些不被获准登录的用户返回True
def is_anonymous(self):
return False
#get_id方法为用户返回唯一的unicode标识符。我们用数据库层生成唯一的id。
def get_id(self):
return unicode(self.id)
def __repr__(self):
return '<User %r>' % (self.name)
用户加载回调
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
函数原型
flask.ext.login.login_user(user, remember=False, force=False, fresh=True)
记住Flask-Login里的user id一直是unicode类型的,所以在我们把id传递给Flask-SQLAlchemy时,有必要把它转化成integer类型.
登录视图函数
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'])
给视图函数添加了一个新的装饰器:oid.loginhandler。它告诉Flask-OpenID这是我们的登录视图函数。
在方法体的开头,我们检测是是否用户是已经经过登录认证的,如果是就重定向到index页面。这儿的思路是如果一个用户已经登录了,那么我们不会让它做二次登录。
全局变量g是Flask设置的,在一个request生命周期中,用来存储和共享数据的变量。所以我猜你已经想到了,我们将把已经登录的用户放到g变量里。
我们在调用redirect()时使用的url_for()方法是Flask定义的从给定的view方法获取url。如果你想重定向到index页面,你h很可能使用redirect('/index'),但是我们有很好的理由让Flask为你构造url。
当我们从登录表单得到返回数据,接下来要运行的代码也是新写的。这儿我们做两件事。首先我们保存remember_me的布尔值到Flask的session中,别和Flask-SQLAlchemy的db.session混淆了。我们已经知道在一个request的生命周期中用Flask的g对象来保存和共享数据。沿着这条线路Flask的session提供了更多,更复杂的服务。一旦数据被保存到session中,它将在同一客户端发起的这次请求和这次以后的请求中永存而不会消亡。数据将保持在session中直到被明确的移除。为了做到这些,Flask为每个客户端建立各自的session。
下面的oid.try_login是通过Flask-OpenID来执行用户认证。这个方法有两个参数,web表单提供的openid和OpenID provider提供的我们想要的list数据项。由于我们定义了包含nickname和email的User类,所以我们要从找nickname和email这些项。
基于OpenID的认证是异步的。如果认证成功,Flask-OpenID将调用有由oid.after_login装饰器注册的方法。如果认证失败那么用户会被重定向到login页面。
Flask-OpenID登录回调
@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参数包含了OpenID provider返回的一些信息。
第一个if声明仅仅是为了验证。我们要求一个有效的email,所以一个没有没提供的email我们是没法让他登录的。
全局变量g.user
如果你注意力很集中,那么你应该记得在login view方法中我们通过检查g.user来判断一个用户是否登录了。为了实现这个我们将使用Flask提供的before_request事件。任何一个被before_request装饰器装饰的方法将会在每次request请求被收到时提前与view方法执行。所以在这儿来设置我们的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)
在这个方法中只有两处变动。首先,我们增加了login_required装饰器。这样表明了这个页面只有登录用户才能访问。
另一个改动是把g.user传给了模板,替换了之间的假对象。
注销登录
@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版本有问题,等他有时间再改,先用0.2.11。
好,果断换回旧版本:
pip uninstall Flask-Login
pip install -v Flask-Login==0.2.11
网友评论