第二章:表單和模板

第一章中,咱們學習了使用Tornado建立一個Web應用的基礎知識。包括處理函數、HTTP方法以及Tornado框架的整體結構。在這章中,咱們將學習一些你在建立Web應用時常常會用到的更強大的功能。css

和大多數Web框架同樣,Tornado的一個重要目標就是幫助你更快地編寫程序,儘量整潔地複用更多的代碼。儘管Tornado足夠靈活,可使用幾乎全部Python支持的模板語言,Tornado自身也提供了一個輕量級、快速而且靈活的模板語言在tornado.template模塊中。html

2.1 簡單示例:Poem Maker Pro

讓咱們以一個叫做Poem Maker Pro的簡單例子開始。Poem Maker Pro這個Web應用有一個讓用戶填寫的HTML表單,而後處理表單的結果。代碼清單2-1是它的Python代碼。python

代碼清單2-1 簡單表單和模板:poemmaker.py
import os.path

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')

class PoemPageHandler(tornado.web.RequestHandler):
    def post(self):
        noun1 = self.get_argument('noun1')
        noun2 = self.get_argument('noun2')
        verb = self.get_argument('verb')
        noun3 = self.get_argument('noun3')
        self.render('poem.html', roads=noun1, wood=noun2, made=verb,
                difference=noun3)

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app = tornado.web.Application(
        handlers=[(r'/', IndexHandler), (r'/poem', PoemPageHandler)],
        template_path=os.path.join(os.path.dirname(__file__), "templates")
    )
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

除了poemmaker.py,你還須要將代碼清單2-2和代碼清單2-3中的兩個文件加入到templates子文件夾中。web

代碼清單2-2 Poem Maker表單:index.html
<!DOCTYPE html>
<html>
    <head><title>Poem Maker Pro</title></head>
    <body>
        <h1>Enter terms below.</h1>
        <form method="post" action="/poem">
        <p>Plural noun<br><input type="text" name="noun1"></p>
        <p>Singular noun<br><input type="text" name="noun2"></p>
        <p>Verb (past tense)<br><input type="text" name="verb"></p>
        <p>Noun<br><input type="text" name="noun3"></p>
        <input type="submit">
        </form>
    </body>
</html>

 

代碼清單2-3 Poem Maker模板:poem.html
<!DOCTYPE html>
<html>
    <head><title>Poem Maker Pro</title></head>
    <body>
        <h1>Your poem</h1>
        <p>Two {{roads}} diverged in a {{wood}}, and I—<br>
I took the one less travelled by,<br>
And that has {{made}} all the {{difference}}.</p>
    </body>
</html>

在命令行執行下述命令:shell

$ python poemmaker.py --port=8000

如今,在瀏覽器中打開http://localhost:8000。當瀏覽器請求根目錄(/)時,Tornado程序將渲染index.html,展現如圖2-1所示的簡單HTML表單。json

圖2-1

圖2-1 Poem Maker Pro:輸入表單api

這個表單包括多個文本域(命名爲noun1noun2等),其中的內容將在用戶點擊"Submit"按鈕時以POST請求的方式送到/poem。如今往裏面填寫東西而後點擊提交吧。瀏覽器

爲了響應這個POST請求,Tornado應用跳轉到poem.html,插入你在表單中填寫的值。結果是Robert Frost的詩《The Road Not Taken》的輕微修改版本。圖2-2展現了這個結果。緩存

圖2-2

圖2-2 Poem Maker Pro:輸出服務器

2.1.1 渲染模板

從結構上講,poemmaker.pyhttp://www.javashuo.com/tag/第一章中的例子很類似。咱們定義了幾個RequestHandler子類並把它們傳給tornado.web.Application對象。那麼有什麼不同的地方呢?首先,咱們向Application對象的__init__方法傳遞了一個template_path參數。

template_path=os.path.join(os.path.dirname(__file__), "templates")

template_path參數告訴Tornado在哪裏尋找模板文件。咱們將在本章和第三章中講解其確切性質和語法,而它的基本要點是:模板是一個容許你嵌入Python代碼片斷的HTML文件。上面的代碼告訴Python在你Tornado應用文件同目錄下的templates文件夾中尋找模板文件。

一旦咱們告訴Tornado在哪裏找到模板,咱們可使用RequestHandler類的render方法來告訴Tornado讀入模板文件,插入其中的模版代碼,並返回結果給瀏覽器。好比,在IndexHandler中,咱們發現了下面的語句:

self.render('index.html')

這段代碼告訴Tornado在templates文件夾下找到一個名爲index.html的文件,讀取其中的內容,而且發送給瀏覽器。

2.1.2 填充

實際上index.html徹底不能稱之爲"模板",它所包含的徹底是已編寫好的HTML標記。這能夠是模板的一個不錯的使用方式,但在更一般的狀況下咱們但願HTML輸出能夠結合咱們的程序傳入給模板的值。模板poem.html使用PoemPageHandler渲染,是這種方式的一個很好的例子。讓咱們看看它是如何工做的吧。

poem.html中,你能夠看到模板中有一些被雙大括號({{和}})括起來的字符串,就像這樣:

<p>Two {{roads}} diverged in a {{wood}}, and I—<br/>
I took the one less travelled by,<br>
And that has {{made}} all the {{difference}}.</p>

在雙大括號中的單詞是佔位符,當咱們渲染模板時但願以實際值代替。咱們可使用向render函數中傳遞關鍵字參數的方法指定什麼值將被填充到HTML文件中的對應位置,其中關鍵字對應模板文件中佔位符的名字。下面是在PoemPageHandler中相應的代碼部分:

noun1 = self.get_argument('noun1')
noun2 = self.get_argument('noun2')
verb = self.get_argument('verb')
noun3 = self.get_argument('noun3')
self.render('poem.html', roads=noun1, wood=noun2, made=verb, difference=noun3)

在這裏,咱們告訴模板使用變量noun1(該變量是從get_argument方法取得的)做爲模板中roads的值,noun2做爲模板中wood的值,依此類推。假設用戶在表單中按順序鍵入了pineapplesgrandfather clockirradiatedsupernovae,那麼結果HTML將會以下所示:

<p>Two pineapples diverged in a grandfather clock, and I—<br>
I took the one less travelled by,<br>
And that has irradiated all the supernovae.</p>

2.2 模板語法

既然咱們已經看到了一個模板在實際應用中的簡單例子,那麼讓咱們深刻地瞭解它們是如何工做的吧。Tornado模板是被Python表達式和控制語句標記的簡單文本文件。Tornado的語法很是簡單直接。熟悉Django、Liquid或其餘類似框架的用戶會發現它們很是類似,很容易學會。

在2.1節中,咱們展現瞭如何在一個Web應用中使用render方法傳送HTML給瀏覽器。你能夠在Tornado應用以外使用Python解釋器導入模板模塊嘗試模板系統,此時結果會被直接輸出出來。

>>> from tornado.template import Template
>>> content = Template("<html><body><h1>{{ header }}</h1></body></html>")
>>> print content.generate(header="Welcome!")
<html><body><h1>Welcome!</h1></body></html>

2.2.1 填充表達式

在代碼清單2-1中,咱們演示了填充Python變量的值到模板的雙大括號中的使用。實際上,你能夠將任何Python表達式放在雙大括號中。Tornado將插入一個包含任何表達式計算結果值的字符串到輸出中。下面是幾個可能的例子:

>>> from tornado.template import Template
>>> print Template("{{ 1+1 }}").generate()
2
>>> print Template("{{ 'scrambled eggs'[-4:] }}").generate()
eggs
>>> print Template("{{ ', '.join([str(x*x) for x in range(10)]) }}").generate()
0, 1, 4, 9, 16, 25, 36, 49, 64, 81

2.2.2 控制流語句

你一樣能夠在Tornado模板中使用Python條件和循環語句。控制語句以{%和%}包圍,並以相似下面的形式被使用:

{% if page is None %}

{% if len(entries) == 3 %}

控制語句的大部分就像對應的Python語句同樣工做,支持ifforwhiletry。在這些狀況下,語句塊以{%開始,並以%}結束。

因此這個模板:

<html>
    <head>
        <title>{{ title }}</title>
    </head>
    <body>
        <h1>{{ header }}</h1>
        <ul>
            {% for book in books %}
                <li>{{ book }}</li>
            {% end %}
        </ul>
    </body>
</html>

當被下面這個處理函數調用時:

class BookHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(
            "book.html",
            title="Home Page",
            header="Books that are great",
            books=[
                "Learning Python",
                "Programming Collective Intelligence",
                "Restful Web Services"
            ]
        )

將會渲染獲得下面的輸出:

<html>
    <head>
        <title>Home Page</title>
    </head>
    <body>
        <h1>Books that are great</h1>
        <ul>
            <li>Learning Python</li>
            <li>Programming Collective Intelligence</li>
            <li>Restful Web Services</li>
        </ul>
    </body>
</html>

不像許多其餘的Python模板系統,Tornado模板語言的一個最好的東西是在iffor語句塊中可使用的表達式沒有限制。所以,你能夠在你的模板中執行全部的Python代碼。

一樣,你也能夠在你的控制語句塊中間使用{% set foo = 'bar' %}來設置變量。你還有不少能夠在控制語句塊中作的事情,可是在大多數狀況下,你最好使用UI模塊來作更復雜的劃分。咱們稍後會更詳細的看到這一點。

