Python Web Flask — Jinja2樣版引擎的使用

Sean Yeh
Python Everywhere -from Beginner to Advanced
19 min readAug 27, 2020

上一篇文章我們學會了如何起始一個Flask網站。以及使用@route來增加不同的路由。然而到目前為止,我們製作的網站頁面中都只有靜態的文字。這樣子的水準,不論是對於一般非商業網站或者是商業網站來說,都嫌太過於簡陋。「網站至少要有HTML在裡面吧」這樣子的需求,不算過分。可是,如果我們單純透過Python來產出一頁頁HTML標籤,並不是一件輕鬆的事,程式設計師必須花費大量的時間處理字串與字串的連結,以及解決HTML特殊字元轉換的工作。

所幸Flask對此有更好的解決方案。之前提到過,Flask預設使用Jinja2樣版引擎。透過使用靈活的Jinja2範本,將HTML頁面與後台應用程式聯繫起來,達到簡化HTML輸出的目的。下面我們就來看看要如何使用Jinja2:

Jinja(じんじゃ)在日文裡面是「神社」的意思。在這裡是指Python的一個元件庫。如果用過其他樣板元件庫的話,諸如Smarty 或者是 Django,應該會對 Jinja2感到熟悉。它主要被設計成兼具彈性、快速且安全的元件庫。

render_template:實現範本

語法:render_template(“網頁檔案名稱.html”)

還記得上一篇的home函式吧,我們就從這裡開始,稍後我們會透過改寫這個程式碼來說明 render_template 的使用方式:

@app.route("/index")@app.route("/")def home():    return "This is Home Page"

使用 Flask 開發網頁的時候,可以透過 render_template() 這個函式輕鬆地呈現出 HTML 網頁的樣式。其使用方法如下:

首先,要先從flask裡面匯入render_template

from flask import render_template

然後,我們可以修改home函式的return部分:

@app.route("/index")@app.route("/")def home():    return "This is Home Page"

把程式碼修改為下面:

@app.route("/index")@app.route("/")def home():    return render_template('home.html')

如果執行這個程式碼的話,render_template會載入home.html檔案到首頁。眼尖的朋友應該注意到了,我們雖然在render_template()裡面註明了home.html,但是卻沒有告訴程式home.html到底放哪裡。難道是根目錄嗎?還是其他的地方?

依照規則,我們應該把HTML檔案放在templates目錄中,這樣子render_template才有辦法載入HTML檔案。如下圖所示,這個是本專案的檔案結構。一但將所有的HTML檔案放在templates目錄中,hello.py才可以透過render_template讀取到它們。

HTML範本檔案可以放置一般的文字、HTML標籤內容、變數甚至於是控制邏輯。

我們可以一一測試看看。

首先,我們建立一個空白html頁面。

<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
</head>
<body>
</body>
</html>

文字與HTML標籤

接下來,我們試著在這個頁面放入一般的文字、HTML標籤內容。

<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
</head>
<body><h1>Hello Word</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus ducimus eveniet laudantium voluptatem labore, atque perferendis reiciendis quas ipsam a est assumenda eos distinctio molestiae nam itaque, cum voluptates eligendi.</p>
<h1>This is my first Page</h1><p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Maiores nemo maxime culpa, iure, minima ducimus magni non hic similique tempore impedit necessitatibus eum! Sunt adipisci porro aperiam fugit magnam delectus.</p></body>
</html>

啟動伺服器測試看看。如果您剛才曾經啟動過伺服器,請重新啟動。切記:每次修改內容,就需要重新啟動伺服器,才可以看到變化。

結果應該會跟上面的畫面一樣,剛剛輸入的<h1>與<p>裡面的內容都顯示出來。到這裡應該沒有什麼問題吧。透過這樣的方法,你可以產生很多很多「靜態」的頁面。(還有一個關於靜態檔案的問題,容待下一篇再說明)

傳送參數與變數

語法:render_template(“網頁檔案名稱.html”, 參數與區域變數)

網頁如果不要只是靜態顯示的話,就需要傳遞參數或變數給網頁作為內容。還記得之前我們試著在網址後面,傳遞name參數。讓頁面顯示與該參數組合後的字串。接下來,我們要試著傳參數到頁面上。(程式碼如下)

@app.route("/hello/<name>")def hello(name):    return f"{name}, Welcome. This is Home Page"

現在我們就來修改這組程式碼。試著透過render_template來輸出參數到HTML頁面上。

首先,我們建立一個hello.html頁面,這個頁面是要給hello函式使用的(您也可以複製前面的home.html)

改寫程式:我們把return的部分依照之前的方式改為render_template。

其中要注意的是,在hello.html後面,還要將上另外一組參數name,並且讓name = name。這是什麼意思?第一個name 是指版型hello.html上面會用到的參數,而後面的name則是指透過@route路由傳遞進來hello()函式的name。也就是/hello/<name>的name與hello(name)的name。不信的話,您可以試著改參數名字看看。

@app.route("/hello/<name>")def hello(name):    return render_template('hello.html',name = name)

程式改完後,我們要回頭去修改hello.html。修改的重點只有一個,請加上{{name}} 來接收路由傳遞的參數結果,其他地方,您可以隨意修改。

{{name}}可以放多次,如下HTML碼,您會發現{{name}}分別被放在三個不同的位置上。

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Hello {{name}}</title></head><body><h1>{{name}}, Welcome. This is Home Page</h1><p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus ducimus eveniet laudantium voluptatem labore, atque perferendis reiciendis quas ipsam a est assumenda eos distinctio molestiae nam itaque, cum voluptates eligendi.</p><h1>This is {{name}}'s first Page</h1><p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Maiores nemo maxime culpa, iure, minima ducimus magni non hic similique tempore impedit necessitatibus eum! Sunt adipisci porro aperiam fugit magnam delectus.</p></body></html>

