4.2 使用模板
1. 路徑與渲染
使用模板,需要仿照靜態(tài)文件路徑設(shè)置一樣,向web.Application類的構(gòu)造函數(shù)傳遞一個(gè)名為template_path的參數(shù)來(lái)告訴Tornado從文件系統(tǒng)的一個(gè)特定位置提供模板文件,如:
app = tornado.web.Application(
[(r'/', IndexHandler)],
static_path=os.path.join(os.path.dirname(__file__), "statics"),
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
在這里,我們?cè)O(shè)置了一個(gè)當(dāng)前應(yīng)用目錄下名為templates的子目錄作為template_path的參數(shù)。在handler中使用的模板將在此目錄中尋找。
現(xiàn)在我們將靜態(tài)文件目錄statics/html中的index.html復(fù)制一份到templates目錄中,此時(shí)文件目錄結(jié)構(gòu)為:
.
├── statics
│ ├── css
│ │ ├── index.css
│ │ ├── main.css
│ │ └── reset.css
│ ├── html
│ │ └── index.html
│ ├── images
│ │ ├── home01.jpg
│ │ ├── home02.jpg
│ │ ├── home03.jpg
│ │ └── landlord01.jpg
│ ├── js
│ │ ├── index.js
│ │ └── jquery.min.js
│ └── plugins
│ ├── bootstrap
│ │ └─...
│ └── font-awesome
│ └─...
├── templates
│ └── index.html
└── test.py
在handler中使用render()方法來(lái)渲染模板并返回給客戶端。
class IndexHandler(RequestHandler):
def get(self):
self.render("index.html") # 渲染主頁(yè)模板,并返回給客戶端。
current_path = os.path.dirname(__file__)
app = tornado.web.Application(
[
(r'^/$', IndexHandler),
(r'^/view/(.*)$', StaticFileHandler, {"path":os.path.join(current_path, "statics/html")}),
],
static_path=os.path.join(current_path, "statics"),
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
2. 模板語(yǔ)法
2-1 變量與表達(dá)式
在tornado的模板中使用{{}}作為變量或表達(dá)式的占位符,使用render渲染后占位符{{}}會(huì)被替換為相應(yīng)的結(jié)果值。
我們將index.html中的一條房源信息記錄
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>398</span>/晚</div>
<div class="house-intro">
<span class="house-title">寬窄巷子+160平大空間+文化保護(hù)區(qū)雙地鐵</span>
<em>整套出租 - 5分/6點(diǎn)評(píng) - 北京市豐臺(tái)區(qū)六里橋地鐵</em>
</div>
</div>
</li>
改為模板:
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{price}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title}}</span>
<em>整套出租 - {{score}}分/{{comments}}點(diǎn)評(píng) - {{position}}</em>
</div>
</div>
</li>
渲染方式如下:
class IndexHandler(RequestHandler):
def get(self):
house_info = {
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護(hù)區(qū)雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
}
self.render("index.html", **house_info)
{{}}不僅可以包含變量,還可以是表達(dá)式,如:
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{p1 + p2}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{"+".join(titles)}}</span>
<em>整套出租 - {{score}}分/{{comments}}點(diǎn)評(píng) - {{position}}</em>
</div>
</div>
</li>
class IndexHandler(RequestHandler):
def get(self):
house_info = {
"p1": 198,
"p2": 200,
"titles": ["寬窄巷子", "160平大空間", "文化保護(hù)區(qū)雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
}
self.render("index.html", **house_info)
2-2 控制語(yǔ)句
可以在Tornado模板中使用Python條件和循環(huán)語(yǔ)句??刂普Z(yǔ)句以{%和%}包圍,并以類似下面的形式被使用:
{% if page is None %}
或
{% if len(entries) == 3 %}
控制語(yǔ)句的大部分就像對(duì)應(yīng)的Python語(yǔ)句一樣工作,支持if、for、while,注意end:
{% if ... %} ... {% elif ... %} ... {% else ... %} ... {% end %}
{% for ... in ... %} ... {% end %}
{% while ... %} ... {% end %}
再次修改index.html:
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{house["title"]}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點(diǎn)評(píng) - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對(duì)不起,暫時(shí)沒有房源。
{% end %}
</ul>
python中渲染語(yǔ)句為:
class IndexHandler(RequestHandler):
def get(self):
houses = [
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護(hù)區(qū)雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護(hù)區(qū)雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護(hù)區(qū)雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護(hù)區(qū)雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護(hù)區(qū)雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
}]
self.render("index.html", houses=houses)
2-3 函數(shù)
static_url()
Tornado模板模塊提供了一個(gè)叫作static_url的函數(shù)來(lái)生成靜態(tài)文件目錄下文件的URL。如下面的示例代碼:
<link rel="stylesheet" href="{{ static_url("style.css") }}">
這個(gè)對(duì)static_url的調(diào)用生成了URL的值,并渲染輸出類似下面的代碼:
<link rel="stylesheet" href="/static/style.css?v=ab12">
優(yōu)點(diǎn):
- static_url函數(shù)創(chuàng)建了一個(gè)基于文件內(nèi)容的hash值,并將其添加到URL末尾(查詢字符串的參數(shù)v)。這個(gè)hash值確保瀏覽器總是加載一個(gè)文件的最新版而不是之前的緩存版本。無(wú)論是在你應(yīng)用的開發(fā)階段,還是在部署到生產(chǎn)環(huán)境使用時(shí),都非常有用,因?yàn)槟愕挠脩舨槐卦贋榱丝吹侥愕撵o態(tài)內(nèi)容而清除瀏覽器緩存了。
- 另一個(gè)好處是你可以改變你應(yīng)用URL的結(jié)構(gòu),而不需要改變模板中的代碼。例如,可以通過(guò)設(shè)置static_url_prefix來(lái)更改Tornado的默認(rèn)靜態(tài)路徑前綴/static。如果使用static_url而不是硬編碼的話,代碼不需要改變。
轉(zhuǎn)義
我們新建一個(gè)表單頁(yè)面new.html
<!DOCTYPE html>
<html>
<head>
<title>新建房源</title>
</head>
<body>
<form method="post">
<textarea name="text"></textarea>
<input type="submit" value="提交">
</form>
{{text}}
</body>
</html>
對(duì)應(yīng)的handler為:
class NewHandler(RequestHandler):
def get(self):
self.render("new.html", text="")
def post(self):
text = self.get_argument("text", "")
print text
self.render("new.html", text=text)
當(dāng)我們?cè)诒韱沃刑钊肴缦聝?nèi)容時(shí):
<script>alert("hello!");</script>