2.2.3 在模板中使用函數

Tornado在全部模板中默認提供了一些便利的函數。它們包括:

escape(s)

替換字符串s中的&、<、>爲他們對應的HTML字符。

url_escape(s)

使用urllib.quote_plus替換字符串s中的字符爲URL編碼形式。

json_encode(val)

將val編碼成JSON格式。(在系統底層,這是一個對json庫的dumps函數的調用。查閱相關的文檔以得到更多關於該函數接收和返回參數的信息。)

squeeze(s)

過濾字符串s,把連續的多個空白字符替換成一個空格。

在Tornado 1.x中,模版不是被自動轉義的。在Tornado 2.0中,模板被默認爲自動轉義(而且能夠在Application構造函數中使用autoscaping=None關閉)。在不一樣版本的遷移時要注意向後兼容。

在模板中使用一個你本身編寫的函數也是很簡單的:只須要將函數名做爲模板的參數傳遞便可,就像其餘變量同樣。

>>> from tornado.template import Template
>>> def disemvowel(s):
...     return ''.join([x for x in s if x not in 'aeiou'])
...
>>> disemvowel("george")
'grg'
>>> print Template("my name is {{d('mortimer')}}").generate(d=disemvowel)
my name is mrtmr

2.3 複雜示例:The Alpha Munger

在代碼清單2-4中,咱們把在這一章中談論過的全部東西都放了進來。這個應用被稱爲The Alpha Munger。用戶輸入兩個文本:一個"源"文本和一個"替代"文本。應用會返回替代文本的一個副本,並將其中每一個單詞替換成源文本中首字母相同的某個單詞。圖2-3展現了要填的表單,圖2-4展現告終果文本。

這個應用包括四個文件:main.py(Tornado程序)、style.css(CSS樣式表文件)、index.html和munged.html(Tornado模板)。讓咱們看看代碼吧:

代碼清單2-4 複雜表單和模板:main.py
import os.path
import random

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')

class MungedPageHandler(tornado.web.RequestHandler):
    def map_by_first_letter(self, text):
        mapped = dict()
        for line in text.split('\r\n'):
            for word in [x for x in line.split(' ') if len(x) > 0]:
                if word[0] not in mapped: mapped[word[0]] = []
                mapped[word[0]].append(word)
        return mapped

    def post(self):
        source_text = self.get_argument('source')
        text_to_change = self.get_argument('change')
        source_map = self.map_by_first_letter(source_text)
        change_lines = text_to_change.split('\r\n')
        self.render('munged.html', source_map=source_map, change_lines=change_lines,
                choice=random.choice)

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app = tornado.web.Application(
        handlers=[(r'/', IndexHandler), (r'/poem', MungedPageHandler)],
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        static_path=os.path.join(os.path.dirname(__file__), "static"),
        debug=True
    )
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()
圖2-3

圖2-3 Alpha Munger:輸入表單

圖2-4

圖2-4 Alpha Munger:輸出

記住Application構造函數中的static_path參數。咱們將在下面進行詳細的介紹,可是如今你所須要知道的就是static_path參數指定了你應用程序放置靜態資源(如圖像、CSS文件、JavaScript文件等)的目錄。另外,你還須要在templates文件夾下添加index.html和munged.html這兩個文件。

代碼清單2-5 Alpha Munger表單:index.html
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="{{ static_url("style.css") }}">
        <title>The Alpha Munger</title>
    </head>
    <body>
        <h1>The Alpha Munger</h1>
        <p>Enter two texts below. The replacement text will have its words
            replaced by words beginning with the same letter in the source text.</p>
        <form method="post" action="/poem">
        <p>Source text<br>
            <textarea rows=4 cols=55 name="source"></textarea></p>
        <p>Text for replacement<br>
            <textarea rows=4 cols=55 name="change"></textarea></p>
        <input type="submit">
        </form>
    </body>
</html>

 

代碼清單2-6 Alpha Munger模板:munged.html
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="{{ static_url("style.css") }}">
        <title>The Alpha Munger</title>
    </head>
    <body>
        <h1>Your text</h1>
        <p>
{% for line in change_lines %}
    {% for word in line.split(' ') %}
        {% if len(word) > 0 and word[0] in source_map %}
            <span class="replaced"
                    title="{{word}}">{{ choice(source_map[word[0]]) }}</span>
        {% else %}
            <span class="unchanged" title="unchanged">{{word}}</span>
        {% end %}
    {% end %}
            <br>
{% end %}
        </p>
    </body>
</html>

最後,將代碼清單2-7中的內容寫到static子目錄下的style.css文件中。

代碼清單2-7 Alpha Munger樣式表:style.css
body {
    font-family: Helvetica,Arial,sans-serif;
    width: 600px;
    margin: 0 auto;
}
.replaced:hover { color: #00f; }

2.3.1 它如何工做

這個Tornado應用定義了兩個請求處理類:IndexHandlerMungedPageHandlerIndexHandler類簡單地渲染了index.html中的模板,其中包括一個容許用戶POST一個源文本(在source域中)和一個替換文本(在change域中)到/poem的表單。

MungedPageHandler類用於處理到/poemPOST請求。當一個請求到達時,它對傳入的數據進行一些基本的處理,而後爲瀏覽器渲染模板。map_by_first_letter方法將傳入的文本(從source域)分割成單詞,而後建立一個字典,其中每一個字母表中的字母對應文本中全部以其開頭的單詞(咱們將其放入一個叫做source_map的變量)。再把這個字典和用戶在替代文本(表單的change域)中指定的內容一塊兒傳給模板文件munged.html。此外,咱們還將Python標準庫的random.choice函數傳入模板,這個函數以一個列表做爲輸入,返回列表中的任一元素。

munged.html中,咱們迭代替代文本中的每行,再迭代每行中的每一個單詞。若是當前單詞的第一個字母是source_map字典的一個鍵,咱們使用random.choice函數從字典的值中隨機選擇一個單詞並展現它。若是字典的鍵中沒有這個字母,咱們展現源文本中的原始單詞。每一個單詞包括一個span標籤,其中的class屬性指定這個單詞是替換後的(class="replaced")仍是原始的(class="unchanged")。(咱們還將原始單詞放到了span標籤的title屬性中,以便於用戶在鼠標通過單詞時能夠查看是什麼單詞被替代了。你能夠在圖2-5中看到這個動做。)

圖2-5

圖2-5 含有被替換單詞提示的Alpha Munger

在這個例子中,你可能注意到了debug=True的使用。它調用了一個便利的測試模式:tornado.autoreload模塊,此時,一旦主要的Python文件被修改,Tornado將會嘗試重啓服務器,而且在模板改變時會進行刷新。對於快速改變和實時更新這很是棒,但不要再生產上使用它,由於它將防止Tornado緩存模板!

2.3.2 提供靜態文件

當編寫Web應用時,你總但願提供像樣式表、JavaScript文件和圖像這樣不須要爲每一個文件編寫獨立處理函數的"靜態內容"。Tornado提供了幾個有用的捷徑來使其變得容易。

2.3.2.1 設置靜態路徑

你能夠經過向Application類的構造函數傳遞一個名爲static_path的參數來告訴Tornado從文件系統的一個特定位置提供靜態文件。Alpha Munger中的相關代碼片斷以下:

app = tornado.web.Application(
    handlers=[(r'/', IndexHandler), (r'/poem', MungedPageHandler)],
    template_path=os.path.join(os.path.dirname(__file__), "templates"),
    static_path=os.path.join(os.path.dirname(__file__), "static"),
    debug=True
)

在這裏,咱們設置了一個當前應用目錄下名爲static的子目錄做爲static_path的參數。如今應用將以讀取static目錄下的filename.ext來響應諸如/static/filename.ext的請求,並在響應的主體中返回。

2.3.2.2 使用static_url生成靜態URL

Tornado模板模塊提供了一個叫做static_url的函數來生成static目錄下文件的URL。讓咱們來看看在index.html中static_url的調用的示例代碼:

<link rel="stylesheet" href="{{ static_url("style.css") }}">

這個對static_url的調用生成了URL的值,並渲染輸出相似下面的代碼:

<link rel="stylesheet" href="/static/style.css?v=ab12">

那麼爲何使用static_url而不是在你的模板中硬編碼呢?有以下幾個緣由。其一,static_url函數建立了一個基於文件內容的hash值,並將其添加到URL末尾(查詢字符串的參數v)。這個hash值確保瀏覽器老是加載一個文件的最新版而不是以前的緩存版本。不管是在你應用的開發階段,仍是在部署到生產環境使用時,都很是有用,由於你的用戶沒必要再爲了看到你的靜態內容而清除瀏覽器緩存了。

另外一個好處是你能夠改變你應用URL的結構,而不須要改變模板中的代碼。例如,你能夠配置Tornado響應來自像路徑/s/filename.ext的請求時提供靜態內容,而不是默認的/static路徑。若是你使用static_url而不是硬編碼的話,你的代碼不須要改變。好比說,你想把靜態資源從咱們剛纔使用的/static目錄移到新的/s目錄。你能夠簡單地改變靜態路徑由static變爲s,而後每一個使用static_url包裹的引用都會被自動更新。若是你在每一個引用靜態資源的文件中硬編碼靜態路徑部分,你將不得不手動修改每一個模板。

相關文章
相關標籤/搜索