[python][flask] Jinja 模板入門

Flask 和 Django 附帶了強大的 Jinja 模板語言。

對於之前沒有接觸過模板語言的人來說,這類語言基本上就是包含一些變量,當準備渲染呈現 HTML 時,它們會被實際的值替換。

這些變量放在標記或分隔符之前。例如:Jinja 模板使用 {% ... %} 表示循環,{{ ... }} 表示一個表達式運算結果返回。

Jinja 模板其實是 html 文件。一般情況下放在 Flask 工程的 /templates 目錄下

1、快速體驗

跑下面的各種 demo 之前,確保你已經安裝了 Jinja (pip install jinja2)

>>> from jinja2 import Template
>>> t = Template("Hello {{ something }}!")
>>> t.render(something="World")
u'Hello World!'

>>> t = Template("My favorite numbers: {% for n in range(1,10) %}{{n}} " "{% endfor %}")
>>> t.render()
u'My favorite numbers: 1 2 3 4 5 6 7 8 9 '

這個 demo 展示了模板中的變量(表達式)是如何最終被替換和渲染的。

2、Flask 最小 DEMO

整個的參考代碼可以在這裡獲得:HERE

不過博主建議按照下面步驟一步步來:

1)安裝 flask

➜  pip install flask

2)創建工程目錄結構:

➜  mkdir flask_example
➜  cd flask_example 
➜  mkdir templates
➜  cd ..
➜  touch run.py
➜  touch requirements.txt

3)編寫 run.py

from flask import Flask, render_template
app = Flask(__name__)


@app.route("/")
def template_test():
    return render_template('template.html', my_string="Wheeeee!", my_list=[0,1,2,3,4,5])


if __name__ == '__main__':
    app.run(debug=True)

這裡,我們創建了一個 / 路由,當我們訪問服務器根路由時,會通過 render_templatetemplate.html 渲染,其中 my_stringmy_list 就是準備傳給模板的實際的值。

4)編寫 template.html 模板

在 templates 目錄下,創建一個 template.html:

<!DOCTYPE html>
<html>
  <head>
    <title>Flask Template Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
    <style type="text/css">
      .container {
        max-width: 500px;
        padding-top: 100px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <p>My string: {{my_string}}</p>
      <p>Value from the list: {{my_list[3]}}</p>
      <p>Loop through the list:</p>
      <ul>
        {% for n in my_list %}
        <li>{{n}}</li>
        {% endfor %}
      </ul>
    </div>
    <script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
  </body>
</html>

5)運行觀察效果

➜  python run.py

效果如下:

可以看到,將模板中的 my_string、my_list[3] 替換掉了,並且用 for 循環語句,生成了一個 list。

3、模板繼承

模板通常利用繼承,繼承包括定義所有後續子模板基本結構的單個基礎模板。您可以使用標記 {% extends %}{% block %} 來實現繼承。

這樣做的用例很簡單:隨着應用程序的增長,以及您繼續添加新模板,您將需要保持公共代碼(如HTML導航欄、Javascript庫、CSS樣式表等)同步,這可能需要大量工作。使用繼承,我們可以將這些公共部分移動到父/基模板,這樣我們就可以創建或編輯這樣的代碼一次,所有子模板都將繼承該代碼。

注意:您應該總是儘可能多地向基本模板添加重複代碼,以節省將來的時間,這將遠遠超過初始時間投資。

讓我們給我們的 DEMO 增加模板:

1)創建基礎模板(保存為 layout.html

<!DOCTYPE html>
<html>
  <head>
    <title>Flask Template Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
    <style type="text/css">
      .container {
        max-width: 500px;
        padding-top: 100px;
      }
      h2 {color: red;}
    </style>
  </head>
  <body>
    <div class="container">
      <h2>This is part of my base template</h2>
      <br>
      {% block content %}{% endblock %}
      <br>
      <h2>This is part of my base template</h2>
    </div>
    <script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
  </body>
</html>

你注意到 {%block%} 標記了嗎?這定義了子模板可以填充的塊或區域。此外,也可實現覆蓋的作用。

2)用模板更新 template.html:

{% extends "layout.html" %}
{% block content %}
  <h3> This is the start of my child template</h3>
  <br>
  <p>My string: {{my_string}}</p>
  <p>Value from the list: {{my_list[3]}}</p>
  <p>Loop through the list:</p>
  <ul>
    {% for n in my_list %}
    <li>{{n}}</li>
    {% endfor %}
  </ul>
  <h3> This is the end of my child template</h3>
{% endblock %}

這樣 layout.html 模板中的 content 塊就會被 template.html 中的新定義給替換掉,最終效果如下:

那麼,我們就可以通過修改 layout.html 給其添加通用導航欄了:(將下列代碼插入到 layout.html<body> 標籤之後)

<nav class="navbar navbar-inverse" role="navigation">
  <div class="container-fluid">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/">Jinja!</a>
    </div>

    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link</a></li>
        <li><a href="#">Link</a></li>
      </ul>
      <form class="navbar-form navbar-left" role="search">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>

現在,從基礎擴展的每個子模板都將具有相同的導航欄。借用Java哲學的一句話:”Write once, use anywhere.”

4、Super Blocks

如果需要從基礎模板渲染塊,使用 super block:

{{ super() }}

給基礎模板增加一個頁腳:

<body>
<div class="container">
 ...
  <h2>This is part of my base template</h2>
  <br>
  <div class="footer">
    {% block footer %}
      Watch! This will be added to my base and child templates using the super powerful super block!
      <br>
      <br>
      <br>
    {% endblock %}
  </div>
</div>
...

