Flask默認使用Jinja2作爲模版引擎。模版引擎包含了變量和表達式,當模版被渲染時,它們被替換爲值和標籤,它們控制着模版的邏輯。下面主要回顧模版的渲染使用,以及Jinja2的語法。
1 模版渲染
1.1 目錄結構
Flask 會在 templates 文件夾裏尋找模板。所以,如果你的應用是個模塊,這個文件夾應該與模塊同級;如果它是一個包,那麼這個文件夾作爲包的子目錄:
場景1: 模塊
application文件應該與templates目錄同級
/application.py
/templates
/hello.html
場景2: 包
templates目錄作爲application的子目錄
/application
/__init__.py
/templates
/hello.html
1.2 渲染
Flask使用 render_template() 方法來渲染模板。渲染時框架會自動尋找templates目錄下的網頁文件,不必添加"templates"這個路徑。
render_template()方法第一個參數爲html文件名,後續參數爲模版文件中的變量名,可支持字典類型變量。
在模板裏,也可以訪問 request 、 session 和 g 對象, 以及 get_flashed_messages() 函數。
用法示例
文件目錄結構:
html文件user.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask首頁</title>
</head>
<body>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello world!</h1>
{% endif %}
</body>
</html>
模版渲染代碼manager.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/user/")
@app.route("/user/<name>")
def index(name=None):
return render_template("user.html", name=name)
if __name__ == '__main__':
app.run(debug=True)
在瀏覽器中輸入URL:http://127.0.0.1:5000/user/bruce
頁面顯示如下
1.3 字典類型變量
當模版中變量比較多時,可以直接使用字典類型變量。
修改user.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask首頁</title>
</head>
<body>
{% if name %}
<h1>Hello {{ name }}!</h1>
<h2>user age: {{age}}, phone: {{phone}}!</h2>
{% else %}
<h1>Hello world!</h1>
{% endif %}
</body>
</html>
修改渲染代碼:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/user/")
@app.route("/user/<name>")
def index(name=None):
info = {
"name": name,
"age": 33,
"phone": 123456789
}
return render_template("user.html", **info)
if __name__ == '__main__':
app.run(debug=True)
瀏覽器輸入URL:http://127.0.0.1:5000/user/bruce.xu
2 Flask變量傳遞
Jinja 2 默認配置如下:
所有擴展名爲 .html 、 .htm 、 .xml 以及 .xhtml 的模板會開啓自動轉義
模板可以利用 {% autoescape %} 標籤選擇自動轉義的開關。
Flask 在 Jinja2 上下文中插入了幾個全局函數和助手,另外還有一些目前默認的值。
Flask全局變量
下面的全局變量默認在 Jinja2 模板中可用
變量 | 說明 |
---|---|
config | 當前的配置對象 (flask.config) |
request | 當前的請求對象 (flask.request)。當模版不是在活動的請求上下文中渲染時這個變量不可用。 |
session | 當前的會話對象 (flask.session)。當模版不是在活動的請求上下文中渲染時這個變量不可用。 |
g | 請求相關的全局變量 (flask.g)。當模版不是在活動的請求上下文中渲染時這個變量不可用。 |
url_for() | flask.url_for() 函數 |
get_flashed_messages() | flask.get_flashed_messages() 函數 |
(1)請求對象request
request對象可以用來獲取請求的方法”request.method”,表單”request.form”,請求的參數”request.args”,請求地址”request.url”等。它本身是一個字典。在模板中,你一樣可以獲取這些內容,只要用表達式符號”{{ }}”括起來即可。
<p>{{request.url}}</p>
(2)會話對象session
session對象可以用來獲取當前會話中保存的狀態,它本身是一個字典。在模板中,你可以用表達式符號”{{ }}”來獲取這個對象。
在request對象中設置session變量
@app.route('/')
def index():
session['user']='guest'
returnrender_template('hello.html')
app.secret_key='123456'
然後在html中可以使用session變量
<p>User:{{session.user}}</p>
(3)全局對象g
全局變量g,用來保存請求中會用到全局內容,比如數據庫連接。模板中也可以訪問。
g對象是保存在應用上下文環境中的,也只在一個請求生命週期內有效。在沒有應用上下文的環境中,這個對象不可用。
Flask配置對象config
@app.route('/')
def index():
g.db='mysql'
returnrender_template('hello.html')
在模版中使用:
<p>DB:{{g.db}}</p>
(4)Flask配置對象config
導入的配置信息,就保存在”app.config”對象中。這個配置對象在模板中也可以訪問。
“config”是全局對象,離開了請求生命週期也可以訪問。
<p>Host:{{config.DEBUG}}</p>
Flask自定義變量
除了Flask提供的標準上下文變量和函數,我們還可以自己定義。
from flask import current_app
@app.context_processor
def appinfo():
return dict(appname=current_app.name)
函數返回的是一個字典,裏面有一個屬性”appname”,值爲當前應用的名稱。我們曾經介紹過,這裏的”current_app”對象是一個定義在應用上下文中的代理。函數用”@app.context_processor”裝飾器修飾,它是一個上下文處理器,它的作用是在模板被渲染前運行其所修飾的函數,並將函數返回的字典導入到模板上下文環境中,與模板上下文合併。然後,在模板中”appname”就如同上節介紹的”request”, “session”一樣,成爲了可訪問的上下文對象。我們可以在模板中將其輸出:
<p>Current App is:{{appname}}</p>
Flask自定義函數
同理我們可以自定義上下文函數,只需將上例中返回字典的屬性指向一個函數即可,下面我們就來定義一個上下文函數來獲取系統當前時間:
import time
@app.context_processor
def get_current_time():
def get_time(timeFormat="%b %d, %Y - %H:%M:%S"):
return time.strftime(timeFormat)
return dict(current_time=get_time)
在模版中使用:
<p>Current Time is:{{current_time()}}</p>
<p>Current Day is:{{current_time("%Y-%m-%d")}}</p>
上下文處理器
Flask 上下文處理器自動向模板的上下文中插入新變量。上下文處理器在模板渲染之前運行,並且可以在模板上下文中插入新值。上下文處理器是一個返回字典的函數,這個字典的鍵值最終將傳入應用中所有模板的上下文:
@app.context_processor
def inject_user():
return dict(user=g.user)
上面的上下文處理器使得模板可以使用一個名爲 user ,值爲 g.user 的變量。 不過這個例子不是很有意思,因爲 g 在模板中本來就是可用的,但它解釋了上下文處理器是如何工作的。
變量不僅限於值,上下文處理器也可以使某個函數在模板中可用(由於 Python 允許傳遞函數):
@app.context_processor
def utility_processor():
def format_price(amount, currency=u'€'):
return u'{0:.2f}{1}.format(amount, currency)
return dict(format_price=format_price)
上面的上下文處理器使得 format_price 函數在所有模板中可用:
{{ format_price(0.33) }}
標準過濾器
過濾器的本質就是函數。有時候我們不僅僅只是需要輸出變量的值,我們還需要修改變量的顯示,甚至格式化、運算等等,這就用到了過濾器。 過濾器的使用方式爲:變量名 | 過濾器。 過濾器名寫在變量名後面,中間用 | 分隔。如:{{variable | capitalize}},這個過濾器的作用:把變量variable的值的首字母轉換爲大寫,其他字母轉換爲小寫。
這些過濾器在 Jinja2 中可用,也是 Jinja2 自帶的過濾器:
tojson()
這個函數把給定的對象轉換爲 JSON 表示,如果你要動態生成 JavaScript 這裏有一個非常有用的例子。
注意 script 標籤裏的東西不應該被轉義,因此如果你想在 script 標籤裏使用它, 請使用 |safe 來禁用轉義,:
<script type=text/javascript>
doSomethingWith({{ user.username|tojson|safe }});
</script>
常用過濾器
字符串操作:
safe:禁用轉義;
{{ 'hello' | safe }}
capitalize:把變量值的首字母轉成大寫,其餘字母轉小寫;
{{ 'hello' | capitalize }}
lower:把值轉成小寫;
{{ 'HELLO' | lower }}
upper:把值轉成大寫;
{{ 'hello' | upper }}
title:把值中的每個單詞的首字母都轉成大寫;
{{ 'hello' | title }}
trim:把值的首尾空格去掉;
{{ ' hello world ' | trim }}
reverse:字符串反轉;
{{ 'olleh' | reverse }}
format:格式化輸出;
{{ '%s is %d' | format('name',17) }}
striptags:渲染之前把值中所有的HTML標籤都刪掉;
{{ 'hello' | striptags }}
列表操作
first:取第一個元素
{{ [1,2,3,4,5,6] | first }}
last:取最後一個元素
{{ [1,2,3,4,5,6] | last }}
length:獲取列表長度
{{ [1,2,3,4,5,6] | length }}
sum:列表求和
{{ [1,2,3,4,5,6] | sum }}
sort:列表排序
{{ [6,2,3,1,5,4] | sort }}
控制自動轉義
自動轉義的概念是自動轉義特殊字符。 HTML (或 XML ,因此也有 XHTML )意義下的特殊字符是 & , > , < , " 以及 ’ 。因爲這些字符在文檔中表示它們特定的含義,如果你想在文本中使用它們,應該把它們替換成相應的“實體”。不這麼做不僅會導致用戶疲於在文本中使用這些字符,也會導致安全問題。
雖然你有時會需要在模板中禁用自動轉義,比如在頁面中顯式地插入 HTML , 可以是一個來自於 markdown 到 HTML 轉換器的安全輸出。
有三種可行的解決方案:
- 在傳遞到模板之前,用 Markup 對象封裝 HTML字符串。一般推薦這個方法。
- 在模板中,使用 |safe 過濾器顯式地標記一個字符串爲安全的 HTML ( {{ myvariable|safe }} )。
- 臨時地完全禁用自動轉義系統。
- 在模板中禁用自動轉義系統,可以使用 {%autoescape %} 塊:
{% autoescape false %}
<p>autoescaping is disabled here
<p>{{ will_not_be_escaped }}
{% endautoescape %}
註冊過濾器
如果你要在 Jinja2 中註冊你自己的過濾器,你有兩種方法。你可以把它們手動添加到應用的 jinja_env 或者使用 template_filter() 裝飾器。
下面兩個例子作用相同,都是反轉一個對象:
@app.template_filter('reverse')
def reverse_filter(s):
return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter
在使用裝飾器的情況下,如果你想以函數名作爲過濾器名,參數是可選的。註冊之後, 你可以在模板中像使用 Jinja2 內置過濾器一樣使用你的過濾器,例如你在上下文中有一個名爲 mylist 的 Python 列表:
{% for x in mylist | reverse %}
{% endfor %}
3 Jinja2模版語法
3.1 模版內部變量
可以使用點( . )來訪問變量的屬性,作爲替代,也可以使用所謂的“下標”語 法( [])。下面的幾行效果是一樣的:
{{ foo.bar }}
{{ foo['bar'] }}
如果你訪問標籤 裏的不帶括號的變量。
如果變量或屬性不存在,會返回一個未定義值。你可以對這類值做什麼取決於應用的配 置,默認的行爲是它如果被打印,其求值爲一個空字符串,並且你可以迭代它,但其它 操作會失敗。
爲方便起見,Jinja2 中 foo.bar 在 Python 層中做下面的事情:
檢查 foo 上是否有一個名爲 bar 的屬性。
如果沒有,檢查 foo 中是否有一個 ‘bar’ 項 。
如果沒有,返回一個未定義對象。
foo[‘bar’] 的方式相反,只在順序上有細小差異:
檢查在 foo 中是否有一個 ‘bar’ 項。
如果沒有,檢查 foo 上是否有一個名爲 bar 的屬性。
如果沒有,返回一個未定義對象。
如果一個對象有同名的項和屬性,這很重要。此外,有一個 attr() 過濾 器,它只查找屬性。
3.2 註釋和空白控制
註釋
{# … #} 對於未包含在模板輸出中的註釋
#。。。##註釋行語句
空白控制
默認配置中,模板引擎不會對空白做進一步修改,所以每個空白(空格、製表符、換行符 等等)都會原封不動返回。如果應用配置了 Jinja 的 trim_blocks ,模板標籤後的 第一個換行符會被自動移除。
此外,你也可以手動剝離模板中的空白。當你在塊(比如一個 for 標籤、一段註釋或變 量表達式)的開始或結束放置一個減號( - ),可以移除塊前或塊後的空白:
{% for item in seq -%}
{{ item }}
{%- endfor %}
這會產出中間不帶空白的所有元素。如果 seq 是 1 到 9 的數字的列表, 輸出會是123456789
如果開啓了 行語句 ,它們會自動去除行首的空白。
注意:標籤和減號之間不能有空白。
有效的:
{%- if foo -%}...{% endif %}
轉義
有時想要或甚至必要讓 Jinja 忽略部分,不會把它作爲變量或塊來處理。例如,如果 使用默認語法,你想在在使用把 {{ 作爲原始字符串使用,並且不會開始一個變量 的語法結構,你需要使用一個技巧。
最簡單的方法是在變量分隔符中( {{ )使用變量表達式輸出:
{{ '{{' }}
對於較大的段落,標記一個塊爲 raw 是有意義的。例如展示 Jinja 語法的實例, 你可以在模板中用這個片段:
{% raw %}
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endraw %}
3.3 控制結構
控制結構指的是所有的那些可以控制程序流的東西 —— 條件(比如 if/elif/ekse )、 for 循環、以及宏和塊之類的東西。控制結構在默認語法中以 {% … %} 塊的形式 出現。
for循環
遍歷序列中的每項。例如,要顯示一個由 users` 變量提供的用戶列表:
<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
因爲模板中的變量保留它們的對象屬性,可以迭代像 dict 的容器:
<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>
注意:無論如何字典通常是無序的,所以你可能需要把它作爲一個已排序的列表傳入 到模板或使用 dictsort 過濾器。
在一個 for 循環塊中你可以訪問這些特殊的變量:
變量 | 描述 |
---|---|
loop.index | 當前循環迭代的次數(從 1 開始) |
loop.index0 | 當前循環迭代的次數(從 0 開始) |
loop.revindex | 到循環結束需要迭代的次數(從 1 開始) |
loop.revindex0 | 到循環結束需要迭代的次數(從 0 開始) |
loop.first | 如果是第一次迭代,爲 True 。 |
loop.last | 如果是最後一次迭代,爲 True 。 |
loop.length | 序列中的項目數。 |
loop.cycle | 在一串序列間期取值的輔助函數。見下面的解釋。 |
在 for 循環中,可以使用特殊的 loop.cycle 輔助函數,伴隨循環在一個字符串/變 量列表中週期取值:
{% for row in rows %}
<li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
{% endfor %}
如果因序列是空或者過濾移除了序列中的所有項目而沒有執行循環,你可以使用 else 渲染一個用於替換的塊:
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% else %}
<li><em>no users found</em></li>
{% endfor %}
</ul>
if條件判斷
Jinja 中的 if 語句可比 Python 中的 if 語句。在最簡單的形式中,你可以測試 一個變量是否未定義,爲空或 false:
{% if kenny.sick %}
Kenny is sick.
{% elif kenny.dead %}
You killed Kenny! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
宏
宏類似常規編程語言中的函數。它們用於把常用行爲作爲可重用的函數,取代 手動重複的工作。
這裏是一個宏渲染表單元素的小例子:
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{
value|e }}" size="{{ size }}">
{%- endmacro %}
在命名空間中,宏之後可以像函數一樣調用:
<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>
在宏內部,你可以訪問三個特殊的變量:
變量 | 說明 |
---|---|
varargs | 如果有多於宏接受的參數個數的位置參數被傳入,它們會作爲列表的值保存在 varargs 變量上。 |
kwargs | 同 varargs ,但只針對關鍵字參數。所有未使用的關鍵字參數會存儲在 這個特殊變量中。 |
caller | 如果宏通過 call 標籤調用,調用者會作爲可調用的宏被存儲在這個 變量中。 |
宏也可以暴露某些內部細節。下面的宏對象屬性是可用的:
屬性 | 說明 |
---|---|
name | 宏的名稱。 {{ input.name }} 會打印 input 。 |
arguments | 一個宏接受的參數名的元組。 |
defaults | 默認值的元組。 |
catch_kwargs | 如果宏接受額外的關鍵字參數(也就是訪問特殊的 kwargs 變量),爲 true 。 |
catch_varargs | 如果宏接受額外的位置參數(也就是訪問特殊的 varargs 變量),爲 true 。 |
caller | 如果宏訪問特殊的 caller 變量且由 call 標籤調用,爲 true 。如果一個宏的名稱以下劃線開始,它不是導出的且不能被導入。 |
用法示例:
可以把宏單獨抽取出來,封裝成html文件,其它模板中導入使用。
如:文件名可以自定義mymacro.html
#單獨的html宏
{% macro function() %}
<input type="text" name="username" placeholde="Username">
<input type="password" name="password" placeholde="Password">
<input type="submit">
{% endmacro %}
#在其它模板文件中先導入,再調用
{% import 'macro.html' as func %}
{% func.function() %}
宏提供了一種模塊化代碼的方式:
(1)在macro.html中定義宏函數
{# myapp/templates/macros.html #}
{% macro nav_link(endpoint, text) %}
{% if request.endpoint.endswith(endpoint) %}
<li class="active"><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% else %}
<li><a href="{{ url_for(endpoint) }}">{{text}}</a></li>
{% endif %}
{% endmacro %}
(2)在其他html文件中導入宏,然後使用
{# myapp/templates/layout.html #}
{% from "macros.html" import nav_link with context %}
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<title>My application</title>
{% endblock %}
</head>
<body>
<ul class="nav-list">
{{ nav_link('home', 'Home') }}
{{ nav_link('about', 'About') }}
{{ nav_link('contact', 'Get in touch') }}
</ul>
{% block body %}
{% endblock %}
</body>
</html>
注意:
- 從 x 導入 y 語句採用了 x 的相對路徑。如果我們的模板是 myapp/templates/user/blog.html,我們可以在使用 from “…/macros.html” 導入 nav_link。
- 當我們說 {% from … import … with context %} 的時候,就是告訴 Jinja 這些變量對宏也可用。
宏調用
在某些情況下,需要把一個宏傳遞到另一個宏。爲此,可以使用特殊的 call 塊。 下面的例子展示瞭如何讓宏利用調用功能:
{% macro render_dialog(title, class='dialog') -%}
<div class="{{ class }}">
<h2>{{ title }}</h2>
<div class="contents">
{{ caller() }}
</div>
</div>
{%- endmacro %}
{% call render_dialog('Hello World') %}
This is a simple dialog rendered by using a macro and
a call block.
{% endcall %}
過濾器
過濾器段允許你在一塊模板數據上應用常規 Jinja2 過濾器。只需要把代碼用 filter 節包裹起來:
{% filter upper %}
This text becomes uppercase
{% endfilter %}
賦值
在代碼塊中,你也可以爲變量賦值。在頂層的(塊、宏、循環之外)賦值是可導出的,即 可以從別的模板中導入。
賦值使用 set 標籤,並且可以爲多個變量賦值:
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}
繼承
模板繼承是爲了重用模板中的公共內容。一般Web開發中,繼承主要使用在網站的頂部菜單、底部。這些內容可以定義在父模板中,子模板直接繼承,而不需要重複書寫。
{% block top %}``{% endblock %}標籤定義的內容,相當於在父模板中挖個坑,當子模板繼承父模板時,可以進行填充。
子模板使用extends指令聲明這個模板繼承自哪。父模板中定義的塊在子模板中被重新定義,在子模板中調用父模板的內容可以使用super()。
extends 標籤用於從另一個模板繼承。你可以在一個文件中使用多次繼承,但是 只會執行其中的一個。
注意:
- 不支持多繼承。
- 爲了便於閱讀,在子模板中使用extends時,儘量寫在模板的第一行。
- 不能在一個模板文件中定義多個相同名字的block標籤。
- 當在頁面中使用多個block標籤時,建議給結束標籤起個名字,當多個block嵌套時,閱讀性更好。
#父模版定義
{% block top %}
頂部菜單
{% endblock top %}
{% block content %}
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
#子模版使用,先繼承,重寫對應block部分
{% extends 'base.html' %}
{% block content %}
需要填充的內容
{% endblock content %}
常用模版設計模式:
父模板 通常定義一個通用的結構,所有 子模板 都能很好的繼承它。在我們的例子中,layout.html 就是一個父模板而其它 .html 文件就是子模板。
templates目錄結構如下:
templates/
layout.html
index.html
about.html
profile/
layout.html
index.html
photos.html
admin/
layout.html
index.html
analytics.html
通常有一個頂層的 layout.html,它定義了應用程序的通用佈局以及你的網站的每一部分。如果你看看上面的目錄的話,你會看到一個頂層的 myapp/templates/layout.html,同樣還有 myapp/templates/profile/layout.html 和 myapp/templates/admin/layout.html。最後兩個文件繼承和修改第一個文件。
在父模板中,我們能定義由子模板來填充的塊。
{# _myapp/templates/layout.html_ #}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block body %}
<h1>This heading is defined in the parent.</h1>
{% endblock %}
</body>
</html>
在子模板中,我們可以擴展父模板並且定義這些塊的內容。
{# _myapp/templates/index.html_ #}
{% extends "layout.html" %}
{% block title %}Hello world!{% endblock %}
{% block body %}
{{ super() }} ##super() 函數讓我們渲染父級塊的內容。
<h2>This heading is defined in the child.</h2>
{% endblock %}
塊
塊用於繼承,同時作爲佔位符和用於替換的內容。 模板繼承 節中詳細地介紹了塊。
包含
include 語句用於包含一個模板,並在當前命名空間中返回那個文件的內容渲 染結果:
{% include 'header.html' %}
Body
{% include 'footer.html' %}
從 Jinja 2.2 開始,你可以把一句 include 用 ignore missing 標記,這樣 如果模板不存在,Jinja 會忽略這條語句。當與 with 或 without context 語句聯合使用時,它必須被放在上下文可見性語句 之前 。這裏是一些有效的例 子:
{% include "sidebar.html" ignore missing %}
{% include "sidebar.html" ignore missing with context %}
{% include "sidebar.html" ignore missing without context %}
宏、繼承、包含區別
宏(Macro)、繼承(Block)、包含(include)均能實現代碼的複用。
繼承(Block)的本質是代碼替換,一般用來實現多個頁面中重複不變的區域。
宏(Macro)的功能類似函數,可以傳入參數,需要定義、調用。
包含(include)是直接將目標模板文件整個渲染出來。
3.4 導入上下文行爲
默認下,每個包含的模板會被傳遞到當前上下文,而導入的模板不會。這樣做的原因 是導入量不會像包含量被緩存,因爲導入量經常只作容納宏的模塊。
無論如何,這當然也可以顯式地更改。通過在 import/include 聲明中直接添加 with context 或 without context ,當前的上下文可以傳遞到模板,而且不會 自動禁用緩存。
{% from 'forms.html' import input with context %}
{% include 'header.html' without context %}
參考文獻:
http://docs.jinkan.org/docs/flask/quickstart.html#id7
http://docs.jinkan.org/docs/flask/templating.html#jinja