Flask 使用Jinja2模板引擎

微軟技術分享發表於2023-11-26

Jinja2,由Flask框架的創作者開發,是一款功能豐富的模板引擎,以其完整的Unicode支援、靈活性、高效性和安全性而備受推崇。最初受Django模板引擎啟發,Jinja2為Flask提供了強大的模板支援,後來也成為其他專案的首選。在本文中,我們將深入探討Jinja2的特性、語法以及如何在Flask應用中使用它來構建動態而又美觀的Web頁面。

IF模板

IF語句用於在模板中執行條件判斷,根據不同的條件呈現不同的內容。在模板中,透過{% if condition %} ... {% endif %}的語法結構來定義條件塊。其中,condition是一個表示式或變數,如果該條件為真,模板引擎將執行if塊內的內容,否則將跳過。

IF模板語句支援多種條件判斷,包括比較運算、邏輯運算等。透過合理運用IF語句,可以根據動態資料或使用者輸入,在頁面上展示不同的資訊或呈現不同的頁面結構,從而實現更加靈活和個性化的頁面設計。

// ----------------------------------------------
// 前端部分
// ----------------------------------------------
{% if username and username == "admin" %}
    <h1>hello {{ username }} welcome</h1>
{% elif username and username == "lyshark" %}
    <h1>hello {{ username }}</h1>
{% else %}
    <h1>hello lyshark</h1>
{% endif %}

// ----------------------------------------------
// 後端部分
// ----------------------------------------------
@app.route("/", methods=["GET", "POST"])
def index():
    return render_template("index.html",username = "admin")

FOR模板

FOR迴圈模板語句允許在模板中對資料進行迭代操作,便於遍歷集合、列表或字典等資料結構,並在模板中對每個元素執行相同的操作。透過{% for item in iterable %} ... {% endfor %}的語法結構,可以定義一個FOR迴圈塊。

在FOR迴圈中,item表示每次迭代中當前的元素,而iterable則是要遍歷的資料集合。迴圈塊內的程式碼將在每次迭代時執行,允許動態生成頁面內容。此外,Jinja2的FOR迴圈還支援迴圈索引、迴圈計數等功能,提供了靈活的迭代控制機制。

FOR模板語句在Web開發中經常用於動態生成頁面元素,特別是在展示多條資料、列表或表格內容時非常實用。透過FOR迴圈,開發者可以更方便地處理和展示動態資料,提高頁面的靈活性和互動性。

// ----------------------------------------------
// 前端部分
// ----------------------------------------------
<!--輸出列表-->
{% for x in digits %}
    <p>輸出列表: {{ x }}</p>
{% endfor %}

<!--輸出字典-->
{% for item in dicts %}
    <!--第一個和最後一個元素-->
    {% if loop.first %}
        <b>第一個元素是: {{ item.name }}</b>
    {% elif loop.last %}
        <b>最後一個元素是: {{ item.name }}</b>
    {% endif %}

    <!--下標輸出-->
    <b>當前下標(從1開始): {{ loop.index }}</b>
    <b>當前下標(從0開始): {{ loop.index0 }}</b>

    <!--字典長度-->
    <b>陣列長度: {{ loop.length }}</b>
    <b>迭代計數(從1開始): {{ loop.revindex }}</b>
    <b>迭代計數(從0開始): {{ loop.revindex0 }}</b>

    <p> 輸出名字: {{ item.name }} 輸出密碼: {{ item.password }}</p>
{% endfor %}

<!--輸出元組-->
{% for href,caption in tuples %}
    <p>{{ href }} = {{ caption }}</p>
{% endfor %}

// ----------------------------------------------
// 後端部分
// ----------------------------------------------
@app.route("/", methods=["GET", "POST"])
def index():
    return render_template("index.html",
                           digits=[1,2,3,4,5],
                           dicts=[
                                {'name':'John','password':'123123'},
                                {'name':'Tom', 'password':'123456'},
                                {'name':'Lisa', 'password':'123123'},
                                {'name':'Bob', 'password':'123456'}
                            ],
                           tuples=[
                                ('index.html', 'Index'),
                                ('about.html', 'About'),
                                ('downloads.html', 'Downloads')]
                           )                    

FOR模板排序允許在模板中對迴圈輸出的元素進行排序或分組操作,透過內建的過濾器實現。這提供了更靈活的控制機制,使得模板能夠按照特定的順序展示資料,或者將資料按照某個條件分組呈現。

透過結合FOR迴圈和排序過濾器,模板可以根據開發者的需求對資料進行動態排列。在模板中,使用類似於{% for item in iterable | sort %} ... {% endfor %}的語法,可以對iterable中的元素進行排序。除了簡單的字母和數字排序外,Jinja2還支援透過自定義函式進行排序,提供了更高度定製的排序功能。

// ----------------------------------------------
// 前端部分
// ----------------------------------------------
<!-- 按指定欄位排序,這裡設reverse為true使其按降序排 -->
<ul>
    {% for item in dicts | sort(attribute='age', reverse=true) %}
         <li> 名字: {{ item.name }} 年齡: {{ item.age }}</li>
    {% endfor %}
</ul>

<!-- 列表分組,每組是一個子列表,組名就是分組項的值 -->
<ul>
    {% for group in dicts|groupby('gender') %}
        <li>組名: {{ group.grouper }}
            <ul>
                {% for user in group.list %}
                    <li>使用者: {{ user.name }}</li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

<!-- 取字典中的某一項組成列表,再將其連線起來 -->
<p>連線後: {{ dicts | map(attribute='name') | join(', ') }}</p>
<p>連線後: {{ dicts | map(attribute='age') | join(', ') }}</p>

// ----------------------------------------------
// 後端部分
// ----------------------------------------------
@app.route("/", methods=["GET", "POST"])
def index():
    return render_template("index.html",
                           dicts = [
                                {'name':'Tom','gender':'M','age':20},
                                {'name':'John','gender':'M','age':18},
                                {'name':'Mary','gender':'F','age':24},
                                {'name':'Bob','gender':'M','age':31},
                                {'name':'Lisa','gender':'F','age':19}]
                           )

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

模板閃現機制

閃現機制是一種在請求之間傳遞資訊的有效方式。它允許在一個請求中設定資料,然後在下一個請求中訪問這些資料。主要透過flash()函式實現,使開發者能夠方便地在請求之間傳遞和呈現資訊,增強了使用者體驗。

閃現機制透過flash()函式實現,主要分為三種:

  1. 基本閃現機制: 使用flash(message, category='message')函式將訊息閃現到下一個請求。這個訊息可以是字串,也可以是其他資料型別,而category引數用於指定訊息的類別,通常預設為'message'。
  2. 訊息分類: 閃現訊息可以根據不同的類別進行分類,以便在前端頁面中有更好的呈現形式。透過設定category引數,可以將訊息劃分為不同的類別,例如'error'、'success'等,以便在模板中有條件地處理這些訊息。
  3. 模板中的處理: 在模板中,可以使用get_flashed_messages(with_categories=False, category_filter=['error', 'warning'])函式獲取所有閃現的訊息。透過with_categories引數可以選擇獲取訊息時是否攜帶類別資訊,而category_filter引數則可以指定只獲取特定類別的訊息。

這三種機制共同構成了Flask框架中靈活且強大的模板閃現系統,使得在Web應用中更便捷地實現訊息傳遞和呈現。

1.模板中獲取閃現資訊,實現在下次請求時返回內容到前臺。

<!--閃現訊息,返回一個列表-->
{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul>
        {% for message in messages %}
          <li>{{ message }}</li>
        {% endfor %}
    </ul>
  {% endif %}
{% endwith %}


{% if error %}
    <p><strong>失敗訊息:</strong> {{ error }}</p>
{% endif %}

<form action="" method=post>
    使用者名稱: <input type=text name=username>
    密碼: <input type=password name=password>
    <input type=submit value="使用者登入">
</form>

後端只需要在驗證透過的情況下,直接呼叫flash()函式實現訊息的前臺傳遞。

from flask import Flask, flash, redirect, render_template, request, url_for

app = Flask(__name__, template_folder="./tempate",static_folder="./tempate")
app.secret_key = 'some_secret'

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == "POST":
        if request.form['username'] != "lyshark" or request.form['password'] != "1233":
            flash("登入失敗了")
            return render_template('index.html',error = "失敗")
        else:
            flash("恭喜您登入成功")

    return render_template('index.html')

if __name__ == "__main__":
    app.run()

2.模板中的分類閃現,在閃現訊息是指定一個訊息分了i,如果不指定則預設分類為Message訊息。

要使用自定義的分類,只要使用flash()函式傳入第二個引數即可。

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <ul>
        {% for category, message in messages %}
          <li class="{{ category }}">{{ category }}:{{ message }}</li>
        {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

後端只需要增加第二個引數來指定需要顯現的分組即可。

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == "POST":
        if request.form['username'] != "lyshark" or request.form['password'] != "1233":
            flash("登入失敗了")
            return render_template('index.html',error = "失敗")
        else:
            flash("恭喜您登入成功","status")
            flash("測試賬戶","username")

    return render_template('index.html')

if __name__ == "__main__":
    app.run()

3.模板中過濾閃現訊息,過濾閃現即指在前臺透過category_filter增加過濾條件,來實現對特殊訊息的過濾輸出。

<!--閃現訊息,增加過濾器-->
{% with messages = get_flashed_messages(category_filter=["username","status"]) %}
  {% if messages %}
    <ul>
        {% for message in messages %}
          <li>{{ message }}</li>
        {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

{% if error %}
    <p><strong>失敗訊息:</strong> {{ error }}</p>
{% endif %}

<form action="" method=post>
    使用者名稱: <input type=text name=username>
    密碼: <input type=password name=password>
    <input type=submit value="使用者登入">
</form>

後端也可在閃現訊息時指定一個category屬性實現過濾機制。

from flask import Flask, flash, redirect, render_template, request, get_flashed_messages

app = Flask(__name__, template_folder="./tempate",static_folder="./tempate")
app.secret_key = 'some_secret'

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == "POST":
        if request.form['username'] != "lyshark" or request.form['password'] != "1233":
            flash("登入失敗了")
            return render_template('index.html',error = "失敗")
        else:
            flash("恭喜您登入成功","status")
            flash("測試新的閃現",category="username")       # 輸出到username
            flash("測試賬戶","username")                    # 閃現到username

    print('閃現的資訊是{}'.format(get_flashed_messages()))
    return render_template('index.html')

if __name__ == "__main__":
    app.run()

自定義上下文

上下文是一個在請求處理過程中可以訪問的全域性物件集合。除了Flask本身提供的預設上下文之外,開發者還可以透過自定義上下文函式來新增額外的全域性變數或函式,以便在檢視函式和模板中使用。這就是自定義上下文函式的作用。

關鍵點和優勢:

  1. 上下文函式的定義: 自定義上下文函式是透過app.context_processor裝飾器定義的。這個函式會在每次請求處理前被呼叫,返回的字典中的鍵值對將成為全域性變數。
  2. 全域性變數的新增: 開發者可以在自定義上下文函式中新增一些全域性變數,這些變數可以在所有檢視函式和模板中直接訪問,無需在每個檢視函式中都進行傳遞。
  3. 公共函式的注入: 除了變數,還可以在自定義上下文函式中注入一些公共函式。這些函式可以用於處理資料、生成通用的HTML片段等。
  4. 模板中的使用: 自定義上下文函式中新增的變數可以直接在模板中使用,而無需在每個檢視函式中都傳遞一遍。這簡化了程式碼,提高了開發效率。
  5. 適用於多個檢視: 自定義上下文函式中新增的內容對整個應用程式的多個檢視都是可用的,因此非常適合用於那些需要在整個應用範圍內共享的資訊。

透過合理使用自定義上下文函式,可以使Flask應用更加靈活、可維護,並提供一致的全域性資訊和功能。這種機制有助於將一些通用的操作和資料注入到應用中,提高了程式碼的可讀性和可重用性。

下面我們就來先定義一個上下文變數以及上下文函式,將返回字典的屬性指向一個函式即可。

<p>當前APP名字: {{ app_name }}</p>
<p>當前時間戳: {{ local_time }}</p>
<p>當前名字: {{ local_user }}</p>

<p>當前時間: {{ current_time() }}</p>
<p>格式化輸出: {{ current_time("%Y-%m-%d") }}</p>

<p> 傳遞整數呼叫函式: {{ add_function(34,12) }}</p>
<p> 傳遞陣列呼叫函式: {{ list_function([1,2,3,4,5]) }}</p>

後端程式碼編寫部分,需要引入current_app然後在需要定義的函式上增加@app.context_processor裝飾器,返回引數透過字典呼叫dict(list_function = get)得到結果。

from flask import Flask, render_template
from flask import current_app
import time

app = Flask(__name__)

# 自定義變數
@app.context_processor
def appinfo():
    return dict(app_name = current_app.name,
                local_time = time.time(),
                local_user = "lyshark"
                )

# 自定義函式
@app.context_processor
def app_function_gettime():
    def get_time(timeFormat="%b %d, %Y - %H:%M:%S"):
        return time.strftime(timeFormat)
    return dict(current_time = get_time)

# 自定義函式(傳遞整數)
@app.context_processor
def app_function_add():
    def get(x=0,y=0):
        z = x + y
        return z
    return dict(add_function = get)

# 自定義函式(傳遞陣列)
@app.context_processor
def app_function_list():
    def get(x=[]):
        sum = 0
        for item in x:
            sum = sum + item
        return sum
    return dict(list_function = get)

@app.route("/", methods=["GET", "POST"])
def index():
    return render_template("index.html")

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

自定義過濾器

自定義過濾器是一種強大的工具,允許開發者在模板中對資料進行各種處理和格式化操作。過濾器其實就是函式,透過使用add_template_filter方法將自定義函式註冊為模板過濾器,從而在模板中呼叫。

關鍵點和優勢:

  1. 過濾器的定義: 開發者可以透過定義一個函式,並使用add_template_filter方法將這個函式註冊為模板過濾器。這個函式將用於對模板中的資料進行處理。
  2. 資料處理和格式化: 自定義過濾器可以執行各種資料處理和格式化操作,如日期格式化、字串截斷、資料轉換等。這有助於在模板中減少邏輯處理,保持模板的簡潔性。
  3. 可重用性: 透過自定義過濾器,開發者可以將常用的資料處理邏輯抽象成函式,提高程式碼的可重用性。這些過濾器可以在多個模板和檢視中共享使用。
  4. 模板中的使用: 一旦註冊了自定義過濾器,就可以在模板中使用它。透過在模板中呼叫過濾器函式,並傳遞相應的引數,可以對模板中的資料進行實時處理。
  5. 框架整合: Flask提供了簡單而強大的方式來整合自定義過濾器,使得開發者可以輕鬆地擴充套件模板引擎的功能,滿足不同場景下的需求。

透過靈活使用自定義過濾器,可以使模板引擎更加強大,滿足更復雜的展示需求。這種機制有助於降低模板中的程式碼複雜度,提高開發效率,同時保持模板的可讀性。

過濾器其實是一個函式,函式支援自定義功能,透過flask的add_template_filter將我們的函式加入到過濾器表單中。

<p>輸出雙數: {{ [1,2,3,4,5,6,7,8,9,10] | double_step }}</p>
<p>輸出子列表: {{ [1,2,3,4,5,6,7,8,9,10] | sub_step(1,5) }}</p>
<p>全域性函式呼叫: {{ global_add(10,20,30) }} </p>

後臺增加過濾器有兩種方式,直接在函式上增加@app.template_filter('sub_step')裝飾器實現,或透過add_template_filter將某個函式直接定義為過濾器使用。

from flask import Flask, render_template
from flask import current_app

app = Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def index():
    return render_template("index.html")

# 增加過濾器(方式1),輸出列表中的雙數
def double_step_filter(x):
    return x[::2]
app.add_template_filter(double_step_filter,"double_step")

# 增加過濾器(方式2)
@app.template_filter('sub_step')
def sub(x, start, end):
    return x[start:end]

# 增加全域性函式
@app.template_global('global_add')
def function(x,y,z):
    return x+y+z

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

自定義測試器

自定義測試器是一種用於在模板中進行條件判斷的工具。類似於過濾器,自定義測試器也是透過註冊函式的方式來實現的,但是它們主要用於在模板中執行布林條件測試。

關鍵點和優勢:

  1. 測試器的定義: 開發者可以定義一個函式,並使用@app.template_test()裝飾器將其註冊為模板測試器。這個函式將包含一些布林條件判斷邏輯。
  2. 條件判斷: 自定義測試器主要用於在模板中進行條件判斷。透過在模板中呼叫測試器函式,並傳遞相應的引數,可以獲取布林值,用於控制模板中的條件分支。
  3. 可讀性和模組化: 將常用的布林條件判斷邏輯抽象成測試器函式,有助於提高模板中的可讀性和模組化程度。這使得在模板中的條件判斷部分更為清晰和易於維護。
  4. 模板中的使用: 一旦註冊了自定義測試器,就可以在模板中使用它。透過在模板中呼叫測試器函式,並傳遞相應的引數,可以獲得布林值,從而決定模板中的條件分支。
  5. 框架整合: Flask提供了簡單而強大的方式來整合自定義測試器,使得開發者可以輕鬆地擴充套件模板引擎的功能,實現更靈活的條件判斷。

透過合理使用自定義測試器,可以使得模板中的條件判斷更為清晰和靈活。這種機制有助於降低模板中的程式碼複雜度,提高開發效率,同時使得模板的邏輯更易於理解和維護。

自定義測試器與過濾器基本一致,區別在於測試器使用@app.template_test()裝飾函式,其他方法與過濾器保持一致。

<!--驗證字串結尾是否是指定字元-->
{% if name is end_with "me" %}
  <h2>"字串 {{ name }}" 結尾是 "me"</h2>
{% else %}
    <h2>"字串 {{ name }}" 結尾不是 "me"</h2>
{% endif %}

<!--驗證陣列中是否有大於10的元素-->
{% if lists is array_of "10" %}
  <h2>列表中存在,大於10的資料</h2>
{% endif %}

測試器後端分別定義兩個函式,一個用於判斷字串結尾是否存在某個字元,另一個則是驗證陣列內是否存在大於元素。

from flask import Flask, render_template

app = Flask(__name__)

# 自定義測試器
@app.template_test('end_with')
def end_with(str,suffix):
    return str.lower().endswith(suffix.lower())

# 自定義測試器(測試陣列內是否有大於某值的)
@app.template_test('array_of')
def array_of(str,suffix):
    for item in str:
        if item >= int(suffix):
            return True

@app.route("/", methods=["GET", "POST"])
def index():
    return render_template("index.html",name = "lyshark me", lists = [1,2,3,4,5,6,7,8,56,33])

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