(三)Flask 學習 —— web 表單

web 表單

回顧

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

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

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

咱們接下來說述的正是咱們上一章離開的地方,因此你可能要確保應用程序 microblog 正確地安裝和工做。python

配置

爲了可以處理 web 表單,咱們將使用 Flask-WTF ,該擴展封裝了 WTForms 而且恰當地集成進 Flask 中。git

許多 Flask 擴展須要大量的配置,所以咱們將要在 microblog 文件夾的根目錄下建立一個配置文件以致於容易被編輯。這就是咱們將要開始的(文件 config.py):github

CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

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

SECRET_KEY 配置僅僅當 CSRF 激活的時候才須要,它是用來創建一個加密的令牌,用於驗證一個表單。當你編寫本身的應用程序的時候,請務必設置很難被猜想到密鑰。數據庫

既然咱們有了配置文件,咱們須要告訴 Flask 去讀取以及使用它。咱們能夠在 Flask 應用程序對象被建立後去作,方式以下(文件 app/__init__.py):flask

from flask import Flask

app = 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 Form
from wtforms import StringField, BooleanField
from wtforms.validators import DataRequired

class 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, redirect
from app import app
from .forms import LoginForm

# index view function suppressed for brevity

@app.route('/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 虛擬環境了,若是你想要運行應用程序的話,請按照第一章的步驟本身建立它。

相關文章
相關標籤/搜索