第21天: Web 開發 Jinja2 模板引擎


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 會將其渲染成&lt;h1&gt;Hello&lt;/&gt;,瀏覽器會顯示出原本的值,但是不會解釋。如果需要瀏覽器解釋的話,可以使用 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,可以當變量未定義時,提供默認值,如果想將 falseFalse 和空( 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 標簽,需要指定一個特殊的名稱,例如 headtitle 等,以便子類用特定的名稱來重構。另外 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 的開發中,還可以用在自動化編碼、測試等眾多領域。
最后在本章開頭,留了個思考題,為什么不將展現邏輯業務邏輯說成是前台后台呢?如果你有答案,歡迎留言交流。

示例代碼

參考

關注公眾號:python技術,回復"python"一起學習交流


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM