iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
0
Modern Web

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

第 23 天:Flask:Jinja2 傳送變數與操作

  • 分享至 

  • xImage
  •  

第 23 天:Flask:Jinja2 傳送變數與操作

  昨天我們用 Jinja2 創造了一個網頁模板,讓我們可以快速地寫出一系列的外觀相似的網頁。但其實 Jinja2 的強大之處可不只於此。讓我們先看一下 Jinja2 官方說明手冊➀提供的範例:

<title>{% block title %}{% endblock %}</title>
<ul>
{% for user in users %}
  <li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
  • 第一行:<title>{% block title %}{% endblock %}</title>
      這就是我們昨天學到的,在<title>的標籤當中,挖一個洞,讓套用此模板的不同網頁可以填入不同的內容。

  • 第二行:<ul>
      這我看得懂,利用<ul>標籤宣告一個無序列清單(unordered list)的開始。

  第三行開始到第五行結束,官方說明文件向我們展示了 Jinja2 另一個強大之處:就是在 HTML 5 的檔案當中,運用類似 Python 的語法,快速的寫出 HTML 5 程式碼。

  • 第三行:{% for user in users %}
      就像是在 Python 當中,我們會用for 項目 in 可數(ˇ)數(ˋ)清單:一樣,開啟了一個for迴圈。

  • 第四行: <li><a href="{{ user.url }}">{{ user.username }}</a></li>
      隨著for迴圈的進行,而一行一行的在 HTML 5 檔案中加入這行程式碼。其中,{{ user.url }}{{ user.username }}是變數,隨著每次迴圈的不同而改變。在這個範例當中,user應該是一個 Python 的字典物件,而urlusername則是這個字典物件的鑰匙,透過{{ user.url }}{{ user.username }}將內容傳到 HTML 5 的檔案當中。

  • 第五行:{% endfor %}
      結束這個for迴圈。在 Python 中,程式會依照縮排來判斷迴圈的範圍。但在 HTML 5 當中,縮排不重要,因此需要這一行宣告for迴圈的範圍。就像 HTML 5 的標籤是成對使用一樣<tag></tag>,一個宣告開始,另一個宣告結束。

  用實際一點的例子再說明一次好了,假設我們手邊的 Python 檔案中有如下資料:

users = [{"username": "我", "url": "我-APP-的名字.herokuapp.com"},
         {"username": "你", "url": "你-APP-的名字.herokuapp.com"}]

  而我們在 HTML 的檔案中寫下:

<ul>
{% for user in users %}
  <li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>

  透過 Flask 的render_template()將可數(ˇ)數(ˋ)物件users從 Python 的檔案中傳送到 HTML 5 檔案:

@app.route("/jinja2_example")
def jinja2_example():
    return render_template("jinja2_example.html", users=users)

  得到user物件的 HTML 5 檔案,在 Jinja2 的作用之下,就可以寫出如下方的 HTML 5程式碼:

<ul>
  <li><a href="我-APP-的名字.herokuapp.com">我</a></li>
  <li><a href="你-APP-的名字.herokuapp.com">你</a></li>
</ul>

  怎麼樣,Jinja2 很厲害吧。有些時候我們根本不可能一個一個在 HTML 5 的檔案中將所有的程式碼都寫出來,這個時候 Jinja2 就是個最棒的幫手了。具體來說是什麼時候呢?比如說我們想要查詢草泥馬的訓練紀錄的時候。我們在第 18 天的時候有提到,藉由 psycopg2 的幫助,我們可以請貼心的 LINE 聊天機器人幫忙查詢草泥馬的訓練紀錄。不過,受限於介面的關係,當訓練紀錄一多了以後,在 LINE 的聊天室窗中觀看密密麻麻的資料實在不是太好的使用者體驗。
  現在,我們就利用 psycopg2 跟 Jinja2 這兩樣強大的工具,讓我們可以在網頁裡愜意的欣賞這一筆一筆的訓練資料吧!

  一樣,從 Python 當中藉由 psycopg2 寫一個查資料的函數:

def web_select_overall():
    DATABASE_URL = os.environ['DATABASE_URL']

    conn = psycopg2.connect(DATABASE_URL, sslmode='require')
    cursor = conn.cursor()
    
    postgres_select_query = f"""SELECT * FROM alpaca_training ORDER BY record_no;"""
    
    cursor.execute(postgres_select_query)
    
    message = []
    while True:
        temp = cursor.fetchmany(10)
        
        if temp:
            message.extend(temp)
        else:
            break
    
    cursor.close()
    conn.close()
    
    return message

  接著,在 flask 中做出一個路由:

@app.route("/show_records")
def show_records():
    python_records = web_select_overall()
    return render_template("show_records.html", html_records=python_records)

  意思是說,當有使用者請求"你-APP-的名字.herokuapp.com/show_records" 的時候,show_records()這個函數會執行,並呼叫我們剛才寫好的web_select_overall(),將資料庫中的表格alpaca_training所有訓練紀錄都調出來,並貼上records的標籤。最後,在render_template()的地方,把python_records傳給show_records.html這個 HTML 5 檔案中的變數html_records,經過 Jinja2 的詮釋,把完整的"show_records.html"傳給請求網頁的使用者。

  大概有概念了嗎?那麼最後我們要來寫的就是"show_records.html"

{% extends "base.html" %}

{% block title %}賴田捕手:訓練紀錄{% endblock %}

{% block main %}

  <div class="container">
    <div class="row align-items-start">
    <div class="col">
      <h1>訓練紀錄</h1>
    </div>

    <!-- 將所有草泥馬的訓練紀錄列在下面 -->
    {% for record in html_records %}
    <div class="row">
    <div class="col">
      <p>{{ record[0] }}<p>
    </div>
    <div class="col">
      <p>{{ record[1] }}<p>
    </div>
    <div class="col">
      <p>{{ record[2] }}<p>
    </div>
    <div class="col">
      <p>{{ record[3] }}<p>
    </div>
    <div class="col">
      <p>{{ record[4] }}<p>
    </div>
    </div>
    {% endfor %}
    <!-- 將所有草泥馬的訓練紀錄列在上面 -->

  </div>

{% endblock %}

  上面那段程式碼,我利用{% for record in html_record %},將html_record當中的項目(不可變更清單物件,tuple)一個一個取出來,接著,在執行每一個for迴圈中,將不可變清單當中的 5 個項目,分別是record[0]record[1]record[2]record[3]record[4],一個一個放到指定的位置上。如此,我們就可以利用 "你-APP-的名字.herokuapp.com/show_records" 這個網頁,查詢所有草泥馬的訓練資料囉。

https://ithelp.ithome.com.tw/upload/images/20191001/20120178w3N27C9JZP.png
圖一、草泥馬訓練全紀錄

  開始對於 Jinja2 有點熟悉的你,是不是已經在猜想,上面那段用 Jinja2 寫出來的程式碼,是不是還可以再更簡單一些呢?讓我們來試試:

{% extends "base.html" %}

{% block title %}賴田捕手:訓練紀錄{% endblock %}

{% block main %}

  <div class="container">
    <div class="row align-items-start">
    <div class="col">
      <h1>訓練紀錄</h1>
    </div>

    <!-- 將所有草泥馬的訓練紀錄列在下面 -->
    {% for record in html_records %}
    <div class="row">
    {% for item in record %}
    <div class="col">
      <p>{{ item }}<p>
    </div>
    {% endfor %}
    </div>
    {% endfor %}
    <!-- 將所有草泥馬的訓練紀錄列在上面 -->

  </div>

{% endblock %}

  太棒了,真的可以!

  這邊為了詳細說明 Jinja2 在 HTML 5 檔案中的運作,所以列出了所有草泥馬訓練紀錄的網頁上,除了一開始斗大的的「訓練紀錄」之外,我沒有再特別加上其他說明文字。但是實際觀賞起來,沒有欄位的說明,看起來果然還是很怪。那就讓我們試著加上欄位說明,順便見識 Jinja2 究竟能讓我們有多偷懶吧。

{% macro easy_row(data, tag) -%}
  {% for record in data %}
  <div class="row">
    {% for item in record %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
  </div>
  {% endfor %}
{%- endmacro %}

  首先,我們動用了一個在 Jinja2 裡面叫做巨集(macro)的東西,其實就可以類比成 Python 裡面的函數。當我們發現同一段程式碼會不停的在我們的網頁中重複出現時,就可以考慮使用巨集。而跟 Python 的函數一樣,在創造一個函數時,需要先用

def 函數的名稱(函數的參數):

來做宣告。Jinja2 在創造巨集時,則需使用

{% macro 巨集的名稱(巨集的參數) -%}

來開頭,並在巨集結束的地方,用

{%- endmacro %}

  當作結尾。

  完整地來看一看怎麼使用巨集吧:

<!-- 宣告一個巨集 -->
{% macro easy_row(data, tag) -%}
  {% for record in data %}
  <div class="row">
    {% for item in record %}
    <div class="col">
    <{{ tag }}>{{ item }}</{{ tag }}>
    </div>
    {% endfor %}
  </div>
  {% endfor %}
{%- endmacro %}

<!-- 宣告一些等等會用來創造欄位說明的變數 -->
{% set col_names = (("record_no", "alpaca_name", "training", "duration", "date"),) %}

<!-- 用巨集快速創造欄位名稱 -->
{{ easy_row(col_names, "h2") }}

<!-- 將所有草泥馬的訓練紀錄列在下面 -->
{{ easy_row(html_records, "p") }}

  首先我們宣告一個巨集easy_row(),它會接受兩個參數datatagdata是用來填入欄位的數據,而tag則是用來控制每一列文字的格式。接著,為了填入一列欄位說明的資料,我在 HTML 5 的檔案中創造了一個變數col_names,就像在 Python 中創造變數的方法類似,不過必須多一個set開頭:

{% set 變數名稱 = 變數內容 %}

  把創造的代表欄位名稱的變數col_names丟進easy_row()巨集當中,並且希望欄位名稱用<h2>作為格式。結果如圖二

https://ithelp.ithome.com.tw/upload/images/20191001/20120178oHrBgTpIYl.png
圖二、利用巨集快速的寫出漂亮的表格

  天啊,簡單不簡單,只靠短短幾行程式碼就造就了如此漂亮的表格,你能不喜歡 Jinja2 嗎?
  今天關於 Jinja2 的介紹就差不多到這裡了。除了今天介紹的{% for %}{% endfor %}{% macro -%}{%- endmacro %}{% set %}等等,Jinja2 也提供{% if %}{% endif %}這類的判斷句。有興趣的可以參考這些網路上的資源➁➂,包括介紹十分詳細用心的 Jinja2 官方使用手冊➀。今天用到的程式碼我也會放在 Github 上,大家有興趣的可以上去參考看看,謝謝大家。另外,如果覺得內容有哪些地方解釋得不夠清楚,或是想更加深入討論的,也非常歡迎大家在文章下面留言,我會盡量回覆的!祝大家有個愉快的夜晚囉!

參考資料

➀ Jinja2 官方說明手冊
➁ Flask pythonise Jinja 變數傳送
➂ Flask Jinja2 讀書筆記

註:對於此系列文有興趣的讀者,歡迎參考由此系列文擴編成書的 LINE Bot by Python,以及最新的系列文《賴田捕手:追加篇》
第 31 天 初始化 LINE BOT on Heroku
第 32 天 快速回覆 QuickReply 介紹
第 33 天 妥善運用 Heroku APP 暫存空間
第 34 天 妥善運用 LINE Notify 免費推播
第 35 天 製造 Deploy to Heroku 按鈕


上一篇
第 22 天:Flask:Jinja2 製作網頁模板
下一篇
第 24 天:Flask:表單的操作
系列文
從LINE BOT到資料視覺化:賴田捕手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言