在第二章中,我們看到了Tornado模板系統(tǒng)如何簡(jiǎn)單地傳遞信息給網(wǎng)頁(yè),使你在插入動(dòng)態(tài)數(shù)據(jù)時(shí)保持網(wǎng)頁(yè)標(biāo)記的整潔。然而,大多數(shù)站點(diǎn)希望復(fù)用像header、footer和布局網(wǎng)格這樣的內(nèi)容。在這一章中,我們將看到如何使用擴(kuò)展Tornado模板或UI模塊完成這一工作。
當(dāng)你花時(shí)間為你的Web應(yīng)用建立和制定模板時(shí),希望像你的后端Python代碼一樣重用你的前端代碼似乎只是合邏輯的,不是嗎?幸運(yùn)的是,Tornado可以讓你做到這一點(diǎn)。Tornado通過(guò)extends和block語(yǔ)句支持模板繼承,這就讓你擁有了編寫能夠在合適的地方復(fù)用的流體模板的控制權(quán)和靈活性。
為了擴(kuò)展一個(gè)已經(jīng)存在的模板,你只需要在新的模板文件的頂部放上一句{% extends "filename.html" %}。
比如,為了在新模板中擴(kuò)展一個(gè)父模板(在這里假設(shè)為main.html),你可以這樣使用:
{% extends "main.html" %}
這就使得新文件繼承main.html的所有標(biāo)簽,并且覆寫為期望的內(nèi)容。
擴(kuò)展一個(gè)模板使你復(fù)用之前寫過(guò)的代碼更加簡(jiǎn)單,但是這并不會(huì)為你提供所有的東西,除非你可以適應(yīng)并改變那些之前的模板。所以,block語(yǔ)句出現(xiàn)了。
一個(gè)塊語(yǔ)句壓縮了一些當(dāng)你擴(kuò)展時(shí)可能想要改變的模板元素。比如,為了使用一個(gè)能夠根據(jù)不同頁(yè)覆寫的動(dòng)態(tài)header塊,你可以在父模板main.html中添加如下代碼:
<header>
{% block header %}{% end %}
</header>
然后,為了在子模板index.html中覆寫{% block header %}{% end %}
部分,你可以使用塊的名字引用,并把任何你想要的內(nèi)容放到其中。
{% block header %}{% end %}
{% block header %}
<h1>Hello world!</h1>
{% end %}
任何繼承這個(gè)模板的文件都可以包含它自己的{% block header %}
和{% end %}
,然后把一些不同的東西加進(jìn)去。
為了在Web應(yīng)用中調(diào)用這個(gè)子模板,你可以在你的Python腳本中很輕松地渲染它,就像之前你渲染其他模板那樣:
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
所以此時(shí),main.html中的body塊在加載時(shí)會(huì)被以index.html中的信息"Hello world!"填充(參見圖3-1)。
圖3-1 Hello world!
我們已經(jīng)可以看到這種方法在處理整體頁(yè)面結(jié)構(gòu)和節(jié)約多頁(yè)面網(wǎng)站的開發(fā)時(shí)間上多么有用。更好的是,你可以為每個(gè)頁(yè)面使用多個(gè)塊,此時(shí)像header和footer這樣的動(dòng)態(tài)元素將會(huì)被包含在同一個(gè)流程中。
下面是一個(gè)在父模板main.html中使用多個(gè)塊的例子:
<html>
<body>
<header>
{% block header %}{% end %}
</header>
<content>
{% block body %}{% end %}
</content>
<footer>
{% block footer %}{% end %}
</footer>
</body>
</html>
當(dāng)我們擴(kuò)展父模板main.html時(shí),可以在子模板index.html中引用這些塊。
{% extends "main.html" %}
{% block header %}
<h1>{{ header_text }}</h1>
{% end %}
{% block body %}
<p>Hello from the child template!</p>
{% end %}
{% block footer %}
<p>{{ footer_text }}</p>
{% end %}
用來(lái)加載模板的Python腳本和上一個(gè)例子差不多,不過(guò)在這里我們傳遞了幾個(gè)字符串變量給模板使用(如圖3-2):
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index.html",
header_text = "Header goes here",
footer_text = "Footer goes here"
)
圖3-2 塊基礎(chǔ)
你也可以保留父模板塊語(yǔ)句中的默認(rèn)文本和標(biāo)記,就像擴(kuò)展模板沒有指定它自己的塊版本一樣被渲染。這種情況下,你可以根據(jù)某頁(yè)的情況只替換必須的東西,這在包含或替換腳本、CSS文件和標(biāo)記塊時(shí)非常有用。
正如模板文檔所記錄的,"錯(cuò)誤報(bào)告目前...呃...是非常有意思的"。一個(gè)語(yǔ)法錯(cuò)誤或者沒有閉合的{% block %}語(yǔ)句可以使得瀏覽器直接顯示500: Internal Server Error(如果你運(yùn)行在debug模式下會(huì)引發(fā)完整的Python堆棧跟蹤)。如圖3-3所示。
總之,為了你自己好的話,你需要使自己的模板盡可能的魯棒,并且在模板被渲染之前發(fā)現(xiàn)錯(cuò)誤。
圖3-3 塊錯(cuò)誤
所以,你會(huì)認(rèn)為這聽起來(lái)很有趣,但卻不能描繪出在一個(gè)標(biāo)準(zhǔn)的Web應(yīng)用中如何使用?那么讓我們?cè)谶@里看一個(gè)例子,我們的朋友Burt希望運(yùn)行一個(gè)名叫Burt's Books的書店。
Burt通過(guò)他的書店賣很多書,他的網(wǎng)站會(huì)展示很多不同的內(nèi)容,比如新品推薦、商店信息等等。Burt希望有一個(gè)固定的外觀和感覺的網(wǎng)站,同時(shí)也能更簡(jiǎn)單的更新頁(yè)面和段落。
為了做到這些,Burt's Book使用了以Tornado為基礎(chǔ)的網(wǎng)站,其中包括一個(gè)擁有樣式、布局和header/footer細(xì)節(jié)的主模版,以及一個(gè)處理頁(yè)面的輕量級(jí)的子模板。在這個(gè)系統(tǒng)中,Burt可以把最新發(fā)布、員工推薦、即將發(fā)行等不同頁(yè)面編寫在一起,共同使用通用的基礎(chǔ)屬性。
Burt's Book的網(wǎng)站使用一個(gè)叫作main.html的主要基礎(chǔ)模板,用來(lái)包含網(wǎng)站的通用架構(gòu),如下面的代碼所示:
<html>
<head>
<title>{{ page_title }}</title>
<link rel="stylesheet" href="{{ static_url("css/style.css") }}" />
</head>
<body>
<div id="container">
<header>
{% block header %}<h1>Burt's Books</h1>{% end %}
</header>
<div id="main">
<div id="content">
{% block body %}{% end %}
</div>
</div>
<footer>
{% block footer %}
<p>
For more information about our selection, hours or events, please email us at
<a href="mailto:contact@burtsbooks.com">contact@burtsbooks.com</a>.
</p>
{% end %}
</footer>
</div>
<script src="https://atts.w3cschool.cn/attachments/image/cimg/script.js") }}"></script>
</body>
</html>
這個(gè)頁(yè)面定義了結(jié)構(gòu),應(yīng)用了一個(gè)CSS樣式表,并加載了主要的JavaScript文件。其他模板可以擴(kuò)展它,在必要時(shí)替換header、body和footer塊。
這個(gè)網(wǎng)站的index頁(yè)(index.html)歡迎友好的網(wǎng)站訪問者并提供一些商店的信息。通過(guò)擴(kuò)展main.html,這個(gè)文件只需要包括用于替換默認(rèn)文本的header和body塊的信息。
{% extends "main.html" %}
{% block header %}
<h1>{{ header_text }}</h1>
{% end %}
{% block body %}
<div id="hello">
<p>Welcome to Burt's Books!</p>
<p>...</p>
</div>
{% end %}
在footer塊中,這個(gè)文件使用了Tornado模板的默認(rèn)行為,繼承了來(lái)自父模板的聯(lián)系信息。
為了運(yùn)作網(wǎng)站,傳遞信息給index模板,下面給出Burt's Book的Python腳本(main.py):
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os.path
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
]
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
debug=True,
)
tornado.web.Application.__init__(self, handlers, **settings)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index.html",
page_title = "Burt's Books | Home",
header_text = "Welcome to Burt's Books!",
)
if __name__ == "__main__":
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
這個(gè)例子的結(jié)構(gòu)和我們之前見到的不太一樣,但你一點(diǎn)都不需要害怕。我們不再像之前那樣通過(guò)使用一個(gè)處理類列表和一些其他關(guān)鍵字參數(shù)調(diào)用tornado.web.Application的構(gòu)造函數(shù)來(lái)創(chuàng)建實(shí)例,而是定義了我們自己的Application子類,在這里我們簡(jiǎn)單地稱之為Application。在我們定義的init方法中,我們創(chuàng)建了處理類列表以及一個(gè)設(shè)置的字典,然后在初始化子類的調(diào)用中傳遞這些值,就像下面的代碼一樣:
tornado.web.Application.__init__(self, handlers, **settings)
所以在這個(gè)系統(tǒng)中,Burt's Book可以很容易地改變index頁(yè)面并保持基礎(chǔ)模板在其他頁(yè)面被使用時(shí)完好。此外,他們可以充分利用Tornado的真實(shí)能量,由Python腳本和/或數(shù)據(jù)庫(kù)提供動(dòng)態(tài)內(nèi)容。我們將在之后看到更多相關(guān)的內(nèi)容。
Tornado默認(rèn)會(huì)自動(dòng)轉(zhuǎn)義模板中的內(nèi)容,把標(biāo)簽轉(zhuǎn)換為相應(yīng)的HTML實(shí)體。這樣可以防止后端為數(shù)據(jù)庫(kù)的網(wǎng)站被惡意腳本攻擊。比如,你的網(wǎng)站中有一個(gè)評(píng)論部分,用戶可以在這里添加任何他們想說(shuō)的文字進(jìn)行討論。雖然一些HTML標(biāo)簽在標(biāo)記和樣式?jīng)_突時(shí)不構(gòu)成重大威脅(如評(píng)論中沒有閉標(biāo)簽),但標(biāo)簽會(huì)允許攻擊者加載其他的JavaScript文件,打開通向跨站腳本攻擊、XSS或漏洞之門。
讓我們考慮Burt's Book網(wǎng)站上的一個(gè)用戶反饋?lái)?yè)面。Melvin,今天感覺特別邪惡,在評(píng)論里提交了下面的文字:
Totally hacked your site lulz <script>alert('RUNNING EVIL H4CKS AND SPL01TS NOW...')</script>
當(dāng)我們?cè)跊]有轉(zhuǎn)義用戶內(nèi)容的情況下給一個(gè)不知情的用戶構(gòu)建頁(yè)面時(shí),腳本標(biāo)簽被作為一個(gè)HTML元素解釋,并被瀏覽器執(zhí)行,所以Alice看到了如圖3-4所示的提示窗口。幸虧Tornado會(huì)自動(dòng)轉(zhuǎn)義在雙大括號(hào)間被渲染的表達(dá)式。更早地轉(zhuǎn)義Melvin輸入的文本不會(huì)激活HTML標(biāo)簽,并且會(huì)渲染為下面的字符串:
Totally hacked your site lulz <script>alert('RUNNING EVIL H4CKS AND SPL01TS NOW...')</script>
圖3-4 網(wǎng)站漏洞問題
現(xiàn)在當(dāng)Alice訪問網(wǎng)站時(shí),沒有惡意腳本被執(zhí)行,所以她看到的頁(yè)面如圖3-5所示。
圖3-5 網(wǎng)站漏洞問題--解決
在Tornado1.x版本中,模板沒有被自動(dòng)轉(zhuǎn)義,所以我們之前談?wù)摰姆雷o(hù)措施需要顯式地在未過(guò)濾的用戶輸入上調(diào)用escape()函數(shù)。
所以在這里,我們可以看到自動(dòng)轉(zhuǎn)義是如何防止你的訪客進(jìn)行惡意攻擊的。然而,當(dāng)通過(guò)模板和模塊提供HTML動(dòng)態(tài)內(nèi)容時(shí)它仍會(huì)讓你措手不及。
舉個(gè)例子,如果Burt想在footer中使用模板變量設(shè)置email聯(lián)系鏈接,他將不會(huì)得到期望的HTML鏈接??紤]下面的模板片段:
{% set mailLink = "<a href="mailto:contact@burtsbooks.com">Contact Us</a>" %}
{{ mailLink }}'
它會(huì)在頁(yè)面源代碼中渲染成如下代碼:
<a href="mailto:contact@burtsbooks.com">Contact Us</a>
此時(shí)自動(dòng)轉(zhuǎn)義被運(yùn)行了,很明顯,這無(wú)法讓人們聯(lián)系上Burt。
為了處理這種情況,你可以禁用自動(dòng)轉(zhuǎn)義,一種方法是在Application構(gòu)造函數(shù)中傳遞autoescape=None,另一種方法是在每頁(yè)的基礎(chǔ)上修改自動(dòng)轉(zhuǎn)義行為,如下所示:
{% autoescape None %}
{{ mailLink }}
這些autoescape塊不需要結(jié)束標(biāo)簽,并且可以設(shè)置xhtml_escape來(lái)開啟自動(dòng)轉(zhuǎn)義(默認(rèn)行為),或None來(lái)關(guān)閉。
然而,在理想的情況下,你希望保持自動(dòng)轉(zhuǎn)義開啟以便繼續(xù)防護(hù)你的網(wǎng)站。因此,你可以使用{% raw %}指令來(lái)輸出不轉(zhuǎn)義的內(nèi)容。
{% raw mailLink %}
需要特別注意的是,當(dāng)你使用諸如Tornado的linkify()和xsrf_form_html()函數(shù)時(shí),自動(dòng)轉(zhuǎn)義的設(shè)置被改變了。所以如果你希望在前面代碼的footer中使用linkify()來(lái)包含鏈接,你可以使用一個(gè){% raw %}塊:
{% block footer %}
<p>
For more information about our selection, hours or events, please email us at
<a href="mailto:contact@burtsbooks.com">contact@burtsbooks.com</a>.
</p>
<p class="small">
Follow us on Facebook at
{% raw linkify("https://fb.me/burtsbooks", extra_params='ref=website') %}.
</p>
{% end %}
這樣,你可以既利用linkify()簡(jiǎn)記的好處,又可以保持在其他地方自動(dòng)轉(zhuǎn)義的好處。
正如前面我們所看到的,模板系統(tǒng)既輕量級(jí)又強(qiáng)大。在實(shí)踐中,我們希望遵循軟件工程的諺語(yǔ),Don't Repeat Yourself。為了消除冗余的代碼,我們可以使模板部分模塊化。比如,展示物品列表的頁(yè)面可以定位一個(gè)單獨(dú)的模板用來(lái)渲染每個(gè)物品的標(biāo)記。另外,一組共用通用導(dǎo)航結(jié)構(gòu)的頁(yè)面可以從一個(gè)共享的模塊渲染內(nèi)容。Tornado的UI模塊在這種情況下特別有用
UI模塊是封裝模板中包含的標(biāo)記、樣式以及行為的可復(fù)用組件。它所定義的元素通常用于多個(gè)模板交叉復(fù)用或在同一個(gè)模板中重復(fù)使用。模塊本身是一個(gè)繼承自Tornado的UIModule類的簡(jiǎn)單Python類,并定義了一個(gè)render方法。當(dāng)一個(gè)模板使用{% module Foo(...) %}標(biāo)簽引用一個(gè)模塊時(shí),Tornado的模板引擎調(diào)用模塊的render方法,然后返回一個(gè)字符串來(lái)替換模板中的模塊標(biāo)簽。UI模塊也可以在渲染后的頁(yè)面中嵌入自己的JavaScript和CSS文件,或指定額外包含的JavaScript或CSS文件。你可以定義可選的embedded_javascript、embedded_css、javascript_files和css_files方法來(lái)實(shí)現(xiàn)這一方法。
為了在你的模板中引用模塊,你必須在應(yīng)用的設(shè)置中聲明它。ui_moudles參數(shù)期望一個(gè)模塊名為鍵、類為值的字典輸入來(lái)渲染它們??紤]代碼清單3-1。
代碼清單3-1 模塊基礎(chǔ):hello_module.py
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os.path
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class HelloHandler(tornado.web.RequestHandler):
def get(self):
self.render('hello.html')
class HelloModule(tornado.web.UIModule):
def render(self):
return '<h1>Hello, world!</h1>'
if __name__ == '__main__':
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[(r'/', HelloHandler)],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
ui_modules={'Hello': HelloModule}
)
server = tornado.httpserver.HTTPServer(app)
server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
這個(gè)例子中ui_module字典里只有一項(xiàng),它把到名為Hello的模塊的引用和我們定義的HelloModule類結(jié)合了起來(lái)。
現(xiàn)在,當(dāng)調(diào)用HelloHandler并渲染hello.html時(shí),我們可以使用{% module Hello() %}模板標(biāo)簽來(lái)包含HelloModule類中render方法返回的字符串。
<html>
<head><title>UI Module Example</title></head>
<body>
{% module Hello() %}
</body>
</html>
這個(gè)hello.html模板通過(guò)在模塊標(biāo)簽自身的位置調(diào)用HelloModule返回的字符串進(jìn)行填充。下一節(jié)的例子將會(huì)展示如何擴(kuò)展UI模塊來(lái)渲染它們自己的模板并包含腳本和樣式表。
很多時(shí)候,一個(gè)非常有用的做法是讓模塊指向一個(gè)模板文件而不是在模塊類中直接渲染字符串。這些模板的標(biāo)記看起來(lái)就像我們已經(jīng)看到過(guò)的作為整體的模板。
UI模塊的一個(gè)常見應(yīng)用是迭代數(shù)據(jù)庫(kù)或API查詢中獲得的結(jié)果,為每個(gè)獨(dú)立項(xiàng)目的數(shù)據(jù)渲染相同的標(biāo)記。比如,Burt想在Burt's Book里創(chuàng)建一個(gè)推薦閱讀部分,他已經(jīng)創(chuàng)建了一個(gè)名為recommended.html的模板,其代碼如下所示。就像前面看到的那樣,我們將使用{% module Book(book) %}標(biāo)簽調(diào)用模塊。
{% extends "main.html" %}
{% block body %}
<h2>Recommended Reading</h2>
{% for book in books %}
{% module Book(book) %}
{% end %}
{% end %}
Burt還創(chuàng)建了一個(gè)叫作book.html的圖書模塊的模板,并把它放到了templates/modules目錄下。一個(gè)簡(jiǎn)單的圖書模板看起來(lái)像下面這樣:
<div class="book">
<h3 class="book_title">{{ book["title"] }}</h3>
<img src="{{ book["image"] }}" class="book_image"/>
</div>
現(xiàn)在,當(dāng)我們定義BookModule類的時(shí)候,我們將調(diào)用繼承自UIModule的render_string方法。這個(gè)方法顯式地渲染模板文件,當(dāng)我們返回給調(diào)用者時(shí)將其關(guān)鍵字參數(shù)作為一個(gè)字符串。
class BookModule(tornado.web.UIModule):
def render(self, book):
return self.render_string('modules/book.html', book=book)
在完整的例子中,我們將使用下面的模板來(lái)格式化每個(gè)推薦書籍的所有屬性,代替先前的book.html
<div class="book">
<h3 class="book_title">{{ book["title"] }}</h3>
{% if book["subtitle"] != "" %}
<h4 class="book_subtitle">{{ book["subtitle"] }}</h4>
{% end %}
<img src="{{ book["image"] }}" class="book_image"/>
<div class="book_details">
<div class="book_date_released">Released: {{ book["date_released"]}}</div>
<div class="book_date_added">
Added: {{ locale.format_date(book["date_added"], relative=False) }}
</div>
<h5>Description:</h5>
<div class="book_body">{% raw book["description"] %}</div>
</div>
</div>
使用這個(gè)布局,傳遞給recommended.html模板的books參數(shù)的每項(xiàng)都將會(huì)調(diào)用這個(gè)模塊。每次使用一個(gè)新的book參數(shù)調(diào)用Book模塊時(shí),模塊(以及book.html模板)可以引用book參數(shù)的字典中的項(xiàng),并以適合的方式格式化數(shù)據(jù)(如圖3-6)。
圖3-6 包含樣式數(shù)據(jù)的圖書模塊
現(xiàn)在,我們可以定義一個(gè)RecommendedHandler類來(lái)渲染模板,就像你通常的操作那樣。這個(gè)模板可以在渲染推薦書籍列表時(shí)引用Book模塊。
class RecommendedHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"recommended.html",
page_title="Burt's Books | Recommended Reading",
header_text="Recommended Reading",
books=[
{
"title":"Programming Collective Intelligence",
"subtitle": "Building Smart Web 2.0 Applications",
"image":"/static/images/collective_intelligence.gif",
"author": "Toby Segaran",
"date_added":1310248056,
"date_released": "August 2007",
"isbn":"978-0-596-52932-1",
"description":"<p>This fascinating book demonstrates how you "
"can build web applications to mine the enormous amount of data created by people "
"on the Internet. With the sophisticated algorithms in this book, you can write "
"smart programs to access interesting datasets from other web sites, collect data "
"from users of your own applications, and analyze and understand the data once "
"you've found it.</p>"
},
...
]
)
如果要用更多的模塊,只需要簡(jiǎn)單地在ui_modules參數(shù)中添加映射值。因?yàn)槟0蹇梢灾赶蛉魏味x在ui_modules字典中的模塊,所以在自己的模塊中指定功能非常容易。
在這個(gè)例子中,你可能已經(jīng)注意到了locale.format_date()的使用。它調(diào)用了tornado.locale模塊提供的日期處理方法,這個(gè)模塊本身是一組i18n方法的集合。format_date()選項(xiàng)默認(rèn)格式化GMT Unix時(shí)間戳為XX time ago
,并且可以向下面這樣使用:
{{ locale.format_date(book["date"]) }}
relative=False將使其返回一個(gè)絕對(duì)時(shí)間(包含小時(shí)和分鐘),而full_format=True選項(xiàng)將會(huì)展示一個(gè)包含月、日、年和時(shí)間的完整日期(比如,July 9, 2011 at 9:47 pm
),當(dāng)搭配shorter=True使用時(shí)可以隱藏時(shí)間,只顯示月、日和年。
這個(gè)模塊在你處理時(shí)間和日期時(shí)非常有用,并且還提供了處理本地化字符串的支持。
為了給這些模塊提供更高的靈活性,Tornado允許你使用embedded_css和embedded_javascript方法嵌入其他的CSS和JavaScript文件。舉個(gè)例子,如果你想在調(diào)用模塊時(shí)給DOM添加一行文字,你可以通過(guò)從模塊中嵌入JavaScript來(lái)做到:
class BookModule(tornado.web.UIModule):
def render(self, book):
return self.render_string(
"modules/book.html",
book=book,
)
def embedded_javascript(self):
return "document.write(\"hi!\")"
當(dāng)調(diào)用模塊時(shí),document.write(\"hi!\")將被包圍,并被插入到的閉標(biāo)簽中:
<script type="text/javascript">
//<![CDATA[
document.write("hi!")
//]]>
</script>
顯然,只是在文檔主體中寫這些內(nèi)容并不是世界上最有用的事情,而我們還有另一個(gè)給予你極大靈活性的選項(xiàng),當(dāng)創(chuàng)建這些模塊時(shí),可以在每個(gè)模塊中包含JavaScript文件。
類似的,你也可以把只在這些模塊被調(diào)用時(shí)加載的額外的CSS規(guī)則放進(jìn)來(lái):
def embedded_css(self):
return ".book {background-color:#F5F5F5}"
在這種情況下,.book {background-color:#555}
這條CSS規(guī)則被包裹在中,并被直接添加到的閉標(biāo)簽之前。
<style type="text/css">
.book {background-color:#F5F5F5}
</style>
更加靈活的是,你甚至可以簡(jiǎn)單地使用html_body()來(lái)在閉合的標(biāo)簽前添加完整的HTML標(biāo)記:
def html_body(self):
return "<script>document.write("Hello!")</script>"
顯然,雖然直接內(nèi)嵌添加腳本和樣式表很有用,但是為了更嚴(yán)謹(jǐn)?shù)陌ㄒ约案麧嵉拇a?。砑訕邮奖砗湍_本文件會(huì)顯得更好。他們的工作方式基本相同,所以你可以使用javascript_files()和css_files()來(lái)包含完整的文件,不論是本地的還是外部的。
比如,你可以添加一個(gè)額外的本地CSS文件如下:
def css_files(self):
return "/static/css/newreleases.css"
或者你可以取得一個(gè)外部的JavaScript文件:
def javascript_files(self):
return "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"
當(dāng)一個(gè)模塊需要額外的庫(kù)而應(yīng)用的其他地方不是必需的時(shí)候,這種方式非常有用。比如,你有一個(gè)使用JQuery UI庫(kù)的模塊(而在應(yīng)用的其他地方都不會(huì)被使用),你可以只在這個(gè)樣本模塊中加載jquery-ui.min.js文件,減少那些不需要它的頁(yè)面的加載時(shí)間。
因?yàn)槟K的內(nèi)嵌JavaScript和內(nèi)嵌HTML函數(shù)的目標(biāo)都是緊鄰標(biāo)簽,html_body()、javascript_files()和embedded_javascript()都會(huì)將內(nèi)容渲染后插到頁(yè)面底部,那么它們出現(xiàn)的順序正好是你指定它們的順序的倒序。
如果你有一個(gè)模塊如下面的代碼所示:
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()最先被編寫,它緊挨著出現(xiàn)在標(biāo)簽的上面。embedded_javascript()接著被渲染,最后是javascript_files()。你可以在圖3-7中看到它是如何工作的。
需要小心的是,你不能包括一個(gè)需要其他地方東西的方法(比如依賴其他文件的JavaScript函數(shù)),因?yàn)榇藭r(shí)他們可能會(huì)按照和你期望不同的順序進(jìn)行渲染。
總之,模塊允許你在模板中渲染格式化數(shù)據(jù)時(shí)非常靈活,同時(shí)也讓你能夠只在調(diào)用模塊時(shí)包含指定的一些額外的樣式和函數(shù)規(guī)則。
正如我們之前看到的,Tornado使擴(kuò)展模板更容易,以便你的網(wǎng)站代碼可以在整個(gè)應(yīng)用中輕松復(fù)用。而使用模塊后,你可以在什么文件、樣式和腳本動(dòng)作需要被包括進(jìn)來(lái)這個(gè)問題上擁有更細(xì)粒度的決策。然而,我們的例子依賴于使用Python原生數(shù)據(jù)結(jié)構(gòu)時(shí)是否簡(jiǎn)單,在你的實(shí)際應(yīng)用中硬編碼大數(shù)據(jù)結(jié)構(gòu)的感覺可不好。下一步,我們將看到如何配合持久化存儲(chǔ)來(lái)處理存儲(chǔ)、提供和編輯動(dòng)態(tài)內(nèi)容。
更多建議: