flask入門的教程-Ajax

文章轉自 :https://github.com/WapeYang/The-Flask-Mega-Tutorial/blob/master/ajax.rst

感謝原作者的付出

轉載時間爲:2014-05-06

Ajax

這將是國際化和本地化的最後一篇文章,我們將會盡所能使得 microblog 應用程序對非英語用戶可用和更加友好。

不知道大家平時有沒有見過網站上有一個 “翻譯” 的鏈接,點擊後會把翻譯後的內容在旁邊顯示給用戶,這些鏈接觸發一個實時自動翻譯的內容。谷歌顯示這個 “翻譯” 鏈接是爲了能夠顯示外國語言的搜索結果。Facebook 顯示它爲了能夠翻譯 blog 內容。今天我們將要添加同樣的功能的 “翻譯” 鏈接到我們的 microblog

客戶端 VS 服務器端

在傳統的沿用至今的服務器端的模型中,有一個客戶端(用戶的瀏覽器)發送請求到我們的服務器上。一個請求能夠簡單地請求一個頁面,像當你點擊 “你的信息” 鏈接,或者它能夠讓我們執行一個動作,像當用戶編輯他的或者她的用戶信息並且點擊提交的按鈕。在這兩種類型的請求中服務器通過發送一個新的網頁到客戶端,直接或通過發出一個重定向的請求來完成這次請求。客戶端接着使用新頁代替目前的頁面。這個循環就會重複只要用戶停留在我們的網頁上。我們叫這種模式爲服務器端,因爲服務器做了所有的工作而客戶端只是在它們收到頁面的時候顯示出來。

在客戶端模式中,我們有一個網頁瀏覽器,再次發送請求到服務器上。服務器會像服務器端模式一樣迴應一個網頁,但是不是所有的頁面數據都是 HTML,同樣也有代碼,典型的就是用 Javascript 編寫的。一旦客戶端接收到頁面會把它顯示出來並且會執行攜帶的代碼。從此,你有一個活躍的客戶端,可以做自己的工作,沒有接觸外面的服務器。在嚴格的客戶端,整個應用程序被下載到客戶端在初始頁面請求中,然後應用程序運行在客戶端上不會刷新頁面,只有向服務器獲取或存儲數據。這種類型的應用稱爲 單頁應用 或者 SPAs。

大多數應用是這兩種模式的結合體。我們的 microblog 應用程序是一個完全的服務器端應用,但是現在我們想要添加些客戶端行爲。爲了實現實時翻譯用戶的 blog 內容,客戶端瀏覽器將會發送一個請求到服務器,但是服務器將會迴應一個翻譯文本而且不需要頁面刷新。客戶端將會動態地插入翻譯到當前頁面。這種技術叫做 Ajax,這是 Asynchronous Javascript and XML 的簡稱。

翻譯用戶生成內容

多虧了 Flask-Babel 我們現在比較好的支持了多語言。假設我們能找到願意幫助我們的翻譯器,我們可以在儘可能多的語言中發佈我們的應用程序。

但是還有一個遺漏問題。因爲有很多各種語言的用戶使用系統,那麼用戶發佈的 blog 內容的語言也是多種的。可能不是本語種的用戶不知道內容的含義,如果我們能夠提供一種自動翻譯的服務這種會不會更好?

這是一個用 Ajax 服務來實現的理想的功能。我們的首頁可以顯示很多的 blog,它們中的一些可能是不同語言的。如果我們使用傳統的服務器端模式來實現翻譯的話,一個翻譯的請求可能會讓原始頁面被新的只翻譯了選擇的 blog 的頁面替換。在用戶讀完翻譯後,我們必須點擊回退鍵去獲取 blog 列表。事實上請求一個翻譯並不是需要更新全部的頁面,這個功能讓應用程序更好,因爲翻譯的文本是動態地插入到原始的文本下面,其他的內容不會發生變化。因此,今天我們將會實現我們的 Ajax 服務。

