Flask學習之五 用戶登錄


英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-logins

中文翻譯地址:http://www.pythondoc.com/flask-mega-tutorial/userlogin.html

開源中國社區:http://www.oschina.net/translate/the-flask-mega-tutorial-part-v-user-logins

 

備注:我是三個一起看的,有些部分的中文翻譯太拗口而且還有錯,因此我選擇是比較清晰的中文解釋,而有些部分是直接翻譯英文博客。

上一部分: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))

備注:其實我現在對python中的@符號的用法還是不甚明了。

注意在 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.loginhandler。它告訴Flask-OpenID這是我們的登錄視圖函數。

在函數開始的時候我們就檢查 g.user 是不是一個已經認證的用戶,如果已經認證就直接跳轉到主頁面,避免二次登錄。

Flask 中的 g 全局變量是一個在請求生命周期中用來存儲和共享數據。登錄的用戶存儲在這里(g)。

我們在調用redirect()時使用的url_for()方法是Flask定義的從給定的view方法獲取url。如果你想重定向到index頁面,你很可能使用redirect('/index'),但是讓Flask為你構造url是有好處的。見 http://dormousehole.readthedocs.org/en/latest/quickstart.html#url 

當我們從登錄表單得到返回數據,接下來要運行的代碼也是新寫的。這兒我們做兩件事。首先我們保存remember_me的布爾值到Flask的 session中,別和Flask-SQLAlchemy的db.session混淆了。之前我們已經知道 flask.g 對象在請求整個生命周期中存儲和共享數據。flask.session 提供了一個更加復雜的服務對於存儲和共享數據。一旦數據存儲在會話對象中,在來自同一客戶端的現在和任何以后的請求都是可用的。數據將保持在session中直到被明確的移除。為了做到這些,Flask為每個客戶端建立各自的 session。

oid.try_login通過Flask-OpenID來執行用戶認證。該函數有兩個參數,用戶在 web 表單提供的 openid 以及我們從 OpenID 提供商得到的數據項列表。因為我們已經在用戶模型類中定義了 nicknameemail,這也是我們將要從 OpenID 提供商索取的。

基於OpenID的認證是異步的。如果認證成功,Flask-OpenID將調用有由oid.after_login裝飾器注冊的方法。如果認證失敗那么用戶會被重定向到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)
        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 僅僅是為了驗證。我們要求一個有效的email,所以如果不提供email,我們是沒法讓用戶登錄的。

接下來,我們將根據email查找數據庫。如果email沒有被找到我們就認為這是一個新的用戶,所以我們將在數據庫中增加一個新用戶,做法就像我們從之前章節學到的一樣。注意我們沒有處理nickname,因為一些OpenID provider並沒有包含這個信息。

接着我們將從Flask session中獲取remember_me的值,如果它存在,那它是我們之前在login view方法中保存到session中的boolean類型的值。

然后,為了注冊這個有效的登錄,我們調用 Flask-Login 的 login_user 函數。

最后,如果在 next 頁沒有提供的情況下,我們會重定向到首頁,否則會重定向到 next 頁。

跳轉到下一頁的這個概念很簡單。比方說我們需要你登錄才能導航到一個頁面,但你現在並未登錄。在Flask-Login中你可以通過 login_required裝飾器來限定未登錄用戶。如果一個用戶想連接到一個限定的url,那么他將被自動的重定向到login頁面。Flask- Login將保存最初的url作為下一個頁面,一旦登錄完成我們便跳轉到這個頁面。

做這個工作Flask-Login需要知道用戶當前在那個頁面。我們可以在app的初始化組件里配置它(文件 app/__init__.py):

lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'

備注:在修改(文件 app/__init__.py)的時候 “from app import views, models” 這句話需要放到最后,否則會報錯,找不到 lm。

lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'
oid = OpenID(app, os.path.join(basedir, 'tmp'))

from app import views, models

 

六、全局變量g.user

在login view方法中我們通過檢查g.user來判斷一個用戶是否登錄了。為了實現這個我們將使用Flask提供的before_request事件。

任何一個被before_request裝飾器裝飾的方法將會在每次request請求被收到時提前與view方法執行。

在這兒來設置我們的g.user變量(文件 app/views.py):

@app.before_request
def before_request():
    g.user = current_user

 

七、index視圖

之前的index視圖是不適合現在的,修改如下(文件 app/views.py):

@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傳給了模板,替換了之間的假對象。

 

運行后,在地址欄輸入http://127.0.0.1:5000/ 會被重定向到登錄頁面

備注:我用的是yahoo的OpenID登錄的,要用OpenID,你得先激活yahoo的OpenID,激活方法自行搜索,這里不贅述了。

我的登錄時間有點長,這是我登錄后的主頁。

登錄后沒有登出之前你是沒辦法再回到登錄頁面的,它自動重定向回來。

 

八、用戶登出

登出的視圖函數是相當地簡單(文件 app/views.py):

@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))

但我們在模板中還沒有注銷登錄的鏈接。我們將在base.html中的頂部導航欄添加這個鏈接(文件 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>

修改后頁面:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM