falsk-web 表單

web 表單

回顧

在上一章節中,我們定義了一個簡單的模板,使用佔位符來虛擬了暫未實現的部分,比如用戶以及文章等。

在本章我們將要講述應用程序的特性之一–表單,我們將會詳細討論如何使用 web 表單。

Web 表單是在任何一個 web 應用程序中最基本的一部分。我們將使用表單允許用戶寫文章,以及登錄到應用程序中。

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

配置

爲了能夠處理 web 表單,我們將使用 Flask-WTF ,該擴展封裝了 WTForms 並且恰當地集成進 Flask 中。

許多 Flask 擴展需要大量的配置,因此我們將要在 microblog 文件夾的根目錄下創建一個配置文件以至於容易被編輯。這就是我們將要開始的(文件 config.py):

CSRF_ENABLED = TrueSECRET_KEY = 'you-will-never-guess'

十分簡單吧,我們的 Flaks-WTF 擴展只需要兩個配置。 CSRF_ENABLED 配置是爲了激活 跨站點請求僞造 保護。在大多數情況下,你需要激活該配置使得你的應用程序更安全些。

SECRET_KEY 配置僅僅當 CSRF 激活的時候才需要,它是用來建立一個加密的令牌,用於驗證一個表單。當你編寫自己的應用程序的時候,請務必設置很難被猜測到密鑰。

既然我們有了配置文件,我們需要告訴 Flask 去讀取以及使用它。我們可以在 Flask 應用程序對象被創建後去做,方式如下(文件 app/__init__.py):

from flask import Flaskapp = Flask(__name__)app.config.from_object('config')from app import views

用戶登錄表單

在 Flask-WTF 中,表單是表示成對象,Form 類的子類。一個表單子類簡單地把表單的域定義成類的變量。

我們將要創建一個登錄表單,用戶用於認證系統。在我們應用程序中支持的登錄機制不是標準的用戶名/密碼類型,我們將使用 OpenID。OpenIDs 的好處就是認證是由 OpenID 的提供者完成的,因此我們不需要驗證密碼,這會讓我們的網站對用戶而言更加安全。

OpenID 登錄僅僅需要一個字符串,被稱爲 OpenID。我們將在表單上提供一個 ‘remember me’ 的選擇框,以至於用戶可以選擇在他們的網頁瀏覽器上種植 cookie ,當他們再次訪問的時候,瀏覽器能夠記住他們的登錄。

所以讓我們編寫第一個表單(文件 app/forms.py):

from flask.ext.wtf import Formfrom wtforms import StringField, BooleanFieldfrom wtforms.validators import DataRequiredclass LoginForm(Form):
    openid = StringField('openid', validators=[DataRequired()])
    remember_me = BooleanField('remember_me', default=False)

我相信這個類不言而明。我們導入 Form 類,接着導入兩個我們需要的字段類,TextField 和 BooleanField

DataRequired 驗證器只是簡單地檢查相應域提交的數據是否是空。在 Flask-WTF 中有許多的驗證器,我們將會在以後看到它們。

表單模板

我們同樣需要一個包含生成表單的 HTML 的模板。好消息是我們剛剛創建的 LoginForm 類知道如何呈現爲 HTML 表單字段,所以我們只需要集中精力在佈局上。這裏就是我們登錄的模板(文件 app/templates/login.html):

<!-- extend from base layout -->
{% extends "base.html" %}