此時,我們可以給 template.html 增加 super block,從而實現子模板復用父模板中的塊:

{% extends "layout.html" %}
{% block content %}
  <h3> This is the start of my child template</h3>
  <br>
  <p>My string: {{my_string}}</p>
  <p>Value from the list: {{my_list[3]}}</p>
  <p>Loop through the list:</p>
  <ul>
    {% for n in my_list %}
    <li>{{n}}</li>
    {% endfor %}
  </ul>
  <h3> This is the end of my child template</h3>
  {% block footer %}
  {{super()}}
  {% endblock %}
{% endblock %}

效果如下:

super block 用於模塊共享父模塊的 block,當然還有一些高級玩法,比如下面的例子:

父模板:

{% block heading %}
  <h1>{% block page %}{% endblock %} - Flask Super Example</h1>
{% endblock %}

子模板:

{% block page %}Home{% endblock %}
{% block heading %}
  {{ super() }}
{% endblock %}

這樣當訪問子模塊時,會拼接一個 <h1>Home - Flask Super Example</h1> 字段。發現沒,我們通過這樣的方法,實現了標題的繼承(有一定的繼承,也有一定的子模塊自己的信息)。

回歸正軌,對於更新標題,我們這裡這樣設計(修改 template.html 中的兩行代碼)

{% block title %}{{title}}{% endblock %}
...
{% block page %}{{title}}{% endblock %}

這樣我們可以通過 python 進來直接修改標題了(修改 run.py):

@app.route("/")
def template_test():
    return render_template(
        'template.html', my_string="Wheeeee!", 
        my_list=[0,1,2,3,4,5], title="Home")

5、Macros

在 Jinja 中,我們可以使用宏來抽象常用的代碼段,這些代碼段被反覆使用以避免重複。例如,通常會在導航欄上突出顯示當前頁面的鏈接(活動鏈接)。否則,我們必須使用 if/elif/else 語句來確定活動鏈接。使用宏,我們可以將這些代碼抽象成一個單獨的文件。

新增一個 macros.html 文件:

{% macro nav_link(endpoint, name) %}
{% if request.endpoint.endswith(endpoint) %}
  <li class="active"><a href="{{ url_for(endpoint) }}">{{name}}</a></li>
{% else %}
  <li><a href="{{ url_for(endpoint) }}">{{name}}</a></li>
{% endif %}
{% endmacro %}

這裡,我們使用了 Flask 的 request object(Jinja 的默認一部分),用來檢查請求端點,然後將活動 class 分配給該端點。

使用基礎模板中的nav navbar nav類更新無序列表:

<ul class="nav navbar-nav">
  {{ nav_link('home', 'Home') }}
  {{ nav_link('about', 'About') }}
  {{ nav_link('contact', 'Contact Us') }}
</ul>

此外,請確保在模板頂部添加導入:{% from "macros.html" import nav_link with context %}

最後,讓我們向控制器添加三個新端點:

@app.route("/home")
def home():
    return render_template(
        'template.html', my_string="Wheeeee!", 
        my_list=[0,1,2,3,4,5], title="Home")

@app.route("/about")
def about():
    return render_template(
        'template.html', my_string="Wheeeee!", 
        my_list=[0,1,2,3,4,5], title="About")

@app.route("/contact")
def contact():
    return render_template(
        'template.html', my_string="Wheeeee!", 
        my_list=[0,1,2,3,4,5], title="Contact Us")

刷新頁面。測試頂部的鏈接。當前頁面是否突出顯示?(每次點擊 Home, About, Contact Us,瀏覽器會自動跳轉到對應的 url,並加載頁面)

6、自定義過濾器

Jinja 使用過濾器修改變量,主要用于格式化目的。

這有個例子;

{{ num | round }}

這將使 num 變量四捨五入。因此,如果我們將參數 num=46.99 傳遞到模板中,那麼將輸出47.0。(把大括號中的語句當做 shell,就明白了,豎線是傳遞作用,round是個過濾器,這裡是所有的過濾器)

再來個例子:

{{ list|join(', ') }}

可以給 list 數組中的變量加個逗號。

其實,除了自帶的過濾器,我們也可以自定義:

1)在 run.py 的所有函數前增加 app = Flask(__name__) 用於創建一個 app
2)增加一個 datetimefilter 函數,並將其註冊到 app 的過濾器

@app.template_filter() # 聲明,這是個過濾器
def datetimefilter(value, format='%Y/%m/%d %H:%M'):
    """Convert a datetime to a different format."""
    return value.strftime(format)

app.jinja_env.filters['datetimefilter'] = datetimefilter

3)這樣,我們在子模板中插入如下代碼:

<h4>Current date/time: {{ current_time | datetimefilter }}</h4>

4)最後,只要在 python 中將時間傳入模板即可:

current_time = datetime.datetime.now()

5)效果如下:

7、結論

這樣,就送大家快速入門了 Jinja,源碼://github.com/mjhea0/thinkful-mentor/tree/master/python/jinja/flask_example

參考鏈接

[1]. 本文源碼
[2]. Primer on Jinja Templating(本文翻譯並參考這篇)
[3]. Flask 官方文檔
[4]. 真正搞明白Python中Django和Flask框架的區別
[5]. Flask 主頁
[6]. 一個 Soft UI Dashboard – Free Jinja Template
[7]. Appseed 這個網站有很多 Flask 模板
[8]. Nginx 服務器 SSL 證書安裝部署
[9]. python django web 開發 —— 15分鐘送到會用(只能送你到這了)


: 在學習 Django 和 Flask 等 Python 的服務器框架時,都需要了解模板的概念,這篇能夠快速帶你入門…