National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 06

Jinja2 模板基礎

把 HTML 從程式裡分開:變數、過濾器、判斷、迴圈與自動跳脫
Interaction實作 25 minute(s)

Learning Objectives

  • 說明為什麼要把展示邏輯從 view 函數分到模板
  • 用 render_template 把資料傳給模板,並存取字典與串列
  • 使用 default、length、upper、round、join、truncate 等過濾器
  • 在模板裡寫 if、for,並使用 loop.index 與 for-else
  • 說明自動跳脫如何防止 XSS,以及 safe 與 render_template_string 的風險

為什麼要把 HTML 從程式裡分開

到目前為止,view 函數都直接回傳字串。頁面只有一行時沒問題,但一個真正的頁面有幾十行 HTML,寫在 Python 字串裡既難讀也難改,把使用者輸入接進字串還很容易出現安全問題。

一個 view 函數其實在做兩件不同的事:

負責什麼 放在哪裡
商業邏輯 查資料、判斷權限、計算 view 函數
展示邏輯 資料要排成什麼樣子 模板(template)

Flask 用 Jinja2 當模板引擎。模板檔案預設放在程式旁邊的 templates 資料夾:

app.py
templates/
    course.html

render_template('course.html', name=name) 會找到這個檔案,把 name 代入,回傳算好的 HTML 字串。

逐步看:資料怎麼交給模板

模板是 templates/course.html(在輸出區的檔案分頁可以看到)。請注意 render_template 後面的引數名稱,和模板裡 {{ }} 的名稱是對應的。逐步動畫只追蹤 .py,模板在 render_template 那一行一次算完。

三種符號與常用過濾器

Jinja2 的語法只有三種符號:

符號 用途 範例
{{ … }} 輸出一個值 {{ teacher.name }}
{% … %} 控制結構 {% if x %}、{% for a in b %}
{# … #} 註解,不會送到瀏覽器 {# 待修改 #}

過濾器(filter)用直線 | 接在值的後面,把值轉換一次再輸出,可以串接多個:

過濾器 作用 要注意的地方
default('匿名') 變數沒有定義時用這個值 空字串不算沒定義;要連空字串也換掉,寫 default('匿名', true)
length 字串長度、串列項目數
upper 轉成大寫
round(1) 四捨五入到小數第 1 位
join('、') 把串列接成一個字串
truncate(30) 截短,結尾加 ... 預設不切斷英文單字;只超過一點點(預設 5 個字元以內)時不截短
safe 標記為安全,不跳脫 有風險,下面會實際操作

完整清單見 Jinja 內建過濾器。

逐步看:過濾器、判斷與迴圈

同一個模板 notices.html 被開了兩次:一次有公告,一次是空串列。請比較兩段輸出,尤其是暱稱那兩行,以及 {% for %} 的 {% else %} 什麼時候出現。

自動跳脫防止的是什麼

留言板、搜尋框、暱稱都是使用者輸入。如果有人把暱稱取成 <script>…</script>,而伺服器把它原樣放進 HTML,瀏覽器就會執行這段程式。其他人打開頁面時,程式在他們的瀏覽器裡以你的網站的身分執行,這叫跨站腳本攻擊(XSS,Cross-Site Scripting)。

Flask 替 .html、.htm、.xml、.xhtml、.svg 模板開啟了自動跳脫(autoescape):{{ }} 輸出時把 <、>、&、引號換成 HTML 實體,瀏覽器會把它們當成文字顯示,不當標籤解析。第 3 課手動呼叫的 escape() 做的就是這件事。

逐步看:同一段字串,兩種輸出

模板 preview.html 把同一個 msg 輸出兩次,第二次多了 |safe。請比較輸出的第一行和第二行。

互動練習:改模板,看 HTML 怎麼變

按「啟動伺服器」後,在模擬瀏覽器裡操作,也可以直接改上面的程式與模板。

改完要按「存檔並重新啟動」,然後按瀏覽器上的 ↻ 或實際操作一次,任務才會判定。

下面的 <script> 只在你自己瀏覽器的沙箱裡執行,碰不到其他頁面。這個技巧只能用在自己的程式或獲得授權的測試。

safe 與 render_template_string 的風險

什麼時候可以用 safe: 只有當內容來自你信任的來源,例如你自己寫的 HTML,或是已經經過 HTML 清理工具處理的內容。使用者輸入的留言不符合這個條件。第 17 課會實際看到這類漏洞被利用的結果。

render_template_string 不要接使用者輸入。 它把字串當成模板來編譯,所以下面的寫法很危險:

# 有漏洞的寫法,不要這樣寫
return render_template_string('<p>' + request.args['q'] + '</p>')

使用者輸入 {{ 7*7 }} 會看到 49,代表他的輸入被當成程式執行了。模板裡預設還可以存取 config(裡面有 secret_key),這類漏洞叫伺服器端模板注入(SSTI)。正確的做法是把模板寫死,使用者輸入當成變數傳進去:

return render_template_string('<p>{{ q }}</p>', q=request.args['q'])

動手做:完成讀書會頁面

修改 PAGE 這個模板(程式最下面的測試與兩個路由不要改):

  1. 標題的 group 轉成大寫
  2. note 是空字串時顯示「沒有備註」
  3. 用過濾器算出成員人數,取代 ?
  4. 用 for 列出成員,格式是 <li>1. 小安</li>;沒有成員時改成一行 <li>還沒有成員</li>

其中一位成員的名字含有 HTML 標籤,請確認它會被跳脫,不要用 safe。

你的輸出
預期輸出

想一想

1.

下面的程式會印出什麼?

from flask import Flask, render_template_string
app = Flask(__name__)
with app.app_context():
    print(render_template_string(
        "{% for x in items %}{{ loop.index }}{{ x }} {% endfor %}",
        items=["a", "b"]))
2.

下面的程式會印出什麼?

from flask import Flask, render_template_string
app = Flask(__name__)
with app.app_context():
    print(render_template_string("[{{ nick|default('匿名') }}]", nick=""))
3.

msg 的值是 <b>hi</b>,模板是 {{ msg }},用 render_template 輸出(檔名是 .html)。回應內容是什麼?

4.

下面哪一個寫法有伺服器端模板注入(SSTI)的風險?

5.

留言內容來自使用者,模板寫成 {{ comment|safe }},會發生什麼事?

第 1 次作答

重點整理

  • view 函數準備資料,模板負責排版;模板預設放在 templates 資料夾
  • {{ }} 輸出、{% %} 控制、{# #} 註解;過濾器用 | 串接
  • default 預設只處理沒定義的變數;loop.index 從 1 開始;for 可以接 else
  • .html 模板會自動跳脫,這是 XSS 的主要防線;safe 會關掉它,不要用在使用者輸入上
  • render_template_string 的模板本身不可以接使用者輸入

現在每個模板都要重複寫一次頁首與導覽列。下一課用模板繼承解決這件事,並加上 CSS 與圖片。

這一課完成了!
Next lesson:版型與靜態檔案
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。