執行伺服器,輸入http://127.0.0.1:5000/hello/Sean 結果顯示如下:

上面程式碼中,name = name的地方,我們也可以使用**locals()來代替。**locals()的意思是傳遞所有的參數與區域變數。

return render_template('hello.html',**locals())

Jinja2 這個套件強大的地方在於,它可以幫我們製作網頁模板,讓許許多多的分頁都能套用同一套模板。

用 Jinja2 做網頁模板

Jinja2樣板語言

Jinja2樣板可以接受任何以文字為基礎的檔案副檔名,例如.html、.xml等。樣板語言,除了顯示普通內容外,還可以顯示變數,同時也有if statement和for迴圈指令。也可以在上面進行標籤、註釋等。

變數

樣板要顯示的變數,可以是一般的變數,也可以是字典(dict)或串列(list)。Jinja2樣板的語法與Python的語法有些不同處,使用上要注意。

串列(list):Python中的語法是 list_name[num]而在樣板語言則是:{{list_name.0}}

例如:

list_foods = ['apple','banana','candy']
//Python
list_foods[0]list_foods[1]list_foods[2]
//Template
{{list_foods.0}}{{list_foods.1}}{{list_foods.2}}

字典(dict):Python中的語法是 dict_name[key]而在Template語言則是:{{dict_name.key}}

例如:

dict_foods = {'1':'apple','2':'banana','3':'candy'}
//Python
dict_foods['1']dict_foods['2']dict_foods['3']
//Template
{{dict_foods.1}}{{dict_foods.2}}{{dict_foods.3}}

流程控制

可以控制程式流程的選擇、巡迴、傳回等。

if statement 判斷

用來判斷所指定的條件是否滿足,根據判斷的結果決定執行哪一個部分的程式碼。在這個部分Jinja2樣板與Python的敘述類似。我們以user為例:

{% if user %}    歡迎,{{ user }}!{% else %}    歡迎,第一次來本站。{% endif %}

for迴圈

在Jinja2樣板中,可以使用for迴圈來顯示list或者是dict資料。使用方式與Python類似。在語法上要用{% for… %}與{% endfor %}來包裹資料項目。例如下面的程式碼:

<ul>    {% for item in news %}        <li>{{ item }}</li>    {% endfor %}</ul>

你也可以將兩個組合起來使用:

{% if users %}  <ul>    {% for user in users%}      <li>{{ user.name }}</li>    {% endfor %}  </ul>{% endif %}

範本繼承

開發過網頁的人應該都知道,一個網站中,很多頁面的部分內容是一樣的,這些內容如頁首、頁尾、以及導覽區、廣告區等等。Jinja2樣板讓我們可以將網頁裡內容一樣的部分抽出來放在基本樣板中,並且套用到同一系列的子樣板網頁上。透過這樣子的方式,就不需要重複製作多次一樣的部分。

基本樣板

這個樣板是用來存放共用內容的檔案。一般我們會將它命名為base.html,用這個檔案來設定網站的HTML基本架構。你可以將共通的部分放在這裡:

例如:<html>、<head>與<body>,以及<head>裡面放頁面標題的<title>、頁面關鍵字與說明的<meta>、CSS樣式檔的<link>與javaScript檔案的<script>。

子樣板

由於基本範本是HTML頁面共通的部分,在製作上我們不再需要重複這部分的內容,只要在其他頁面裡繼承基本範本的內容就可以。繼承的方式是在子樣本中使用{% extends %}這個關鍵字來呼叫base.html。這表示這個子範本繼承了base.html範本。

{% extends "base.html" %}

要注意的是,extends必須是子範本中的第一個標籤。

我們可以用{% block content %}{% endblock %}放基本範本所沒有的內容。

{% block content %}{% endblock %}

使用block區塊是子樣板的關鍵字,content是子範本本體的名稱。同一個頁面中,content的名稱不可以一樣。

實際使用

下面的base.html基本樣板使用了bootstrap的CSS框架。中間使用了{% block body %}{% endblock %} 來給子樣板使用。

<!DOCTYPE html><html lang="zh-tw">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{site_name}}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/hello/sean">Hello Sean</a>
</li>
</ul>
</div>
</nav>
<div class="container">{% block body %}{% endblock %}</div><script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
</body>
</html>

hello.html子樣板。在子樣板裡面,第一行必須是使用extends語法把基本樣板拉進來。之後,可以放block區塊,在這裡我們放了組block,名稱剛好等於基本樣板裡面的名稱( 一樣是 {% block body %},這樣兩者就可以對應在一起了)。

{% extends "base.html" %}{% block body %}<div class="row"><div class="col">
<h1>{{name}}, Welcome. This is Home Page</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus ducimus eveniet laudantium voluptatem labore, atque perferendis reiciendis quas ipsam a est assumenda eos distinctio molestiae nam itaque, cum voluptates eligendi.</p>
</div>
<div class="col">
<h1>This is {{name}}'s first Page</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Maiores nemo maxime culpa, iure, minima ducimus magni non hic similique tempore impedit necessitatibus eum! Sunt adipisci porro aperiam fugit magnam delectus.</p>
</div>
</div>
{% endblock %}

基本上來說,就是在block的地方挖了個洞,預留一個空位,等之後套用這個樣板的頁面就可以填上。這就是 Jinja2 運作的方式,下一回我們會來討論關於傳送資料的方式。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。