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 07

版型與靜態檔案

模板繼承、include、巨集、CSS 與圖片、自訂錯誤頁
Interaction實作 25 minute(s)

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、網址不存在、程式出錯。請注意後面三種分別從哪裡進到錯誤處理函數。

互動練習:替網站加上版型與錯誤頁

這個網站有三個頁面,其中「聯絡」還沒有使用版型。

改完程式或模板,按「存檔並重新啟動」,再按瀏覽器的 ↻ 或實際操作一次,任務才會判定。任務四要看的 <title> 不會顯示在畫面上,請到「回應原始碼」面板確認。

任務三可以參考 about.html 的寫法。

錯誤頁要注意的兩件事

狀態碼要自己寫。 剛才的任務六如果只寫 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>

完成兩件事(程式最下面的測試不要改):

  1. 讓 HOME 繼承 base.html:title 區塊是「首頁 - 」加上原本的標題,content 區塊是 <h1>歡迎</h1>。可以參考已經寫好的 NOT_FOUND
  2. 補上 404 的錯誤處理函數,並回傳正確的狀態碼
你的輸出
預期輸出

想一想

1.

base.html 是 A{% block x %}B{% endblock %}C。子模板如下,輸出是什麼?

{% extends 'base.html' %}{% block x %}{{ super() }}D{% endblock %}
2.

同樣的 base.html,子模板如下,輸出是什麼?

{% extends 'base.html' %}{% block x %}D{% endblock %}外面
3.

下面的程式會印出什麼?

from flask import Flask
app = Flask(__name__)

@app.errorhandler(404)
def nf(e):
    return "找不到"

print(app.test_client().get("/nope").status_code)
4.

一個頁面引用一個 CSS 檔和兩張圖片,瀏覽器沒有快取。打開這一頁總共會送幾個請求?

5.

url_for('static', filename='style.css') 比直接寫 /static/style.css 好在哪裡?

第 1 次作答

重點整理

  • extends 放在第一行,內容寫進 block,super() 保留父模板的內容
  • 固定片段用 include,帶參數的片段用 macro
  • static 資料夾公開,用 url_for('static', filename=…) 引用;瀏覽器讀到 HTML 之後才會另外要 CSS 與圖片
  • errorhandler 回傳時要自己帶上狀態碼;除錯模式下看不到 500 頁面

到這裡頁面都是單向的。下一課開始接收使用者輸入:表單、驗證,以及送出之後為什麼要轉址。

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