web框架flask(13)——AJAX

Ajax

这将是国际化和本地化的最后一篇文章,咱们将会尽所能使得 microblog 应用程序对非英语用户可用和更加友好。javascript

不知道你们平时有没有见过网站上有一个 “翻译” 的连接,点击后会把翻译后的内容在旁边显示给用户,这些连接触发一个实时自动翻译的内容。谷歌显示这个 “翻译” 连接是为了可以显示外国语言的搜索结果。Facebook 显示它为了可以翻译 blog 内容。今天咱们将要添加一样的功能的 “翻译” 连接到咱们的 microbloghtml

客户端 VS 服务器端

在传统的沿用至今的服务器端的模型中,有一个客户端(用户的浏览器)发送请求到咱们的服务器上。一个请求可以简单地请求一个页面,像当你点击 “你的信息” 连接,或者它可以让咱们执行一个动做,像当用户编辑他的或者她的用户信息而且点击提交的按钮。在这两种类型的请求中服务器经过发送一个新的网页到客户端,直接或经过发出一个重定向的请求来完成此次请求。客户端接着使用新页代替目前的页面。这个循环就会重复只要用户停留在咱们的网页上。咱们叫这种模式为服务器端,由于服务器作了全部的工做而客户端只是在它们收到页面的时候显示出来。java

在客户端模式中,咱们有一个网页浏览器,再次发送请求到服务器上。服务器会像服务器端模式同样回应一个网页,可是不是全部的页面数据都是 HTML,一样也有代码,典型的就是用 Javascript 编写的。一旦客户端接收到页面会把它显示出来而且会执行携带的代码。今后,你有一个活跃的客户端,能够作本身的工做,没有接触外面的服务器。在严格的客户端,整个应用程序被下载到客户端在初始页面请求中,而后应用程序运行在客户端上不会刷新页面,只有向服务器获取或存储数据。这种类型的应用称为 单页应用 或者 SPAs。python

大多数应用是这两种模式的结合体。咱们的 microblog 应用程序是一个彻底的服务器端应用,可是如今咱们想要添加些客户端行为。为了实现实时翻译用户的 blog 内容,客户端浏览器将会发送一个请求到服务器,可是服务器将会回应一个翻译文本并且不须要页面刷新。客户端将会动态地插入翻译到当前页面。这种技术叫作 Ajax,这是 Asynchronous Javascript and XML 的简称。jquery

翻译用户生成内容

多亏了 Flask-Babel 咱们如今比较好的支持了多语言。假设咱们能找到愿意帮助咱们的翻译器,咱们能够在尽量多的语言中发布咱们的应用程序。数据库

可是还有一个遗漏问题。由于有不少各类语言的用户使用系统,那么用户发布的 blog 内容的语言也是多种的。可能不是本语种的用户不知道内容的含义,若是咱们可以提供一种自动翻译的服务这种会不会更好?json

这是一个用 Ajax 服务来实现的理想的功能。咱们的首页能够显示不少的 blog,它们中的一些多是不一样语言的。若是咱们使用传统的服务器端模式来实现翻译的话,一个翻译的请求可能会让原始页面被新的只翻译了选择的 blog 的页面替换。在用户读完翻译后,咱们必须点击回退键去获取 blog 列表。事实上请求一个翻译并非须要更新所有的页面,这个功能让应用程序更好,由于翻译的文本是动态地插入到原始的文本下面,其余的内容不会发生变化。所以,今天咱们将会实现咱们的 Ajax 服务。flask

实现实时翻译须要几个步骤。首先,咱们须要肯定要翻译的文本的原语言类型。一旦咱们知道原语言类型咱们也就知道需不须要对一个给定的用户翻译,由于咱们也知道这个用户选择的语言类型。当翻译被提供,用户但愿看到它的时候,将会调用 Ajax 服务。最后一步就是客户端的 javascript 代码将会动态地把翻译文本插入到页面中。windows

肯定 blog 语言

咱们首先的问题就是肯定 blog 撰写的语言。这不是一门精确的科学,它不会老是可以检测的语言的类型,因此咱们只能尽最大努力去作。咱们将会使用 guess-language Python 模块。所以,请安装这个模块。针对 Linux 和 Mac OS X 用户:api

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 TranslateMicrosoft Translator。二者都是有偿服务,但微软提供的是免费的入门级的 API。在过去,谷歌提供了一个免费的翻译服务,但已不存在。这使咱们很容易选择翻译服务。

使用 Microsoft Translator 服务

为了使用 Microsoft Translator,这里有一些流程须要完成:

  • 应用的开发者须要在 Azure Marketplace 上注册 Microsoft Translator app。这里能够选择服务级别(免费的选项在最下面)。
  • 接着开发者须要 注册应用。注册应用将会得到客户端 ID 以及客户端密钥代码,这些用于发送请求的一部分。

一旦注册部分完成,接下来处理请求翻译的步骤以下:

这听起来很复杂,所以若是不须要关注细节的话,这里有一个作了不少“脏”工做而且把文本翻译成别的语言的函数(文件 app/translate.py):

try:
    import httplib  # Python 2
except ImportError:
    import http.client as httplib  # Python 3
try:
    from urllib import urlencode  # Python 2
except ImportError:
    from urllib.parse import urlencode  # Python 3
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 = 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?' + urlencode(params))
        response = json.loads("{\"response\":" + conn.getresponse().read().decode('utf-8') + "}")
        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.pypoedittr_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 应用程序的调试技术。

相关文章
相关标签/搜索