Tornado之模板
知識點
- 靜態文件配置
- static_path
- StaticFileHandler
- 模板使用
- 變量與表達式
- 控制語句
- 函數
- 塊
4.1 靜態文件
現在有一個預先寫好的靜態頁面文件 (下載靜態文件資源), 我們來看下如何用tornado提供靜態文件。
static_path
我們可以通過向web.Application類的構造函數傳遞一個名為static_path的參數來告訴Tornado從文件系統的一個特定位置提供靜態文件,如:
app = tornado.web.Application( [(r‘/‘, IndexHandler)], static_path=os.path.join(os.path.dirname(__file__), "statics"), )
在這裏,我們設置了一個當前應用目錄下名為statics的子目錄作為static_path的參數。現在應用將以讀取statics目錄下的filename.ext來響應諸如/static/filename.ext的請求,並在響應的主體中返回。
對於靜態文件目錄的命名,為了便於部署,建議使用static
對於我們提供的靜態文件資源,可以通過http://127.0.0.1/static/html/index.html
來訪問。而且在index.html中引用的靜態資源文件,我們給定的路徑也符合/static/...的格式,故頁面可以正常瀏覽。
<link href="/static/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/plugins/font-awesome/css/font-awesome.min.css" rel="stylesheet"> <link href="/static/css/reset.css" rel="stylesheet"> <link href="/static/css/main.css" rel="stylesheet"> <link href="/static/css/index.css" rel="stylesheet"> <script src="/static/js/jquery.min.js"></script> <script src="/static/plugins/bootstrap/js/bootstrap.min.js"></script> <script src="/static/js/index.js"></script>
StaticFileHandler
我們再看剛剛訪問頁面時使用的路徑http://127.0.0.1/static/html/index.html
,這中url顯然對用戶是不友好的,訪問很不方便。我們可以通過tornado.web.StaticFileHandler來自由映射靜態文件與其訪問路徑url。
tornado.web.StaticFileHandler是tornado預置的用來提供靜態資源文件的handler。
import os current_path = os.path.dirname(__file__) app = tornado.web.Application( [ (r‘^/()$‘, StaticFileHandler, {"path":os.path.join(current_path, "statics/html"), "default_filename":"index.html"}), (r‘^/view/(.*)$‘, StaticFileHandler, {"path":os.path.join(current_path, "statics/html")}), ], static_path=os.path.join(current_path, "statics"), )
- path 用來指明提供靜態文件的根路徑,並在此目錄中尋找在路由中用正則表達式提取的文件名。
- default_filename 用來指定訪問路由中未指明文件名時,默認提供的文件。
現在,對於靜態文件statics/html/index.html,可以通過三種方式進行訪問:
- http://127.0.0.1/static/html/index.html
- http://127.0.0.1/
- http://127.0.0.1/view/index.html
4.2 使用模板
1. 路徑與渲染
使用模板,需要仿照靜態文件路徑設置一樣,向web.Application類的構造函數傳遞一個名為template_path的參數來告訴Tornado從文件系統的一個特定位置提供模板文件,如:
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"), )
在這裏,我們設置了一個當前應用目錄下名為templates的子目錄作為template_path的參數。在handler中使用的模板將在此目錄中尋找。
現在我們將靜態文件目錄statics/html中的index.html復制一份到templates目錄中,此時文件目錄結構為:
. ├── 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()方法來渲染模板並返回給客戶端。
class IndexHandler(RequestHandler): def get(self): self.render("index.html") # 渲染主頁模板,並返回給客戶端。 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. 模板語法
2-1 變量與表達式
在tornado的模板中使用{{}}作為變量或表達式的占位符,使用render渲染後占位符{{}}會被替換為相應的結果值。
我們將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平大空間+文化保護區雙地鐵</span> <em>整套出租 - 5分/6點評 - 北京市豐臺區六裏橋地鐵</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}}點評 - {{position}}</em> </div> </div> </li>
渲染方式如下:
class IndexHandler(RequestHandler): def get(self): house_info = { "price": 398, "title": "寬窄巷子+160平大空間+文化保護區雙地鐵", "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" } self.render("index.html", **house_info)
{{}}不僅可以包含變量,還可以是表達式,如:
<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}}點評 - {{position}}</em> </div> </div> </li> class IndexHandler(RequestHandler): def get(self): house_info = { "p1": 198, "p2": 200, "titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"], "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" } self.render("index.html", **house_info)
2-2 控制語句
可以在Tornado模板中使用Python條件和循環語句。控制語句以{\%和\%}包圍,並以類似下面的形式被使用:
{% if page is None %}
或
{% if len(entries) == 3 %}
控制語句的大部分就像對應的Python語句一樣工作,支持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"]}}點評 - {{house["position"]}}</em> </div> </div> </li> {% end %} {% else %} 對不起,暫時沒有房源。 {% end %} </ul>
python中渲染語句為:
class IndexHandler(RequestHandler): def get(self): houses = [ { "price": 398, "title": "寬窄巷子+160平大空間+文化保護區雙地鐵", "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" }, { "price": 398, "title": "寬窄巷子+160平大空間+文化保護區雙地鐵", "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" }, { "price": 398, "title": "寬窄巷子+160平大空間+文化保護區雙地鐵", "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" }, { "price": 398, "title": "寬窄巷子+160平大空間+文化保護區雙地鐵", "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" }, { "price": 398, "title": "寬窄巷子+160平大空間+文化保護區雙地鐵", "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" }] self.render("index.html", houses=houses)
2-3 函數
static_url()
Tornado模板模塊提供了一個叫作static_url的函數來生成靜態文件目錄下文件的URL。如下面的示例代碼:
<link rel="stylesheet" href="{{ static_url("style.css") }}">
這個對static_url的調用生成了URL的值,並渲染輸出類似下面的代碼:
<link rel="stylesheet" href="/static/style.css?v=ab12">
優點:
- static_url函數創建了一個基於文件內容的hash值,並將其添加到URL末尾(查詢字符串的參數v)。這個hash值確保瀏覽器總是加載一個文件的最新版而不是之前的緩存版本。無論是在你應用的開發階段,還是在部署到生產環境使用時,都非常有用,因為你的用戶不必再為了看到你的靜態內容而清除瀏覽器緩存了。
- 另一個好處是你可以改變你應用URL的結構,而不需要改變模板中的代碼。例如,可以通過設置static_url_prefix來更改Tornado的默認靜態路徑前綴/static。如果使用static_url而不是硬編碼的話,代碼不需要改變。
轉義
我們新建一個表單頁面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>
對應的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)
當我們在表單中填入如下內容時:
<script>alert("hello!");</script>
寫入的js程序並沒有運行,而是顯示出來了:
我們查看頁面源代碼,發現<、>、"等被轉換為對應的html字符。
<script>alert("hello!");</script>
這是因為tornado中默認開啟了模板自動轉義功能,防止網站受到惡意攻擊。
我們可以通過raw語句來輸出不被轉義的原始格式,如:
{% raw text %}
註意:在Firefox瀏覽器中會直接彈出alert窗口,而在Chrome瀏覽器中,需要set_header("X-XSS-Protection", 0)
若要關閉自動轉義,一種方法是在Application構造函數中傳遞autoescape=None,另一種方法是在每頁模板中修改自動轉義行為,添加如下語句:
{% autoescape None %}
escape()
關閉自動轉義後,可以使用escape()函數來對特定變量進行轉義,如:
{{ escape(text) }}
自定義函數
在模板中還可以使用一個自己編寫的函數,只需要將函數名作為模板的參數傳遞即可,就像其他變量一樣。
我們修改後端如下:
def house_title_join(titles): return "+".join(titles) class IndexHandler(RequestHandler): def get(self): house_list = [ { "price": 398, "titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"], "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" }, { "price": 398, "titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"], "score": 5, "comments": 6, "position": "北京市豐臺區六裏橋地鐵" }] 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"]}}點評 - {{house["position"]}}</em> </div> </div> </li> {% end %} {% else %} 對不起,暫時沒有房源。 {% end %} </ul>
2-4 塊
我們可以使用塊來復用模板,塊語法如下:
{% block block_name %} {% end %}
現在,我們對模板index.html進行抽象,抽離出父模板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來使用父模板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"]}}點評 - {{house["position"]}}</em> </div> </div> </li> {% end %} {% else %} 對不起,暫時沒有房源。 {% end %} </ul> {% end %} {% block footer %} <p><span><i class="fa fa-copyright"></i></span>愛家租房 享受家的溫馨</p> {% end %}
4.3 練習
-
對比Django模板與Tornado模板的異同。
-
練習使用Tornado模板的語法。
4.3 練習
-
對比Django模板與Tornado模板的異同。
-
練習使用Tornado模板的語法。
Tornado之模板