by 太陽雪
被之前的文章中,簡單介紹了 Python Web 開發框架 Flask,知道了如何寫個 Hello World,但是距離用 Flask 開發真正的項目,還有段距離,現在我們目標更靠近一些 —— 學習下 Jinja2 模板。
模板的作用
模板是用來做什么的呢?模板是用來更高效地生成相應時的 Html 文本的,沒有模板,可以手寫,比如之前的 hello world 示例,寫段 html 代碼:
<h1>Hello world!</h1>
對於簡單的練習還行,但對於規模大的,動態化程度高的項目來說,這樣寫就有些勉強了,即,不利於項目和產品化。那么模板有什么好處呢:
- 能讓展現邏輯和業務邏輯
展示邏輯即 UI,就是用來給用戶看和操作的,業務邏輯是業務規則,比如什么條件可以注冊,什么權限能考到什么。模板將展現邏輯封裝起來,業務邏輯寫在視圖函數中。 - 能使項目更易維護
由於展現邏輯和業務邏輯的分離,它們可以由不同的開發人員來維護,不會有代碼沖突的問題 - 使項目更加安全
在做交互式開發中,有個原則: 永遠不要相信用戶的輸入,因為惡意用戶可能通過輸入來注入(關於注入以后有機會可以單獨聊聊),而模板在一定程度上會防注入,例如用戶輸入一點 html 代碼作為輸入,默認情況下模板會將其替換為網絡安全字符,以防止惡意注入。 - 能提高開發效率
有了模板,相當於一個展示邏輯的函數,所以就可以被復用,可以用在不同的視圖函數中,也可以用在不同的項目中
思考下:
上面提到的展現邏輯和業務邏輯,為什么不直接說成前台和后台呢?
如果你有答案和想法,歡迎留言討論。
Jinja2 模板引擎
Jinja2 是 Flask 框架默認支持的模板引擎,並不是唯一也不是最好(因人而異,沒有最好)模板引擎,不同的 Web 框架,比如 Django、Nodejs 等都有自己的模板引擎,甚至一些程序員自己實現的模板引擎(我就這么干過),但大體思路是一樣的,都是要將數據替換或者轉換到,用特殊格式標記了位置的模板中,以合成動態的 html,這種技術不新鮮,在之前的打印模板,如水晶報表里就有,無非就是標記和語法不同而已,所以要舉一反三。
引入渲染函數
像其他功能一樣,要使用模板引擎,先引入
from flask import render_template
注意: 要將將模板文件放置在項目根目錄(即
print(__file__)
顯示的路徑)下的templates
文件夾中
例如模板文件 hello.html
為:
{% raw %}
<h1>Hello {{ name }} </h1>
{% endraw %}
視圖函數可以寫成:
@app.route('/user/<name>')
def index(name):
return render_template('hello.html', name=name)
Flask提供的 render_template
函數把Jinja2模板引擎集成到了程序中。 render_template
函數第一個參數是模板的文件名,隨后的參數都是鍵值對,表示模板中變量的對應的真實值,在上面代碼中,模板會接收到一個名為 name
的變量
變量
模板文件就是普通的文本文件,然后將需要替換的部分用雙大括號( {{ }}
)標記出來,雙大括號中,表示要替換的變量名,這個變量支持基本數據類型,以及列表、詞典、對象和元組。如模板 template.html
:
{% raw %}
<p> A value form a string: {{ name }}.</p>
<p> A value form a int: {{ myindex }}.</p>
<p> A value form a list: {{ mylist[3]] }}.</p>
<p> A value form a list, with a variable index: {{ mylist[myindex] }}.</p>
<p> A value form a dictionary: {{ mydict['key'] }}.</p>
<p> A value form a tuple: {{ mytuple }}.</p>
<p> A value form a tuple by index: {{ mytuple[myindex] }}.</p>
{% endraw %}
視圖函數代碼:
@app.route('/template/')
def template():
name = 'Jinja2 模板引擎'
myindex = 1
mylist = [1,2,3,4]
mydict = {
key: 'age',
value: '25'
}
mytuple = (1,2,3,4)
return render_template('template.html', name=name, myindex=myindex, mylist=mylist, mydict=mydict, mytuple=mytuple)
顯示結果:
過濾器
有些時候需要對要在模板中替換的值做一些特殊處理,比如首字母大寫,去掉前后空格等等,有種選擇就是使用過濾器。
說明
Jinjia2 模板引擎中,過濾器類似於 Linux 命令中的管道,例如將字符串變量的首字母大寫
{% raw %}
<h1>{{ name | capitalize}}</h1>
{% endraw %}
過濾器可以拼接,和 linux 的管道命令一樣,如對值進行全部變大寫,並且去除前后空白字符:
{% raw %}
<h1>{{ name | upper | trim }}</h1>
{% endraw %}
如上代碼,過濾器和變量之間用管道符號 | 相連,相當於對變量值作進一步加工。
一些常用的過濾器
過濾器 | 說明 |
---|---|
safe | 渲染是不轉義 |
capitalize | 首字母大寫 |
lower | 所有字母小寫 |
upper | 所有字母大寫 |
title | 值中每個單詞首字母大寫 |
trim | 刪除首位空白字符 |
striptags | 渲染時刪除掉值中所有HTML標簽 |
注意: safe 過濾器,默認情況下,處於安全考慮,Jinja2 會轉義所有變量,例如一個變量的值為<h1>Hello</h1>
, Jinja2 會將其渲染成<h1>Hello</>
,瀏覽器會顯示出原本的值,但是不會解釋。如果需要瀏覽器解釋的話,可以使用 safe 過濾器
例如模板文件html.html
為:
{% raw %}
<h1>{{ html | safe }}</h1>
{% endraw %}
視圖函數為:
@app.route('/html')
def html():
return render_template('html.html', html='<b>bob</b>')
注意:千萬別在不可信的值上使用 safe 過濾器,例如用戶在表單上輸入的文本。
還有一些有用的過濾器
default
,可以當變量未定義時,提供默認值,如果想將false
、False
和空(none
)視為未定義,需要提供第二個參數為true
{% raw %}
<!-- 提供默認值過濾器 -->
<h1>Hello {{ name | default('world') }}!</h1>
<!-- 將false、False和空(none)視為未定義的默認值過濾器 -->
<h1>Hello {{ name | default('world', true)! }}</h1>
{% endraw %}
當變量 name
的未定義時,上下兩個顯示效果一樣,當值為 none
時,上面會顯示 Hello none!
, 而下面的會顯示 Hello world!
- 列表過濾器
min
,max
, 得到列表中的最小值或最大值
自定義過濾器
過濾器雖然有很多,但總有不滿足需求的時候,例如首行文字縮進、將金額轉化為中文的大寫等等。
過濾器實質就是個函數,所以,第一定義一個過濾器函數,第二,注冊到Jinjia2 的過濾器中。
# 定義過濾器函數
def mylen(arg):# 實現一個可以求長度的函數
return len(arg)
def interval(test_str, start, end): # 返回字符串中指定區間的內容
return test_str[int(start):int(end)]
# 注冊過濾器
env = app.jinja_env
env.filters['mylen'] = mylen
env.filters['interval'] = interval
# 視圖函數
@app.route('/myfilter')
def myfilter():
return render_template('myfilter.html', phone='13300000000')
模板文件
{% raw %}
<h1>電話號碼是:{{ phone }}, 長度為:{{ phone | mylen }},運營商號:{{ phone | interval(0,3) }}</h1>
{% endraw %}
過濾器注冊代碼還可以寫在初始化代碼
__init__.py
中
控制結構
很多時候,需要更智能的模板渲染,即能給渲染編程,比如男生一個樣式,女生一樣樣式,控制結構指令需要用指令標記來指定,下面介紹下一些簡單的控制結構
條件
即在模板中用 if-else
控制結構
{% raw %}
{% if gender=='male' %}
Hello, Mr {{ name }}
{% else %}
Hello, Ms {{ name }}
{% endif %}
{% endraw %}
視圖函數
@app.route('/hello2/<name>/<gender>')
def hello2(name, gender):
return render_template('hello2.html', name=name, gender=gender)
在控制結構里,代碼語法同python
循環
循環對於渲染列表,很有幫助,循環的標記是 for
。例如獎列表的內容顯示在 ul
中
{% raw %}
<ul>
{% for name in names %}
<li>{{ name }} </li>
{% endfor %}
</ul>
{% endraw %}
例如給定一個學生列表,將其用無序列表 ul
顯示出來
宏——模板中的函數
模板中可以定義宏,相當於定義了一個函數,可以重復使用,讓邏輯更清晰。
首先,定義一個宏:
mymacro.html
{% raw %}
{% macro render_name(name) %}
<li>{{ name }}</li>
{% endmacro %}
{% endraw %}
然后使用宏, 例如將循環結構的例子中,顯示名稱的地方,改為調用宏
{% raw %}
<ul>
{% for name in names %}
{{ render_name(name) }}
{% endfor %}
</ul>
{% endraw %}
調用宏,和調用函數是一樣的,不過要將代碼寫在 {{}}
雙大括號內。
一般我們會將宏存在單獨的文件中,以便復用,在需要用到宏的地方,引用就好了
{% raw %}
{% import 'mymarco.html' as macros %}
<ul>
{% for name in names %}
{{ macros.render_name(name) }}
{% endfor%}
</ul>
{% endraw %}
如上所述,用 improt 引入宏定義文件,通過 as 指定別名,和 python 的模塊引入一樣。指定別名是一個良好的編程習慣,可以將一個復雜的東西形象化,同時像一個命名空間一樣,有效的避免沖突。
include
另外可以將多個模板片段寫入一個單獨文件,再包含( include
)在所有模板中,以提高開發效率:
{% raw %}
{% include 'common.html' %}
{% endraw %}
include
進來的文件,相當於將文件中的內容復制到 include
的位置,所以自使用之前需要考慮仔細
模板繼承
如果覺得 include
過於呆板,靈活性差,Jinja2 模板引擎還有更高級的功能——繼承。類似於 Python 代碼中類的繼承,一起看看。
首先定義一個基類, base.html:
{% raw %}
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock%} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
<h3>這是基類的內容</h3>
{% endblock %}
</body>
</html>
{% endraw %}
其中的 block
標簽,定義了可以被子類重構(替換)的部分,每個 blcok
標簽,需要指定一個特殊的名稱,例如 head
、title
等,以便子類用特定的名稱來重構。另外 block
標簽需要有結束標簽 endblock
,類似於類C語言中的大括號,當然 block
標簽也可以嵌套。
接下來,定義一個子類模板 hello3.html:
{% raw %}
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style></style>
{% endblock %}
{% block body %}
{{ super() }}
<h3>這是子類的內容 Hello world!</h>
{% endblock %}
{% endraw %}
效果如圖所示:
通過 extends
標記來指定需要繼承的基類,然后用 block
標記來設置子類需要替換調基類中的內容,只要 block
指定的名稱一樣就行。
另外,如不需要完全替換調基類的內容,可以在子類 block
中,調用 super
方法,以獲取基類在此名稱下的內容,這樣就能達到更號的靈活性。
總結
今天介紹了 Jinja2 模板引擎的基本用法和特點,期望通過不同的特點,讓你了解到模板的基本用法,以便更快的使用和進一步學習更深入的內容。另外,想通過 Jinja2 模板引擎,說明模板的基本特征,以便觸類旁通、舉一反三,更快的學習其他優秀的模板, 同時也想說明,模板不僅僅可以用在 Web 的開發中,還可以用在自動化編碼、測試等眾多領域。
最后在本章開頭,留了個思考題,為什么不將展現邏輯和業務邏輯說成是前台和后台呢?如果你有答案,歡迎留言交流。
參考
- 圖書: Flask Web開發 https://item.jd.com/12418677.html
- API-Jinja Documentation(2,10.x) https://jinja.palletsprojects.com/en/2.10.x/api/#the-context
- ansible基礎-Jinjia2模板——過濾器 https://www.cnblogs.com/mauricewei/p/10056379.html
關注公眾號:python技術,回復"python"一起學習交流