Learning Objectives
- 用 extends、block、super() 讓多個頁面共用同一個版型
- 分辨 include 與 macro 的用途,並把重複的片段抽出來
- 用 url_for 引用 static 資料夾的 CSS 與圖片,並說明瀏覽器怎麼載入它們
- 用 errorhandler 自訂 404 與 500 錯誤頁,並回傳正確的狀態碼
每一頁都長得差不多
一個網站的頁面通常有相同的頁首、導覽列與頁尾,只有中間的內容不同。如果每個模板都複製一份,改一個連結就要改幾十個檔案,而且一定會漏掉幾個。
Jinja2 的模板繼承解決這件事:寫一份 base.html 當版型,在會變動的地方挖區塊(block),其他頁面只需要填這些區塊。
<!-- templates/base.html -->
<title>{% block title %}課程筆記{% endblock %}</title>
<nav>首頁 | 關於</nav>
<main>{% block content %}{% endblock %}</main>
<!-- templates/home.html -->
{% extends 'base.html' %}
{% block content %}<h1>歡迎</h1>{% endblock %}
逐步看:兩個頁面共用一個版型
Python 這邊和上一課沒有差別,view 函數只指定要畫哪個模板。請到輸出區的檔案分頁打開 base.html、home.html、about.html,比較兩個回應的 <title> 與 <main>。
繼承的規則,以及 include 與 macro
繼承有三條要記住的規則:
{% extends %}要放在模板的第一個標籤- 子模板寫在區塊外面的內容不會輸出:內容一定要放進某個
{% block %} - 想保留父模板的內容再加東西,用
{{ super() }}
抽出重複內容還有另外兩個工具:
| 工具 | 用途 | 寫法 |
|---|---|---|
include |
插入一段固定的 HTML,可以讀到目前的變數 | {% include '_nav.html' %} |
macro |
像函數一樣帶參數的片段,放在另一個檔案時要先匯入 | {% from '_macros.html' import badge %} 然後 {{ badge('新', color='red') }} |
一段內容只是搬位置用 include;同一段 HTML 要依參數變化好幾次(標籤、表單欄位、卡片)用 macro。檔名開頭的底線是常見的習慣,表示這個檔案是片段,不會單獨畫成頁面。
要注意:用 import 匯入的巨集預設讀不到呼叫端的變數,需要的值要當參數傳進去。
逐步看:include、巨集與靜態檔案
範例的 static 資料夾有一個 CSS 和一張 SVG 圖。請看 url_for 算出來的網址,以及後面實際請求這些檔案時的 mimetype。
靜態檔案怎麼被載入
放在 static 資料夾的檔案不經過 view 函數,Flask 會直接回傳。開發時由 Flask 處理;正式環境通常交給 Nginx 這類網頁伺服器(第 19 課)。
app.py
static/
style.css
logo.svg
templates/
base.html
打開一個有一個 CSS、一張圖的頁面,瀏覽器總共送出 3 個請求:先要 HTML,讀到 <link> 和 <img> 之後再要另外兩個。下面的練習裡,「請求紀錄」會把這些請求列出來。
引用時一律用 url_for,不要寫死 /static/style.css:網站如果部署在 /myapp/ 這樣的子路徑底下,寫死的網址就會失效,url_for 會算出正確的結果。
static 資料夾是公開的。 任何人知道網址就能開,不需要登入。不要把還沒公開的資料、成績、設定檔放在裡面。
逐步看:自訂錯誤頁
四個請求會走到四種結果:正常、程式呼叫 abort、網址不存在、程式出錯。請注意後面三種分別從哪裡進到錯誤處理函數。
錯誤頁要注意的兩件事
狀態碼要自己寫。 剛才的任務六如果只寫 return render_template('404.html'),畫面看起來一樣,但狀態碼是 200。Flask 文件特別提醒這一點:處理函數的回應不會自動帶上它處理的狀態碼。
除錯模式下看不到 500 頁面。 開著 --debug 時,發生例外會顯示互動式的除錯畫面,不會呼叫你寫的 500 處理函數。要測試 500 頁面,請關掉除錯模式再試。第 2 課說過,除錯模式不可以用在正式環境。
動手做:首頁與 404 共用版型
templates/base.html 已經放好了,內容是:
<title>{% block title %}讀書會{% endblock %}</title>
<nav>首頁</nav>
<main>{% block content %}{% endblock %}</main>
完成兩件事(程式最下面的測試不要改):
- 讓
HOME繼承base.html:title區塊是「首頁 - 」加上原本的標題,content區塊是<h1>歡迎</h1>。可以參考已經寫好的NOT_FOUND - 補上 404 的錯誤處理函數,並回傳正確的狀態碼
想一想
base.html 是 A{% block x %}B{% endblock %}C。子模板如下,輸出是什麼?
{% extends 'base.html' %}{% block x %}{{ super() }}D{% endblock %}
同樣的 base.html,子模板如下,輸出是什麼?
{% extends 'base.html' %}{% block x %}D{% endblock %}外面
下面的程式會印出什麼?
from flask import Flask
app = Flask(__name__)
@app.errorhandler(404)
def nf(e):
return "找不到"
print(app.test_client().get("/nope").status_code)
一個頁面引用一個 CSS 檔和兩張圖片,瀏覽器沒有快取。打開這一頁總共會送幾個請求?
url_for('static', filename='style.css') 比直接寫 /static/style.css 好在哪裡?
重點整理
extends放在第一行,內容寫進block,super()保留父模板的內容- 固定片段用
include,帶參數的片段用macro static資料夾公開,用url_for('static', filename=…)引用;瀏覽器讀到 HTML 之後才會另外要 CSS 與圖片errorhandler回傳時要自己帶上狀態碼;除錯模式下看不到 500 頁面
到這裡頁面都是單向的。下一課開始接收使用者輸入:表單、驗證,以及送出之後為什麼要轉址。