{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your OpenID:<br>
        {{form.openid(size=80)}}<br>
    </p>
    <p>`form`.`remember_me` Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

請注意,此模板中,我們重用了 base.html 模板通過 extends 模板繼承聲明語句。實際上,我們將在所有我們的模板中做到這一點,以確保所有網頁的佈局一致性。

在我們的模板與常規的 HTML 表單之間存在一些有意思的不同處。模板期望一個實例化自我們剛纔創建地表單類的表單對象儲存成一個模板參數,稱爲 form。當我們編寫渲染這個模板的視圖函數的時候,我們將會特別注意傳送這個模板參數到模板中。

form.hidden_tag() 模板參數將被替換爲一個隱藏字段,用來是實現在配置中激活的 CSRF 保護。如果你已經激活了 CSRF,這個字段需要出現在你所有的表單中。

我們表單中實際的字段也將會被表單對象渲染,你只必須在字段應該被插入的地方指明一個 `form`.`field_name` 模板參數。某些字段是可以帶參數的。在我們的例子中,我們要求表單生成一個 80 個字符寬度的 openid 字段。

因爲我們並沒有在表單中定義提交按鈕,我們必須按照普通的字段來定義。提交字段實際並不攜帶數據因此沒有必要在表單類中定義。

表單視圖

在我們看到我們表單前的最後一步就是編寫渲染模板的視圖函數的代碼。

實際上這是十分簡單因爲我們只需要把一個表單對象傳入模板中。這就是我們新的視圖函數(文件 app/views.py):

from flask import render_template, flash, redirectfrom app import appfrom .forms import LoginForm# index view function suppressed for [email protected]('/login', methods = ['GET', 'POST'])def login():
    form = LoginForm()
    return render_template('login.html',
        title = 'Sign In',
        form = form)

所以基本上,我們已經導入 LoginForm 類,從這個類實例化一個對象,接着把它傳入到模板中。這就是我們渲染表單所有要做的。

讓我們先忽略 flash 以及 redirect 的導入。我們會在後面介紹。

這裏唯一的新的知識點就是路由裝飾器的 methods 參數。參數告訴 Flask 這個視圖函數接受 GET 和 POST 請求。如果不帶參數的話,視圖只接受 GET 請求。

這個時候你可以嘗試運行應用程序,在瀏覽器上看看錶單。在你運行應用程序後,你需要在瀏覽器上打開 http://localhost:5000/login 。

我們暫時還沒有編寫接收數據的代碼,因此此時按提交按鈕不會有任何作用。

接收表單數據

Flask-WTF 使得工作變得簡單的另外一點就是處理提交的數據。這裏是我們登錄視圖函數更新的版本,它驗證並且存儲表單數據 (文件 app/views.py):

@app.route('/login', methods = ['GET', 'POST'])def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
        return redirect('/index')
    return render_template('login.html',
        title = 'Sign In',
        form = form)

validate_on_submit 方法做了所有表單處理工作。當表單正在展示給用戶的時候調用它,它會返回 False.

如果 validate_on_submit 在表單提交請求中被調用,它將會收集所有的數據,對字段進行驗證,如果所有的事情都通過的話,它將會返回 True,表示數據都是合法的。這就是說明數據是安全的,並且被應用程序給接受了。

如果至少一個字段驗證失敗的話,它將會返回 False,接着表單會重新呈現給用戶,這也將給用戶一次機會去修改錯誤。我們將會看到當驗證失敗後如何顯示錯誤信息。

當 validate_on_submit 返回 True,我們的登錄視圖函數調用了兩個新的函數,導入自 Flask。flash 函數是一種快速的方式下呈現給用戶的頁面上顯示一個消息。在我們的例子中,我將會使用它來調試,因爲我們目前還不具備用戶登錄的必備的基礎設施,相反我們將會用它來顯示提交的數據。flash 函數在生產服務器上也是十分有作用的,用來提供反饋給用戶有關的行動。

閃現的消息將不會自動地出現在我們的頁面上,我們的模板需要加入展示消息的內容。我們將添加這些消息到我們的基礎模板中,這樣所有的模板都能繼承這個函數。這是更新後的基礎模板(文件 app/templates/base.html):

<html>
  <head>
    {% if title %}
    <title>`title` - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog: <a href="/index">Home</a></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>

顯示閃現消息的技術希望是不言自明的。

在我們登錄視圖這裏使用的其它新的函數就是 redirect。這個函數告訴網頁瀏覽器引導到一個不同的頁面而不是請求的頁面。在我們的視圖函數中我們用它重定向到前面已經完成的首頁上。要注意地是,閃現消息將會顯示即使視圖函數是以重定向結束。

是到了啓動應用程序的時候,測試下表單是如何工作的。確保您嘗試提交表單的時候,OpenID 字段爲空,看看 Required 驗證器是如何中斷提交的過程。

加強字段驗證

現階段的應用程序,如果表單提交不合理的數據將不會被接受。相反,會返回表單讓用戶提交合法的數據。這確實是我們想要的。

然後,好像我們缺少了一個提示用戶表單哪裏出錯了。幸運的是,Flask-WTF 也能夠輕易地做到這一點。

當字段驗證失敗的時候, Flask-WTF 會向表單對象中添加描述性的錯誤信息。這些信息是可以在模板中使用的,因此我們只需要增加一些邏輯來獲取它。

這就是我們含有字段驗證信息的登錄模板(文件 app/templates/login.html):

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
  <h1>Sign In</h1>
  <form action="" method="post" name="login">
      {{ form.hidden_tag() }}
      <p>
          Please enter your OpenID:<br>
          {{ form.openid(size=80) }}<br>
          {% for error in form.openid.errors %}
            <span style="color: red;">[{{ error }}]</span>
          {% endfor %}<br>
      </p>
      <p>{{ form.remember_me }} Remember Me</p>
      <p><input type="submit" value="Sign In"></p>
  </form>
{% endblock %}

唯一的變化就是我們增加了一個循環獲取驗證 openid 字段的信息。通常情況下,任何需要驗證的字段都會把錯誤信息放入 form.field_name.errors 下。在我們的例子中,我們使用 form.openid.errors 。我們以紅色的字體顏色顯示這些錯誤信息以引起用戶的注意。

處理 OpenIDs

事實上,很多用戶並不知道他們已經有一些 OpenIDs。一些大的互聯網服務提供商支持 OpenID 認證自己的會員這並不是衆所周知的。比如,如果你有一個 Google 的賬號,你也就有了一個它們的 OpenID。

爲了讓用戶更方便地使用這些常用的 OpenID 登錄到我們的網站,我們把它們的鏈接轉成短名稱,用戶不必手動地輸入這些 OpenID。

我首先開始定義一個 OpenID 提供者的列表。我們可以把它們寫入我們的配置文件中(文件 config ):

CSRF_ENABLED = TrueSECRET_KEY = 'you-will-never-guess'OPENID_PROVIDERS = [
    { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' },
    { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' },
    { 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' },
    { 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' },
    { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]

現在讓我們看看如何在我們登錄視圖函數中使用它們:

@app.route('/login', methods = ['GET', 'POST'])def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
        return redirect('/index')
    return render_template('login.html',
        title = 'Sign In',
        form = form,
        providers = app.config['OPENID_PROVIDERS'])

我們從配置中獲取 OPENID_PROVIDERS,接着把它作爲 render_template 中一個參數傳入模板中。

我敢確信你們已經猜到了,我們還需要多做一步來達到目的。我們現在就來說明如何在登錄模板中渲染這些提供商的鏈接(文件 app/templates/login.html):

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
<script type="text/javascript">
function set_openid(openid, pr)
{
    u = openid.search('<username>')
    if (u != -1) {
        // openid requires username
        user = prompt('Enter your ' + pr + ' username:')
        openid = openid.substr(0, u) + user
    }
    form = document.forms['login'];
    form.elements['openid'].value = openid
}
</script>
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{ form.hidden_tag() }}
    <p>
        Please enter your OpenID, or select one of the providers below:<br>
        {{ form.openid(size=80) }}
        {% for error in form.openid.errors %}
          <span style="color: red;">[`error`]</span>
        {% endfor %}<br>
        |{% for pr in providers %}
          <a href="javascript:set_openid('{{ pr.url }}', '{{ pr.name }}');">{{ pr.name }}</a> |
        {% endfor %}
    </p>
    <p>{{ form.remember_me }} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

模板變得跟剛纔不一樣了。一些 OpenIDs 含有用戶名,因此對於這些用戶,我們必須利用 javascript 的魔力提示用戶輸入用戶名並且組成 OpenIDs。當用戶點擊一個 OpenIDs 提供商的鏈接並且(可選)輸入用戶名,該提供商相應的 OpenID 就被寫入到文本域中。

下面就是點擊 Google OpenID 鏈接後,我們登錄界面的一個截圖:

_images/1.jpg

結束語

儘管我們在登錄表單上已經取得了很多進展,我們實際上沒有做任何用戶登錄到我們的系統,到目前爲止我們所做的是登錄過程的 GUI 方面。這是因爲在做實際登錄之前,我們需要有一個數據庫,那裏可以記錄我們的用戶。

在下一章中,我們會得到我們的數據庫並且運行它,接着我們將完成我們的登錄系統。敬請關注後續文章。

如果你想要節省時間的話,你可以下載 microblog-0.3.zip

但是請注意的是 zip 文件已經不包含 flask 虛擬環境了,如果你想要運行應用程序的話,請按照第一章的步驟自己創建它。


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