寫入的js程序并沒有運(yùn)行,而是顯示出來(lái)了:

我們查看頁(yè)面源代碼,發(fā)現(xiàn)<、>、"等被轉(zhuǎn)換為對(duì)應(yīng)的html字符。
<script>alert("hello!");</script>
這是因?yàn)閠ornado中默認(rèn)開啟了模板自動(dòng)轉(zhuǎn)義功能,防止網(wǎng)站受到惡意攻擊。
我們可以通過(guò)raw語(yǔ)句來(lái)輸出不被轉(zhuǎn)義的原始格式,如:
{% raw text %}
注意:在Firefox瀏覽器中會(huì)直接彈出alert窗口,而在Chrome瀏覽器中,需要set_header("X-XSS-Protection", 0)
若要關(guān)閉自動(dòng)轉(zhuǎn)義,一種方法是在Application構(gòu)造函數(shù)中傳遞autoescape=None,另一種方法是在每頁(yè)模板中修改自動(dòng)轉(zhuǎn)義行為,添加如下語(yǔ)句:
{% autoescape None %}
escape()
關(guān)閉自動(dòng)轉(zhuǎn)義后,可以使用escape()函數(shù)來(lái)對(duì)特定變量進(jìn)行轉(zhuǎn)義,如:
{{ escape(text) }}
自定義函數(shù)
在模板中還可以使用一個(gè)自己編寫的函數(shù),只需要將函數(shù)名作為模板的參數(shù)傳遞即可,就像其他變量一樣。
我們修改后端如下:
def house_title_join(titles):
return "+".join(titles)
class IndexHandler(RequestHandler):
def get(self):
house_list = [
{
"price": 398,
"titles": ["寬窄巷子", "160平大空間", "文化保護(hù)區(qū)雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
},
{
"price": 398,
"titles": ["寬窄巷子", "160平大空間", "文化保護(hù)區(qū)雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺(tái)區(qū)六里橋地鐵"
}]
self.render("index.html", houses=house_list, title_join = house_title_join)
前段模板我們修改為:
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title_join(house["titles"])}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點(diǎn)評(píng) - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對(duì)不起,暫時(shí)沒有房源。
{% end %}
</ul>
2-4 塊
我們可以使用塊來(lái)復(fù)用模板,塊語(yǔ)法如下:
{% block block_name %} {% end %}
現(xiàn)在,我們對(duì)模板index.html進(jìn)行抽象,抽離出父模板base.html如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
{% block page_title %}{% end %}
<link href="{{static_url('plugins/bootstrap/css/bootstrap.min.css')}}" rel="stylesheet">
<link href="{{static_url('plugins/font-awesome/css/font-awesome.min.css')}}" rel="stylesheet">
<link href="{{static_url('css/reset.css')}}" rel="stylesheet">
<link href="{{static_url('css/main.css')}}" rel="stylesheet">
{% block css_files %}{% end %}
</head>
<body>
<div class="container">
<div class="top-bar">
{% block header %}{% end %}
</div>
{% block body %}{% end %}
<div class="footer">
{% block footer %}{% end %}
</div>
</div>
<script src="{{static_url('js/jquery.min.js')}}"></script>
<script src="{{static_url('plugins/bootstrap/js/bootstrap.min.js')}}"></script>
{% block js_files %}{% end %}
</body>
</html>
而子模板index.html使用extends來(lái)使用父模板base.html,如下:
{% extends "base.html" %}
{% block page_title %}
<title>愛家-房源</title>
{% end %}
{% block css_files %}
<link href="{{static_url('css/index.css')}}" rel="stylesheet">
{% end %}
{% block js_files %}
<script src="{{static_url('js/index.js')}}"></script>
{% end %}
{% block header %}
<div class="nav-bar">
<h3 class="page-title">房 源</h3>
</div>
{% end %}
{% block body %}
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title_join(house["titles"])}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點(diǎn)評(píng) - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對(duì)不起,暫時(shí)沒有房源。
{% end %}
</ul>
{% end %}
{% block footer %}
<p><span><i class="fa fa-copyright"></i></span>愛家租房 享受家的溫馨</p>
{% end %}