實現實時翻譯需要幾個步驟。首先,我們需要確定要翻譯的文本的原語言類型。一旦我們知道原語言類型我們也就知道需不需要對一個給定的用戶翻譯,因爲我們也知道這個用戶選擇的語言類型。當翻譯被提供,用戶希望看到它的時候,將會調用 Ajax 服務。最後一步就是客戶端的 javascript 代碼將會動態地把翻譯文本插入到頁面中。

確定 blog 語言

我們首先的問題就是確定 blog 撰寫的語言。這不是一門精確的科學,它不會總是能夠檢測的語言的類型,所以我們只能盡最大努力去做。我們將會使用 guess-language Python 模塊。因此,請安裝這個模塊。針對 Linux 和 Mac OS X 用戶:

flask/bin/pip install guess-language

針對 Windows 用戶:

flask\Scripts\pip install guess-language

有了這個模塊,我們將會掃描每一篇 blog 的內容試着猜測它的語言種類。因爲我們不想一遍一遍地掃描同一篇 blog,我們將會針對每一篇 blog 只做一次,當用戶提交 blog 的時候就去掃描。我們將會把每一篇 blog 的語言種類存儲在數據庫中。

因此讓我們開始在我們的 Post 表中添加一個 language 字段:

class Post(db.Model):
    __searchable__ = ['body']

    id = db.Column(db.Integer, primary_key = True)
    body = db.Column(db.String(140))
    timestamp = db.Column(db.DateTime)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    language = db.Column(db.String(5))

每一次修改數據庫,我們都需要做一次遷移:

$ ./db_migrate.py
New migration saved as microblog/db_repository/versions/005_migration.py
Current database version: 5

現在我們已經在數據庫中有了存儲 blog 內容語言類型的地方,因此讓我們檢測每一個 blog 語言種類:

from guess_language import guessLanguage

@app.route('/', methods = ['GET', 'POST'])
@app.route('/index', methods = ['GET', 'POST'])
@app.route('/index/<int:page>', methods = ['GET', 'POST'])
@login_required
def index(page = 1):
    form = PostForm()
    if form.validate_on_submit():
        language = guessLanguage(form.post.data)
        if language == 'UNKNOWN' or len(language) > 5:
            language = ''
        post = Post(body = form.post.data,
            timestamp = datetime.utcnow(),
            author = g.user,
            language = language)
        db.session.add(post)
        db.session.commit()
        flash(gettext('Your post is now live!'))
        return redirect(url_for('index'))
    posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False)
    return render_template('index.html',
        title = 'Home',
        form = form,
        posts = posts)

如果語言猜測不能工作或者返回一個非預期的結果,我們會在數據庫中存儲一個空的字符串。

顯示 “翻譯” 鏈接

接下來一步就是在 blog 旁邊顯示 “翻譯” 鏈接(文件 app/templates/posts.html):

{% if post.language != None and post.language != '' and post.language != g.locale %}
<div><a href="#">{{ _('Translate') }}</a></div>
{% endif %}

這個鏈接需要我們添加一個新的翻譯文本, “翻譯”('Translate') 是需要被包含在翻譯文件裏面,這裏需要執行前面一章介紹的更新翻譯文本的流程。

我們現在還不清楚如何觸發這個翻譯,因此現在鏈接不會做任何事情。

翻譯服務

在我們的應用能夠使用實時翻譯之前,我們需要找到一個可用的服務。

現在有很多可用的翻譯服務,但是很多是需要收費的。

兩個主流的翻譯服務是 Google Translate 和 Microsoft Translator。兩者都是有償服務,但微軟提供的是免費的入門級的 API。在過去,谷歌提供了一個免費的翻譯服務,但已不存在。這使我們很容易選擇翻譯服務。

使用 Microsoft Translator 服務

爲了使用 Microsoft Translator,這裏有一些流程需要完成:

  • 應用的開發者需要在 Azure Marketplace 上註冊 Microsoft Translator app。這裏可以選擇服務級別(免費的選項在最下面)。
  • 接着開發者需要 註冊應用。註冊應用將會獲得客戶端 ID 以及客戶端密鑰代碼,這些用於發送請求的一部分。

一旦註冊部分完成,接下來處理請求翻譯的步驟如下:

這聽起來很複雜,因此如果不需要關注細節的話,這裏有一個做了很多“髒”工作並且把文本翻譯成別的語言的函數(文件app/translate.py):

import urllib, httplib
import json
from flask.ext.babel import gettext
from config import MS_TRANSLATOR_CLIENT_ID, MS_TRANSLATOR_CLIENT_SECRET

def microsoft_translate(text, sourceLang, destLang):
    if MS_TRANSLATOR_CLIENT_ID == "" or MS_TRANSLATOR_CLIENT_SECRET == "":
        return gettext('Error: translation service not configured.')
    try:
        # get access token
        params = urllib.urlencode({
            'client_id': MS_TRANSLATOR_CLIENT_ID,
            'client_secret': MS_TRANSLATOR_CLIENT_SECRET,
            'scope': 'http://api.microsofttranslator.com',
            'grant_type': 'client_credentials'
        })
        conn = httplib.HTTPSConnection("datamarket.accesscontrol.windows.net")
        conn.request("POST", "/v2/OAuth2-13", params)
        response = json.loads (conn.getresponse().read())
        token = response[u'access_token']

        # translate
        conn = httplib.HTTPConnection('api.microsofttranslator.com')
        params = {
            'appId': 'Bearer ' + token,
            'from': sourceLang,
            'to': destLang,
            'text': text.encode("utf-8")
        }
        conn.request("GET", '/V2/Ajax.svc/Translate?' + urllib.urlencode(params))
        response = json.loads("{\"response\":" + conn.getresponse().read().decode('utf-8-sig') + "}")
        return response["response"]
    except:
        return gettext('Error: Unexpected error.')

這個函數從我們的配置文件中導入了兩個新值,id 和密鑰代碼(文件 config.py):

# microsoft translation service
MS_TRANSLATOR_CLIENT_ID = '' # enter your MS translator app id here
MS_TRANSLATOR_CLIENT_SECRET = '' # enter your MS translator app secret here

上面的 ID 和密鑰代碼是需要自己去申請,步驟上面已經講了。即使你只希望測試應用程序,你也能免費地註冊這項服務。

因爲我們又添加了新的文本,這些也是需要翻譯的,請重新運行 tr_update.pypoedit 和 tr_compile.py 來更新翻譯的文件。

讓我們翻譯一些文本

因此我們該怎樣使用翻譯服務了?這實際上很簡單。這是例子:

$ flask/bin/python
Python 2.6.8 (unknown, Jun  9 2012, 11:30:32)
>>> from app import translate
>>> translate.microsoft_translate('Hi, how are you today?', 'en', 'es')
u'¿Hola, cómo estás hoy?'

服務器上的 Ajax

現在我們可以在多種語言之間翻譯文本,因此我們準備把這個功能整合到我們應用程序中。

當用戶點擊 blog 旁的 “翻譯” 鏈接的時候,會有一個 Ajax 調用發送到我們服務器上。我們將看看這個調用是如何生產的, 現在讓我們集中精力實現服務器端的 Ajax 調用。

服務器上的 Ajax 服務像一個常規的視圖函數,不同的是不返回一個 HTML 頁面或者重定向,它返回的是數據,典型的格式化成XML 或者 JSON。因爲 JSON 對 Javascript 比較友好,我們將使用這種格式(文件 app/views.py):

from flask import jsonify
from translate import microsoft_translate

@app.route('/translate', methods = ['POST'])
@login_required
def translate():
    return jsonify({
        'text': microsoft_translate(
            request.form['text'],
            request.form['sourceLang'],
            request.form['destLang']) })

這裏沒有多少新內容。這個路由處理一個攜帶要翻譯的文本以及原語言類型和要翻譯的語言類型的 POST 請求。因爲這是個 POST 請求,我們獲取的是輸入到 HTML 表單中的數據,請直接使用 request.form 字典。我們用這些數據調用我們的一個翻譯函數,一旦我們獲取翻譯的文本就用 Flask 的 jsonify 函數把它轉換成 JSON。客戶端看到的這個請求響應的數據類似這個格式:

{ "text": "<translated text goes here>" }

客戶端上的 Ajax

現在我們需要從網頁瀏覽器上調用 Ajax 視圖函數,因爲我們需要回到 post.html 子模板來完成我們最後的工作。

首先我們需要在模版中加入一個有唯一 id 的 span 元素,以便我們在 DOM 中可以找到它並且替換成翻譯的文本(文件app/templates/post.html):

<p><strong><span id="post{{post.id}}">{{post.body}}</span></strong></p>

同樣,我們需要給一個 “翻譯” 鏈接一個唯一的 id,以保證一旦翻譯顯示我們能隱藏這個鏈接:

<div><span id="translation{{post.id}}"><a href="#">{{ _('Translate') }}</a></span></div>

爲了做出一個漂亮的並且對用戶友好的功能,我們將會加入一個動態的圖片,開始的時候是隱藏的,僅僅出現當翻譯服務運行在服務器上,同樣也有唯一的 id:

<img id="loading{{post.id}}" style="display: none" src="/static/img/loading.gif">

目前我們有一個名爲 post<id> 的元素,它包含要翻譯的文本,還有一個名爲 translation<id> 的元素,它包含一個 “翻譯” 鏈接但是不久就會被翻譯後的文本代替,也有一個 id 爲 loading<id> 的圖片,它將會在翻譯服務工作的時候顯示。

現在我們需要在 “鏈接” 鏈接點擊的時候觸發 Ajax。與直接從鏈接上觸發調用相反,我們將會創建一個 Javascript 函數,這個函數做了所有工作,因爲我們有一些事情在那裏做並且不希望在每個模板中重複代碼。讓我們添加一個對這個函數的調用當 “翻譯” 鏈接被點擊的時候:

<a href="javascript:translate('{{post.language}}', '{{g.locale}}', '#post{{post.id}}', '#translation{{post.id}}', '#loading{{post.id}}');">{{ _('Translate') }}</a>

變量看起來有些多,但是函數調用很簡單。假設有一篇 id 爲 23,使用西班牙語寫的 blog,用戶想要翻譯成英語。這個函數的調用如下:

translate('es', 'en', '#post23', '#translation23', '#loading23')

最後我們需要實現的 translate(),我們將不會在 post.html 子模板中編寫這個函數,因爲每一篇 blog 內容會有些重複。我們將會在基礎模版中實現這個函數,下面就是這個函數(文件 app/templates/base.html):

<script>
function translate(sourceLang, destLang, sourceId, destId, loadingId) {
    $(destId).hide();
    $(loadingId).show();
    $.post('/translate', {
        text: $(sourceId).text(),
        sourceLang: sourceLang,
        destLang: destLang
    }).done(function(translated) {
        $(destId).text(translated['text'])
        $(loadingId).hide();
        $(destId).show();
    }).fail(function() {
        $(destId).text("{{ _('Error: Could not contact server.') }}");
        $(loadingId).hide();
        $(destId).show();
    });
}
</script>

這段代碼依賴於 jQuery,需要詳細瞭解上述幾個函數的話,請查看 jQuery

結束語

近來當使用 Flask-WhooshAlchemy 爲全文搜索的時候,會有一些數據庫的警告。在下一章的時候,我們針對這個問題來講講 Flask 應用程序的調試技術。

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

我希望能在下一章繼續見到各位!


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