1. 程式人生 > >Tornado之模板

Tornado之模板

python alt 子目錄 參數 應用 itl 部分 ren bsp

知識點

  • 靜態文件配置
    • 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,可以通過三種方式進行訪問:

  1. http://127.0.0.1/static/html/index.html
  2. http://127.0.0.1/
  3. 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字符。

&lt;script&gt;alert(&quot;hello!&quot;);&lt;/script&gt;

這是因為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>愛家租房&nbsp;&nbsp;享受家的溫馨</p>
{% end %}

4.3 練習

  1. 對比Django模板與Tornado模板的異同。

  2. 練習使用Tornado模板的語法。

4.3 練習

  1. 對比Django模板與Tornado模板的異同。

  2. 練習使用Tornado模板的語法。

Tornado之模板