在第二章中,咱們看到了如何使用tornado的template輕鬆地將數據從handler傳送給web頁面。讓咱們在保持簡潔的web tag結構的同時,輕鬆地向web頁面插入動態數據,然而大部分網站都但願使用一個高可用的響應模型,將內容按照頁眉、頁腳和佈局框架的形式進行管理。在這一章咱們將讓你瞭解到如何經過tornado的template或UI模塊完成這種擴展。 javascript
塊和替換 css
當你花費了大量的時間爲你的web應用建立和製做模板時,你有沒有發現,它們彷佛只是按照邏輯的形式進行佈局,你但願你的前端代碼和後端代碼同樣可以儘量多的重用對嗎?tornado提供了豐富的模板集成和擴展的塊語句幫助你完成這一點,tornado能夠按照你想要的方式靈活的控制和改變你現有的模板,提升它們的重用性。若是想要擴展示有的模板,你只須要將{% extends 」filename.html「%}放到你的模板中。例如使用你的父模板(main.html)去擴展一個新的模板,你只須要這麼作: html
它將會在新的web頁面中繼承並使用main.html,而後將main.html的內容插入到你但願顯示的地方。有了這個系統,你能夠建立一個主模板,嵌入到其餘有特殊需求的子模版中,在子模塊中你可使用動態內容或效果快速地擴展你的應用。 前端
基本的塊 java
除非你能夠快速的使用和改變tornado中的模板,不然不建議你去改動擴展模板,你可使用塊語句去完成你的改動。 python
一個塊語句能夠將一些元素封裝到模板中,假如你想要改變它。例如爲了實現一個動態的標題,你但願這個效果能覆蓋每個頁面,那麼你能夠把這個效果寫到你的父模板main.html中。 jquery
而後,去重寫子模塊中的{% block header%}{% end %}切分的內容。你能夠參考下面的方式使用任何內容填充: web
全部繼承的模板均可以將{% block header %} 和 {% end %}的標籤插入到任何地方。你能夠經過一個簡單的python腳本在web應用中調用一個已命名的子模板,就像這樣: ajax
例如在這裏,index.html會在頁面顯示一個main.html的內容:「hello world」,你能夠在圖片3-1中查看到效果。 mongodb
如今咱們能夠看到,這是一個很是有用的功能,讓咱們能夠更快地去管理多個頁面的總體頁面結構,你還可使用不一樣的block在同一個頁面中,動態的元素如頁眉、頁腳均可以放在同一個頁面中。
下面是一個例子,咱們添加了多個blocks到咱們父模板中:
圖3-1 hello world
咱們能夠參考這個子模板index.html的形式去擴展咱們的父模板main.html。
在python腳本和以前看上去的同樣,只不過咱們如今經過添加一些變量將數據插入到template中,請查看效果圖3-2:
圖3-2
你也能夠在父模板中放入一些默認的文本到塊語句標識符內。假如擴展的模板沒有指定本身的替換塊,將會顯示默認的文本,這種方式讓你能夠根據須要靈活地修改其中一些頁面的塊語句,特別適合導入或替換:JavaScript、CSS文件和標識的塊。
圖3-3
一個模板的文檔標識應該可以幫助咱們顯示一些錯誤的語法或異常的關閉:「錯誤報告位於……」。有一些{% block %}聲明的異常關閉或語法錯誤將會致使返回一個500:內部服務器錯誤的提示(若是你是在debug模式下運行,會顯示一個完整的python堆棧跟蹤表)到瀏覽器上。(請查看圖3-3的內容)
總而言之,你本身須要保證模板的健壯性,並儘量地在錯誤出現前找到它。
一個模板的練習:Burt’s Books
你是否定爲這些聽起來很是有趣,可是又想象不出來如何在一個web應用中使用這些特性呢?讓咱們來看一個例子吧:咱們的好朋友Burt經營了一家名爲Burt’s Book的書店
Burt經過商店購買了不少書,他如今須要一個網站爲來訪者展現不一樣的書籍介紹及更多的東西,Burt但願有一個頁面佈局上保持一致,可是又能夠很是方便地更新頁面和內容的網站。
爲此,Burt‘s Book使用了Tornado來搭建這個網站,使用一個主模板來定義全部的樣式、佈局、標題、頁眉、頁腳等細節,而後用一個很是輕量的子模板處理頁面信息,有了這樣一個應用系統,Burt就能夠在一個頁面完成發佈圖書信息、員工建議、事件安排等更多共享信息的操做。Burt’s Book的網站基於一個主模板main.html來完成網站的總體架構,它看起來是這樣的:
這個頁面定義了整個結構,應用了一個CSS樣式表,而且加載了主要的JavaScript文件。其它模板能夠對這個主模板進行擴展,替換掉其中的頁面、頁腳、內容。
通過擴展main.html後,咱們只須要替換掉頁眉、和內容的默認文本就能夠實現一個index.html頁面,這個網站的首頁index.html向web訪問的人提供了一些關於商店的信息。
在這裏咱們全部的子模板使用tornado模板繼承main.html默認的頁腳,將全部的信息傳送給咱們的index.html模板以後,這個Burt’s Book 站點的python腳本 main.py就能夠運行了。
這個例子的結構和咱們以前見到的彷佛有些不一樣,可是這沒什麼好怕的,咱們不是經過調用tornado.web.application構造函數列表的實例的形式來實現,而是經過和其它參數來定義咱們本身的應用類,咱們經過一個很簡單的方式去初始化和調用咱們本身實現的方法,咱們建立了一個handlers的列表和一個字典去傳遞對應的數值,而且使用這些值去調用並初始化咱們的父類。像這樣
tornado.web.Application.__init__(self, handlers, **settings)
當這個應用系統完成以後,Burt’s Book就能夠很輕鬆地改變索引頁面,而且保證main.html模板能夠正常地被其它子頁面調用。此外他們還能夠發揮tornado框架的優點,經過python腳本讓網站使用動態的內容或者數據庫,咱們將在後續的部分看到更多細節的實現。
轉義
在默認狀況下,tornado將會對HTML模板開啓自動轉義,將其轉換爲關聯的HTML實體,這有助於防止惡意腳本對網站數據庫的攻擊,假如你的網站有一個討論的功能,用戶能夠添加任何他們喜歡的文章並對此進行討論。雖然大部分的HTML tag並不可以給網站帶來危險,可是一些未轉義的<script>標記可讓攻擊者加載外部的JavaScript文件,開啓一些後門、跨站腳本、XSS漏洞等等。
讓咱們來思考一下這個例子,Burt’s Book有一個用戶反饋的頁面, Melvin今天在評論表單中提交了一個惡意攻擊的文本:
如今當Alice登陸這個網站的時候,他的網頁將會顯示圖3-4的內容。
圖3-4
在tornado1.x中,template沒有提供自動轉移的功能,因此咱們須要討論一下,如何經過調用escape()方法清除用戶的危險輸入。
在這裏咱們能夠看到怎麼經過轉義去保護你的用戶不被惡意代碼攻擊。可是它也一樣會攔截你的一些動態的HTML模板和模塊。
例如,Burt想要經過模板的變量添加一個郵箱的鏈接信息在頁腳,Burt添加的鏈接將會被攔截。讓咱們看看Burt的代碼:
這段代碼在頁面中將會被調整成下面的內容:
圖3-5
這就是轉義autoescaping()致使的,很明顯,用戶將沒辦法聯繫到Burt。
爲了處理這樣的狀況,你能夠經過設置autoescape = None禁用autoescaping功能,或者像下面這樣在每一頁修改autoescape的功能。
這些autoescape 不須要使用{end} tag標記結束,固然咱們也能夠經過設置xhtml_escape去啓用autoescaping(這是默認開啓的),或者將其關閉。
實際上,你不管如何都要一直啓用autoescaping對網站進行保護,固然也能夠在標籤中使用{%raw %}去禁用自動轉義對模板的渲染。
這裏有一個特別重要的事情,當你使用tornado的linkify()和xsrf_form_html()功能時,會影響autoescaping的設置。例如:你想要使用linkify()在頁腳中插入一個連接(autoescaping已經啓用了),你可使用{%raw%}來實現關閉autoescaping功能:
這樣你就能夠快速的在這裏使用linkify()功能,可是在其餘地方autoescaping仍然起做用
UI模塊
正如咱們看到的,template系統很是輕量級但功能又很是強大,在實際使用中,咱們還須要聽從一些軟件工程的原則:DRY原則(Don’t Repeat Yourself)。不要在項目中出現重複的代碼,咱們能夠經過template 模塊儘量地去掉冗餘代碼。例如,咱們能夠爲顯示物品清單定義一個列表模板,在每個須要使用的地方經過render去調用它,此外還能夠定義一個導航模板放到共享模板中。tornado的UI模塊能夠有效的解決這些問題。
UI模塊將不少可重用的組件:設計風格、樣式、特效放到了一個模板中。這些頁面元素一般都被多個模板重用或在一個模板中反覆使用。module模塊自己就是一個簡單的python類,它繼承了tornado的UImodule類中定義的render方法。當一個模模板經過{%module Foo(…)%} tag 去引用另外一個模板時 ,tornado的template 引擎將會去調用module類中的render方法進行渲染,而後返回一個替換模板的字符串給template。UI模塊能夠嵌入本身的JavaScript和CSS到渲染的頁面中,固然你也能夠定義一些可選的embedded_javascript , embedded_css,javascript_file,css_file等文件到頁面中。
使用基本模塊
想要在你的模板中引入一個模塊,你必需要在應用程序中聲明它。這個UI 模塊將會把maps模塊做爲參數導入到模板中,請查看例子3-1:
在這個例子中只有一個項用到了UI_module字典。同時咱們在HelloModule類中已經定義了一個名字爲Hello的模塊,如今當咱們調用HelloHandler 時將會顯示hello.html,咱們能夠經過{%module Hello()%} 這個模板標籤導入HelloModule類渲染後生成的字符串:
這個hello.html模板將會用調用HelloModule類返回的字符串,去替換掉module標籤。這個例子在接下來的部分將會展現如何擴展UI模塊,導入JavaScript腳本和樣式表來渲染咱們的模板。
深刻模塊
一般,咱們會將模板中的module 標籤用module類中渲染的字符串替換掉。這些模板將會被咱們當成一個總體。
在一個應用程序中,UI模塊一般用來對數據庫查詢或者API查詢的結果進行迭代,在一個獨立的項目中展示帶有相同表示的數據。例如Burt但願可以在網站中建立一個推薦閱讀的模塊,在下面的代碼中咱們能夠看到,他將會建立一個recommended.html模板,並經過{%module Book(book)%} tag來插入數據:
Burt將會在book.html模板中建立一個Book模塊,它被存放在templates/modules目錄中。一個簡單的book模板看起來是這樣的:
如今咱們定義一個BookModule類,它將會繼承UIModule中的render_string方法,這個方法會將模板和它的關鍵字提取出來渲染而後做爲字符串返回給調用者。
在整個完整的示例中,咱們將會使用如下模板去格式化有推薦書籍的屬性,並替換掉book.html模板中的標籤。
根據這樣的排列,這個模塊將會調用每一本書籍的參數並傳遞給recommended.html模板,每一次調用都會生成一個新的書籍參數,這個模塊(還有book.html模塊)能夠按照恰當的格式引用每一本書籍的參數(請查看效果圖3-6)
如今咱們能夠定義一個RecommendedHandler,它將會按照Book 模塊返回的推薦書籍列表來渲染一個模板。
經過添加ui_modules 參數的映射就可使用附加的模塊,由於templates能夠調用任何模塊中定義的ui_modules映射,輕鬆地將特殊功能插入到咱們的模板中。
圖片3-6
在這個例子中,你可能已經注意到了咱們使用了locale.format_date()。它調用tornado.locale.module中的datahandling方法。這是一個擁有許多國際化選項的方法format_data(),在默認狀況下,它使用GMT的Unix時間戳來顯示時間,固然咱們也能夠經過這樣的方式{{locale.format_date(book["data"])}} relative = False的方式得到一個絕對時間(小時和分鐘),或者經過full_format=True的方式讓它顯示一個完整的時間(例如 July 9, 2011 at 9:47pm),還能夠經過shorter = True的方式讓它只顯示月日年。
這個模塊能夠有效地幫助咱們處理時間和日期的格式
嵌入 JavaScript 和CSS
爲了在模塊中引入更多特性,tornado容許你在模塊中嵌入單獨的CSS和JavaScript到embedded_css()和embedded_javascript()方法中,例如假如你想要在調用模塊時添加一行文字到DOM中,你能夠經過嵌入JavaScript到模塊中實現這個需求。
當模塊被調用的時候,在靠近<body>標籤的地方,將會添加一個document.write(\」hi!\」)到<script> 標籤中。
很明顯,僅僅添加內容到文檔中不是最有效率事情,咱們能夠在調用的時候根據不一樣的模塊靈活地定製導入不一樣的JavaScript和CSS:
在這個例子中,則將會在<head>標籤的附件插入<style>標籤引入.book{background-color:#555} CSS 規則:
若是但願獲得更多的特性,你可使用html_body()在靠近</body>的地方插入更多html標籤
很明顯,它可以幫助咱們在簡潔的代碼風格下有效地管理複雜的關聯文件(樣式表、腳本文件),你還能夠經過使用javascript_file()和css_files()去導入一些本地或外部的支持文件。例如你能夠像這樣導入一個獨立的CSS文件:
或者去獲取一個外部的JavaScript文件:
這樣能夠高效的管理模塊額外導入的庫。假如你有一個模塊須要使用jQuery UI 庫(其它模塊不須要使用),你能夠只在這個模塊加載jquery-ui.min.js文件,而其它模塊則不須要加載。
由於JavaScript-embedding, HTML-embedding嵌入功能在對</body>插入替換字符串時,html_body(), javascript_files(),embedded_javascript()將會按照倒序的方式插入到頁面的底部,因此若是你有一個模塊,你應該像這樣去指定嵌入元素:
- class SampleModule(tornado.web.UIModule):
- def render(self, sample):
- return self.render_string(
- 」modules/sample.html」,
- sample=sample
- )
- def html_body(self):
- return 」<div class=\」addition\」><p>html_body()</p></div>」
- def embedded_javascript(self):
- return 」document.write(\」<p>embedded_javascript()</p>\」)」
- def embedded_css(self):
- return 」.addition {color: #A1CAF1}」
- def css_files(self):
- return 」/static/css/sample.css」
- def javascript_files(self):
- return 」/static/js/sample.js」
這個html_body()將會做爲</body>以前的元素第一個寫入,接下來會由embedded_javascript()去渲染他,最後執行的是javascript_files(),你能夠在圖3-7中看到它是如何實現的,請注意,你若是在其它地方沒有導入這些請求的方法(好比使用JavaScript功能去替換一些文件),那麼你的頁面可能顯示效果會與你指望的不一樣
總之,tornado容許你靈活地使用規範的格式去渲染模板,也容許你對某一個模塊引入外部的樣式表或功能規則進行渲染,經過使用module的一些特殊功能,你能夠有效的增強代碼的可重用性,讓你的網站開發更簡單更快速。
總結
正如咱們看到的,tornado讓你能夠很輕鬆的對模板進行擴展,經過添加模塊,你能夠更精確地操做調用的文件、樣式表和腳本。然而目前在咱們的例子中常常用到python風格的數據類型,這會給程序帶來許多硬編碼的數據結構。接下來讓我帶你去看看如何經過動態的方式去處理數據持久化、存儲服務等內容。
原創翻譯,首發: http://blog.xihuan.de/tech/web/tornado/tornado_extending_templates.html