iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 22
1
Modern Web

從LINE BOT到資料視覺化:賴田捕手系列 第 22

第 22 天:Flask:Jinja2 製作網頁模板

第 22 天:Flask:Jinja2 製作網頁模板

  昨天利用內容傳遞網路的資源,成功的做出了一份漂亮的網頁,是不是對網頁設計燃起了一絲興趣呢?還記得我們昨天做的網頁安插了一堆空的連結吧,就是那些href後面應該加上連結網址(url)結果卻只看到href="#",光是瀏覽列大概就有 8 個了。沒錯,今天我們就是要來做更多網頁,並把連結慢慢補完(!?),就從"從頭開始"這個分頁開始吧。
  首先,我想要保留主頁(home.html)的<head>裡面那些設定,再來,我也希望每一個分頁都有相同的導覽列<nav>,和最底下的<footer>。那要更改的根本只剩下中間內容的部分而已嘛。也是啊,這樣網頁才有一致性。那麼就來動手囉:

<!DOCTYPE html>
<html lang="zh-TW">
<head>
以下略

  不會吧,真的要這樣一個一個打,我們昨天都做的那麼辛苦了,今天為了一個類似的分頁,居然要從頭開始?沒事的沒事的,有一個東西叫做複製貼上,我們可以用複製貼上快速產生一個新的檔案,然後把想保留的地方保留下來,想更動的地方稍做更動就好。是的,這當然是個方法,但是當網頁數量一多,或是網頁越來越複雜,有時我們會連要更動的地方在哪都要在 HTML 5 程式碼堆裡找好久才找得到,還會怕沒看清楚一不小心刪錯程式碼。難道沒有更好的方法嗎?有的,這就是為什麼我們要學 flask 的原因。
  開發 flask 的團隊同時也開發了一個與它息息相關的套件 Jinja2。Jinja2 這個套件強大的地方在於,它除了可以幫我們製作網頁模板,讓許許多多的分頁都能套用同一套模板外,更厲害的,還可以叫 Python 幫我們寫 HTML 5 的程式碼。嚇到了吧。不囉嗦,馬上就來看看怎麼做。
  要用 Jinja2 做網頁模板,第一件事是找出我們要做的網頁裡共通的部分。以我們昨天設計的網頁為例,在<head>當中相關的設定我希望能套用到同一系列的網頁上,唯一可能會更改的是<title>網頁標題的部分。因此做為模板的<head>會變成這樣:

<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <!-- 提供 ico 檔案 -->
  <link rel="icon" href="{{ url_for('static', filename='img/alpaca_logo.ico') }}">

  <!-- 引入 cdn CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+TC&display=swap">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.css">  

  <!-- 引入我們自己的 CSS -->
  <link rel="stylesheet" href="{{ url_for('static', filename='css/custom.css') }}">

  <!-- 直接定義 CSS -->
  <style> body {font-family: 'Noto Sans TC', sans-serif;}</style>

  <!-- 可供更改的部分 -->
  <title>{% block title %}{% endblock %}</title>

</head>

  上面程式碼最後面一段,看到<title>標籤當中,出現了神祕的{% block title %}{% endblock %},你可以想像成在模板中,我們先在這個位置挖了個洞,預留一個空位,等之後套用這個模板的網頁就可以填上。這就是 Jinja2 運作的方式。
  接著來看看<body>

<body>
  <!-- 希望能固定出現在每個網頁的 nav -->
  <nav>
中間略
  </nav>

<main>
  <!-- 可供更改的部分 -->
  {% block main %}{% endblock %}
</main>

<!-- 希望能固定出現在每個網頁的 footer -->
<footer>
中間略
</footer>

  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

  <!-- 可供更改的部分 -->
  {% block script %}{% endblock %}
</body>

  在<body>當中,我們希望導覽列能夠固定出現在這一系列的網頁上,因此在模板中要認真的把<nav>當中所有的程式碼都打出來。接著,從<div class="jumbotron"><div class="container">這些則是隨著不同頁面而更改的內容,因此不需要放在模板裡。然而,要在模板中預留空位,讓將來我們套用了模板之後還可以寫入<div class="jumbotron"><div class="container">等等的。怎麼做呢?當然就是加上一段{% block main %}{% endblock %}

  最後,也許在不同的頁面上,需要引入不同的 JavaScript,因此也預留一個位置給 Javascript:{% block script %}{% endblock %}。這樣就做好我們的模板了,先把這麼模板儲存為base.html然後放進資料夾 template 吧:

D:\alpaca_fighting>tree /F
Folder PATH listing
Volume serial number is 9C33-6XDD
D:.
│   Procfile
│   requirements.txt
│   runtime.txt
│   config.ini
│   clock.py
│   app_day_22.py
│
├───templates
│       base.html
│       home.html
│
├───custom_models
│       PhoebeTalks.py
│       utils.py
│       CallDatabase.py
│       PhoebeFlex.py
│
└───static
    ├───img
    │       alpaca_logo.ico
    │
    └───css
            custom.css

  這樣一來我們就有了模板base.html。現在讓我們回過頭來試著修改一下home.html,看看套用了base.html之後,homt.html的程式碼可以寫得多簡單?

<!-- 宣告我們要套用模板 -->
{% extends "base.html" %}

{% block title %}賴田捕手{% endblock %}

{% block main %}
<div class="jumbotron">
  中間略
</div>
<div class="container">
  <div class="row desc">
  <div class="col">
    <h2><i class="fas fa-kiwi-bird"></i>  輕鬆</h2>
    <p>中間略</p>
    </div>
  <div class="col">
      <h2><i class="fas fa-smile-wink"></i>  愉悅</h2>
    <p>中間略</p>
    </div>
  <div class="col">
      <h2><i class="fas fa-leaf"></i>  無害</h2>
    <p>中間略</p>
    </div>
  </div>
</div>

{% endblock %}

  讓我來說明一下這是怎麼運作的。開頭我們用{% extends "base.html" %}宣告我們要偷懶了,要套用已經寫好而且放在 template 資料夾裡面的base.html了。接著,還記得我們在base.html模板裡挖了 3 個洞嗎?第一個洞{% block title %}{% endblock %},這回我們用{% block title %}賴田捕手{% endblock %}填滿了。而這個洞,我們剛好把它挖在<title>的標籤裡,因此經過 Jinja2 的詮釋,瀏覽器就知道我們要寫的其實是<title>賴田捕手</title>。有點概念了嗎,很好。那麼第二個洞是{% main block %}{% endblock %},這回我們用一長串的內容去填滿,而這一長串的內容,會被放在模板base.html相對應的洞裡,並交給瀏覽器。最後一個洞{% block script %}{% endblock %},在這次設計的頁面裡,我們並沒有想要加入的程式碼,因此就先空著不動它,這樣就完成了。Python 檔案裡的路由不需要更動一樣是:

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

  這樣就成了,試著把它推向 Heroku 看看成果吧,套用模板base.html的結果應該跟昨天把所有 HTML 5 程式碼都寫在home.html裡是一模一樣的。

https://ithelp.ithome.com.tw/upload/images/20190930/20120178sMN2Surta2.png
圖一、套用模板base.html之後做出來的網站。紅色的字為我們在模板中挖的洞,讓套用該模板的home.html可以填入不同的內容。

  既然學會了 Jinja2 的網頁模板,不如打鐵趁熱,讓我們再做一份網頁吧?

{% extends "base.html" %}

{% block title %}賴田捕手:重頭開始{% endblock %}

{% block main %}

  <div class="container welcome">
      <div class="row">
      
      <h1><i class="fas fa-baseball-ball"></i> 賴田捕手</h1>
      <p>  如果有人真的有興趣聽的話,那麼第一件事情想必是關於我是不是個魯蛇阿宅,不然就是我的身高體重,我平常都吃些什麼,或是家裡眷養了幾隻草尼馬等等不登大雅之堂的問題。不過呢,我並不打算說這些。事實是,我對於討論這些東西一點興趣也沒有。首先得知道的是,草尼馬們並不喜歡被人說三道四的。他們極度纖細敏感,非常重視個人隱私,重視到即使你直直瞪進他們的眼睛,你還是猜不透他們在想些什麼。我並不是在說他們城府很深什麼的,而是因為如果我還不打算進入正題的話,字數限制就要到了。因此,我只準備說一些這半年多來,我從Python、到LINE BOT、到heroku、到資料視覺化,搖搖晃晃跌跌撞撞而又漫無目的的心路歷程。這一路過來,我接受了很多人的幫助,認識或不認識,我都點滴在心。想利用這次鐵人賽的機會,把做一隻LINE BOT的程序有系統的記錄下來,將手頭上好不容易掌握住的東西梳理一遍,也算是權充感謝。關於如何解釋這隻LINE BOT的腳色定位,想了一想,覺得還挺像是個人秘書的。從收集信息,數據處理,到資料視覺化。因此這個鐵人賽的主題,即是從LINE BOT到資料視覺化的30天:賴田捕手。</p>
      </div>
    </div>

{% endblock %}

{% block script %}
<script>
  alert("從頭開始其是套用模板做出來的!");
</script>
{% endblock %}

  一樣,一開始我們用{% extends "base.html" %}來宣告我們想要套用位在 template 資料夾裡的模板base.html。接著分別在{% block title %}{% endblock %}{% block main %}{% endblock %}{% block script %}{% endblock %}三個挖好的洞中加入新的內容。因為我們的模板base.html中已經引用過 Font Awesome 的內容傳遞網路資源了,因此在這段程式碼中,我可以直接利用 Font Awesome 提供的元素<i class="fas fa-baseball-ball"></i>而不需要再做一次引用的宣告。我把這份檔案存成from_start.html,並在 Python 檔案中加入一個路由把from_start.html放上去:

@app.route("/from_start")
def from_start():
    return render_template("from_start.html")

  最後記得在base.html中放入連結的位置。以我而言,我想要放在導覽列中「從頭開始」的連結中。

<li class="nav-item">
  <a class="nav-link" href="#">從頭開始</a>
</li>

  好了,把它們推上 Heroku 上試試看吧!

https://ithelp.ithome.com.tw/upload/images/20190930/20120178d0pa0LXxQs.png
圖二、利用 Jinja2 套用模板base.html做出的另一個網頁

  太棒了,用模板又快速做出一份網頁了!

  今天我們介紹了如何用 Jinja2 挖洞來製作網頁模板,以及如何套用 Jinja2 製作出來的網頁模板並把需要的內容放入洞中,相關的程式碼我已經放到 Github 上面了,有興趣的人可以到這邊來看看。今天有提到,Jinja2 的強大之處是,它甚至可以幫我們寫 HTML 5 的程式碼。怎麼辦到的呢?我明天會花一個篇幅的內容好好的來介紹一下。最後,今天相關的內容我是根據以下的教學寫成的,有興趣的也可以過去瞧瞧。謝謝大家!

參考資料

➀ shaoeChen Flask Jinja 樣版繼承
➁ Flask pythonise Jinja 樣版繼承


上一篇
第 21 天:Flask:裝飾我們的網頁
下一篇
第 23 天:Flask:Jinja2 傳送變數與操作
系列文
從LINE BOT到資料視覺化:賴田捕手30

尚未有邦友留言

立即登入留言