iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Software Development

都是 P 開頭的程式語言,你是在 py 啥啦系列 第 14

[14] [燒瓶裡的部落格] 04. Templates 模板

我們已經寫好驗證的 views,但是如果現在啟動服務的話
無論開啟哪個 URL,都會看到一個TemplateNotFound錯誤
這是因為 view 使用了render_template(),但是 template 檔案還沒有寫
模板檔案會存放在 flaskr 資料夾內的templates文件夾內

Flask 使用Jinja模板引擎來渲染模板,範例中會使用模板來顯示在使用者瀏覽器中的 HTML

在 Flask 中, Jinja 被設定為自動轉譯 HTML 模板中的任何資料,這代表直接渲染內容是安全的
任何使用者輸入的可能被瀏覽器執行的內容,如<>,會被轉譯成安全的值
轉譯的結果在瀏覽器中看起來一樣,但是不會被瀏覽器執行

Jinja 看上去以及執行起來很像 Python
在 Jinja 中,任何位於{{}}之間的變數會被輸出成文字
程式判斷式則是放在{%%}之間,例如iffor

與 Python 不同,不是使用縮排分隔,而是使用特殊 tag 分隔
因為區塊內的渲染出來的結果可能有自己的縮排

基本 Layout

與其在不同的頁面中重寫整個頁面的 HTML,應該讓每一頁的模板繼承一個基本的共用模板
接著在不同頁面中複寫需要修改的部分

flaskr/templates/base.html

<!doctype html>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
  <h1>Flaskr</h1>
  <ul>
    {% if g.user %}
      <li><span>{{ g.user['username'] }}</span>
      <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
    {% else %}
      <li><a href="{{ url_for('auth.register') }}">Register</a>
      <li><a href="{{ url_for('auth.login') }}">Log In</a>
    {% endif %}
  </ul>
</nav>
<section class="content">
  <header>
    {% block header %}{% endblock %}
  </header>
  {% for message in get_flashed_messages() %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
  {% block content %}{% endblock %}
</section>

g 在模板中自動可用!
根據g.user是否被設定(記得嗎?之前在load_logged_in_user中進行)
選擇要顯示使用者名稱和登出按鈕,還是註冊和登入按鈕?
而用於生成 view 網址的url_for()也是自動可用的,不需要手動指定

在頁面標題下面,content 內容的前面,模板會循環顯示 get_flashed_messages() 回傳的每個消息
可以用這個方式把之前在 view 中使用 flash() 保存的錯誤訊息顯示出來

There are three blocks defined here that will be overridden in the other templates:

模板中定義了三個 block,這些區塊內容會被其他模板複寫

  1. {% block title %}會改變當前瀏覽器分頁的 title

  2. {% block header %}改變頁面的標題

  3. {% block content %}每個頁面的具體內容,例如登入表單或者部落格文章

其他模板直接放在templates資料夾內
為了更好地管理檔案,屬於某個 blueprints 的模板會被放在與同名的資料夾內

註冊

flaskr/templates/auth/register.html

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Register">
  </form>
{% endblock %}

開頭使用{% extends 'base.html' %}對 Jinja 宣告這個模板繼承 base.html
並且需要載入相應的 block,所有複寫的內容必須位於{% block %}標籤中

一個好用的模式是把{% block title %}放在{% block header %}內部
這樣不但可以設定 title block,還可以把其值作為 header block 的內容,一舉兩得!

inputtag 使用了required屬性,告訴瀏覽器這些輸入框是必填的
如果使用者使用不支援持這個屬性的舊版瀏覽器,或者不是用瀏覽器發出的請求
那麼你還是要在 view 中驗證輸入資料
即使前端已經做了一些驗證,後端還是要完全檢查,這一點非常重要!

登入

這個模板除了標題和送出按鈕外,和註冊模板相同

flaskr/templates/auth/login.html

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Log In">
  </form>
{% endblock %}

註冊一個使用者

現在驗證模板已寫好,可以來測試了
啟動測試環境之後開啟http://127.0.0.1:5000/auth/register頁面

在不填寫表單的情況,嘗試點擊「Register」按鈕,瀏覽器會顯示出錯信息

嘗試在register.html中刪除required屬性後再次點擊「Register」按鈕
瀏覽器不會顯示錯誤,而頁面會重新載入並顯示來自於 view 中 flash() 錯誤訊息

填寫使用者名稱和密碼後會重定導向到登錄頁面
嘗試輸入錯誤的使用者名稱,或者輸入正確的使用者名稱和錯誤的密碼

真實情況下不要這麼做!會引發資安疑慮,弱點掃描不會過
因為提示駭客錯誤是來自哪個欄位,接著就可以使用枚舉法暴力破解

如果登錄成功,那麼會看到一個錯誤訊息
因為我們還沒有寫登入後要轉向的 view:index


上一篇
[13] [燒瓶裡的部落格] 03. Blueprints 和 View function
下一篇
[15] [燒瓶裡的部落格] 05. 靜態檔案
系列文
都是 P 開頭的程式語言,你是在 py 啥啦30

尚未有邦友留言

立即登入留言