2019年9月26日 星期四

Python 學習筆記 : Django 2 測試 (二) : 模板基礎與靜態檔案

二月底做了初步的 Django 2.0 測試後, 準備下一步是建立包含 jQuery UI 的模板, 但卻發現以前學 jQuery UI 時趕時間沒有把筆記寫好寫滿, 就花了一段時間補寫, 接著四月又開始忙授課, 然後是陪二哥大學申請入學面試 .... 直到最近二哥上大學了, 我才又重新回到 Django 軌道 (還好婉拒了 12 月的授課).

經過了大半年, Django 版本也從 2.1.7 升到 2.2.5 所以先更新版本吧! 更新 Django 可在 pip install 後加上 -U 參數 :

pip3 install django -U

本篇測試參考了下面幾本書中的範例加以改寫 :
  1. It's django - 用 Python 迅速打造 Web 應用 (楊孟穎, 袁克倫, 碁峰)
  2. Python 新手使用 django 架站的 16 堂課 (何敏煌, 博碩)
  3. Python 新手使用 django 架站技術實作 (何敏煌, 林亮昀, 博碩)
  4. Python 網頁程式交易 App 實作 (林萍珍, 博碩)
  5. Beginning Django (Daniel Rubio, Apress)
  6. Django 2 Web Development Cookbook 3rd Edition(Jake Kronika, Packt)
  7. 一次搞定所有 Python Web 框架開發百科全書 (佳魁, 劉長龍) 
  8. 科班出身的MVC網頁開發 : 使用Python + Django (佳魁, 王友釗)
Django 2 官方教學文件參考 :

https://docs.djangoproject.com/en/2.2/

Python 學習筆記索引參考 :

Python 學習筆記索引

本系列之前的筆記參考 :

Python 學習筆記 : Django 2 測試 (一) : 請求與回應處理

線上教材參考 :

The Complete Guide To Install Django And Configure It
2019 iT 邦幫忙鐵人賽 : From Django 1.11 to Django 2.1 系列
Django 2 By Example 全书翻译、踩坑及教程

做完 Django 的請求與回應處理測試後, 本篇繼續測試 Django 2 的模版與靜態檔案功能. 由前一篇測試中可以發現, 即使可以用多行字串表示法 (三個連續引號對) 提高 views.py 中的 HTML 代碼可讀性, 但是在 views.py 的處理函數中直接用 HttpResponse 輸出網頁 HTML 字串的方式會使網頁內容與控制邏輯混在一起, 不符合 Django 鬆耦合 (llosely-coupled) 精神, 也不利於前後端之分工, 增加維護上的困難. 解決辦法就是利用 Django 的模板 (Template) 功能.

所謂的模板基本上就是一個嵌入變數與控制標籤的網頁檔, 利用 views.py 傳送變數值給模板引擎, 它會將變數以所傳送的值替換以便生成最終的 HTML 網頁, 此過程稱為渲染或繪製 (render), 如下圖所示 :




在建立網頁模板之前有一個準備工作要做, 就是建立存放模板檔案與靜態檔案的目錄, 這些都是整個網頁專案要用的, 所以要建在上層專案目錄底下.


一. 基本模板用法 : 

基本用法是指從視圖處理函數 views.py 中傳遞字典變數到模板網頁中. 首先須在專案中建立存放模板網頁與靜態檔案的目錄.

1. 新增 templates 與 static 目錄 :

在 Django 中, 所有副檔名為 htm 或 html 之網頁模板網頁模板會集中放在上層專案目錄下的一個專用子目錄中 (名稱可自訂, 例如 /templates). 另外, 網頁中可能會用到的圖檔, CSS 樣式檔, 以及 Javascript 程式等靜態檔案, 這些檔案也是集中放在上層專案目錄下的一個專用子目錄中 (名稱可自訂, 例如 /static, 這是設定檔 settings.py 中的預設值), 也可以在其底下建立三個子目錄以分類存放圖檔, 樣式檔, 以及 Javascript 程式檔. 這些目錄在新增專案時不會自動產生, 需手動建立 :

D:\Python\test\mysite>mkdir templates
D:\Python\test\mysite>mkdir static
D:\Python\test\mysite>cd static
D:\Python\test\mysite>mkdir images
D:\Python\test\mysite>mkdir css
D:\Python\test\mysite>mkdir js

這樣整個專案目錄結構如下 :


mysite (上層專案目錄)
     |____ manage.py
     |____ mysite  (下層專案目錄)
     |              |____ __init__.py
     |              |____ settings.py
     |              |____ urls.py
     |              |____ views.py  (自行添加)
     |              |____ wsgi.py
     |____ templates (網頁模板目錄)
     |____ static (靜態檔案目錄)
                    |____ images
                    |____ css
                    |____ js


這樣便可將模板網頁存放於 templates 子目錄下, 專案要用到的靜態檔案則分別置於 images, css, 與 js 下, 但在使用模版與靜態檔案之前還必須在專案設定檔 settings.py 中設定路徑, 這樣 Django 才能存取到這些手動建立的目錄下的檔案.


2. 編輯 settings.py 中的模版引擎與目錄設定 :

(1). 設定網頁模板路徑 :

開啟下層專案目錄下的 settings.py 檔, 其中的 TEMPLATES 變數的 DIRS 屬性就是用來設定模板路徑的, 需填入模版目錄的絕對路徑, 可用設定檔最前面建立的專案根目錄變數 BASE_DIR 與上面所建立的模板目錄名稱 'templates' 串接組合而成 :

os.path.join(BASE_DIR, 'templates')

此指令會將根目錄路徑與 templates 結合為模板目錄的絕對路徑 :

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中的 BACKEND 屬性用來設定所使用模版引擎, 預設為 DjangoTemplates, 也可以改用第三方引擎例如 jinja2, 這樣 BACKEND 就要改為 django.template.backends.jinja2.jinja2. 預設的模版引擎其實就很好用了.

經過上面的設定後便可以編輯網頁模板檔案了 (詳下).


(2). 設定靜態檔案路徑 :

開啟 settings.py 可發現在建立專案時, 此設定檔最底下已經預設靜態目錄 STATIC_URL 為 '/static', 在其底下再加入一個靜態目錄絕對位址變數  STATICFILES_DIRS :

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]

經過以上設定後, 就可以在網頁模板中使用靜態檔案了, 使用前須先宣告要用模板標籤載入靜態檔案, 語法如下 :

{% load static %}   

此 static 為內建之 django.contrib.staticfiles 模組所提供之指令, 在專案設定檔 settings.py 的 INSTALLED_APP 串列變數中已定義, 有些書上使用 {% load static from staticfiles %} 或 {% load staticfiles %} 都是較舊的用法, 現在應改用 {% load static %}, 參考 :

What is the difference between {% load staticfiles %} and {% load static %}

注意, load 指令只要使用一次即可, 通常放在網頁的 head 元素內. 載入宣告後才可以用模板標籤指令 static 載入靜態檔案, 語法如下 :

樣式檔 : <link rel="stylesheet" href="{% static "css/style.css" %}">
程式檔 : <script src="{% static "js/myscript.css" %}"></script>
圖片檔 : <img src="{% static "images/logo.jpg" %}">

注意, 模板標籤內的引號不影響外面屬性的引號, 不須單雙交替. 另外在 HTML 部分, link 元素使用 href 屬性連結樣式檔; 而 script 元素則用 src 屬性載入程式檔.


3. 編輯模版網頁與 views.py 處理函數 : 

經過以上設定就可以使用模板語法來編輯模板網頁了. 模板網頁中可以嵌入如下三種元素 : 變數, 標籤, 以及註解 :


 模板語法元素 說明
 {{ 變數 }} 來自 views.py 的變數用來直接替換該位置內容
 {% 標籤 %} 標籤用來控制資料顯示邏輯
 {# 註解 #} 註解會被模板引擎忽略


其中標籤語法操作的對象其實也是從 views.py 處理函數傳遞過來的變數, 這些變數是透過呼叫捷徑函數 django.shortcuts.render() 傳遞過來的, 在 views.py 中使用 render() 繪製網頁前須先匯入 :

from django.shortcuts import render

這樣便可直接呼叫 render() 函數了, 其參數有三 :

render(request, "template.htm", dict)

第一個參數 request 是包裝了客戶端請求訊息的 HttpRequest 物件; 第二個參數是要繪製的模板網頁檔案名稱 (放在 /templates 底下), 第三個參數是要傳遞給模板網頁的變數組成之字典, 例如 :

#views.py
from django.shortcuts import render
def helloworld(request):
    dict1={‘name’: ‘Tony’, ‘score’: 80}
    return render(request, ‘hello.htm’, dict1)

然後在模板網頁中直接用字典的鍵 (例如 {{ name }} 與 {{ score }}) 就可以存取所傳送的變數了, 如下圖所示 : 




如果要傳遞的變數很多, 自行組成字典較麻煩, 這時可以呼叫 locals() 函數, 它會傳回所有區域變數組成的字典, 例如 {‘name’: ‘Tony’, ‘score’: 80}, 可直接作為 render() 的第三參數, 但要注意, locals() 函數只會傳回所有區域變數組成的字典, 全域變數不包括在內. 例如 :

#views.py
from django.shortcuts import render
def helloworld(request):
    name=Tony

    score=80

    return render(request, ‘hello.htm’, locals())

此處兩個區域變數 name 與 score 都會被 locals() 組成字典傳遞給模板. 在模板網頁中同樣是直接用字典的鍵 (例如 {{ name }} 與 {{ score }}) 存取所傳送的變數.




注意, 因為 locals() 會將全部區域變數打包成字典, 因此如果區域變數本身就是字典, 物件, 串列或元組等容器類型變數, 則在模板網頁中, 必須使用物件的點運算子來存取存取元素變數. 模板引擎會以下列順序來查找所傳遞的變數 , 一找到便停止 :


 查找優先順序 相對於 Python 說明
 1. 字典鍵 {{ dict.key }} dict["key"] 字典鍵亦可用整數, 例如 {{ dict.0 }}
 2. 物件屬性 {{ obj.attr }} obj.attr attr 為物件 obj 之屬性
 3. 物件方法 {{ obj.method }} obj.method() method() 為物件 obj 之方法
 4. 串列或元組元素 {{ list.0 }} list.0 list 為串列


如果是字典變數, 則點運算子後面為 key (文字數字均可); 如果是物件變數, 點運算子後面是其屬性; 如果是串列或元組, 則點運算子後面是其索引. 下圖為字典變數的例子 :




注意, 這裡 locals() 傳遞的變數是 {'dict1': {'name': 'Tony', 'score': 80}}, 因此藥用 {{ dict1.name }} 存取 name 變數, 而非 {{ name }}.

前一篇的 Hello World 測試可以改用模板來做, 步驟如下 :

(1). 編輯模板網頁檔 :

此例需要兩個網頁檔 : hello.htm 與 home.htm (均存放於 /templates 底下), 前者顯示顯示 "Hello", 以及從 views.py 傳遞過來的人名與時間訊息, 底下有一個回首頁的連結圖檔. 關於 HTML5 網頁參考 :

網頁技術速學筆記 (一) : HTML 基礎

<--! hello.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
  {% load staticfiles %}
  <link rel="stylesheet" href="{% static "css/hello.css" %}">
  <script src="{% static "js/hello.js" %}"></script>
</head>
<body>
  <p class="msg">Hello, {{ name }}, 現在的時間是 {{ now }}</p>
  <a href="/">
    <img src="{% static "images/home.jpg" %}" alt="回首頁">
  </a>
</body>
</html>

此網頁用到三種靜態檔案資源 : 樣式檔 (hello.css), 程式檔 (hello.js), 以及圖檔 (home.jpg), 須分別存放於 /static/css, /static/js, 與 /static/images 下.

另外編輯 home.htm 首頁檔 :

<--! home.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <b>歡迎來到我的首頁!</b>
</body>
</html>

此兩檔案要放在 /templates 底下.


(2). 編輯樣式檔 hello.css :

/* hello.css  */
.msg {
  font-weight: bold;
  text-shadow: navy 3px 3px 3px;
  }

此檔案要放在 /static/css 底下.


(3). 編輯程式檔 hello.js :

其功能就是彈出一個訊息框而已 :

/* hello.js */
alert("Hello!");

此檔案要放在 /static/js 底下.


(4). 編輯 views.py 檔 :

#views.py
from django.shortcuts import render
from datetime import datetime

def hello(request, name):
    now=datetime.now()
    dict1={'name': name, 'now': now}
    return render(request, 'hello.htm', dict1)

def home(request):
    return render(request, 'home.htm', {})

視圖處理程式定義了兩個函數 hello() 與 home(). 其中 hello() 會傳遞名字與時間變數到模板網頁 hello.htm; 而 home() 則傳遞空字典到模板網頁 home.htm, 不需要傳變數第三參數就傳空字典.


(5). 編輯 urls.py 檔 : 

#urls.py
from django.contrib import admin
from django.urls import path
from mysite.views import home, hello

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/<name>/', hello),
    path('', home),
]

此檔案處理 URL 路由, 第一個 path 是建立專案時預設的, 用來管理網站後台, 另兩個 path 則是將專案 URL 交由 views.py 中的函數處理, 所以開頭必須從 mysite.views 模組匯入 hello 與 home 這個函數. 也可以用 import mysite.views 匯入整個 views.py 模組, 但這樣 path() 的第二參數 (處理函數) 就要寫成 views.hello 與 views.home 了.

在上層專案目錄下啟動開發伺服器 :

python manage.py runserver 

然後於瀏覽器網址列輸入 http://127.0.0.1:8000/hello/Tony/ 結果如下 :




網頁載入後首先會執行 hello.js, 這會彈出一個顯示 'Hello' 的訊息窗, 按底下的 Home 圖檔會回到首頁.

D:\Python\test\mysite>python manage.py runserver     
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
September 26, 2019 - 16:37:36
Django version 2.2.5, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[26/Sep/2019 16:37:41] "GET /hello/Tony/ HTTP/1.1" 200 367


二. 進階模板用法 :

進階用法是指使用模板標籤指令來控制網頁的顯示方式, 主要包含 if 判斷 (分支) 標籤與 for 迴圈標籤, 語法除了結尾多了 endif 與 endfor 外, 與 Python 是一樣的. 另外模板引擎還提供過濾器 (filter) 可對要輸出的變數進行格式化或特定運算.


1. if 判斷 (分支) 標籤 : 

利用變數運算式的真假值控制輸出何種網頁 :


 單一分支 雙重分支 多重分支
 {% if 條件式 %}
 輸出網頁
 (% endif %}
 {% if 條件式 %}
 輸出網頁1
 {% else %}
 輸出網頁2
 (% endif %}
 {% if 條件式 %}
 輸出網頁1
 {% elif %}
 輸出網頁2
 {% elif %}
 輸出網頁3
 {% else %}
 輸出網頁4
 (% endif %}


注意, endif 的 end 與 if 之間沒有空格, elif 的 el 與 if 之間也是, 若有空格會造成語法錯誤.


2. for 迴圈標籤 :

模板語言的迴圈包括 for 與 empty 標籤, 以及 forloop 物件所提供的 7 種屬性. for 標籤操作的變數是由 views.py 傳遞過來的可迭代變數, 例如串列或元組, 模板的迴圈與 Python 的 for~in 迴圈類似, 但需要 endfor 結尾, 而且不可使用 break 與 continue :


(1). for 迴圈語法 :

{% for 變數 in 可迭代變數 %}
網頁1
{% empty %}
網頁2 (迴圈為空時輸出)
{% endfor %}

注意, empty 標籤必須放在 for 迴圈的最後面 (即 endfor 標籤前面), 當迴圈為空 (即可迭代變數元素個數為 0) 時, 網頁1將不會輸出, 而是輸出夾在 empty 與 endfor 標籤中間的網頁2.

如果要做倒序迭代, 只要在可迭代變數後面加上 reversed 即可 :

{% for 變數 in 可迭代變數 reversed %}
網頁1
{% empty %}
網頁2 (迴圈為空時輸出)
{% endfor %}


(2). forloop 變數 :

為了方便控制迴圈與顯示內容, 模板引擎還提供了 forloop 物件變數, 它的 7 個屬性如下表所示 :


 迴圈變數 forloop 的屬性 說明
 forloop.counter 迴圈計數器 (1 起始)
 forloop.counter0 迴圈計數器 (0 起始)
 forloop.revcounter 倒數之迴圈計數器 (由總圈數遞減至 1)
 forloop.revcounter0 倒數之迴圈計數器 (由總圈數遞減至 0)
 forloop.first 是否為第一個迴圈 (True/False)
 forloop.last 是否為最後一個迴圈 (True/False)
 forloop.parentloop 上一層迴圈的 forloop 變數


(3). 過濾器 (filter) :

模板引擎提供了一些過濾器 (其實就是模板的內建函數), 以便在輸出變數前對變數內容或格式做修正或運算, 使最終能輸出符合需要之結果. 用法是在變數後面以管線符號 "|" 串接過濾器, 可串接多個過濾器, 模板引擎會依照順序處理 :

{{ 變數 | 過濾器1 | 過濾器2  | 過濾器3 | ... }} 

常用的過濾器如下表 :


 常用模板過濾器 說明 範例
 add 加上一個數值或做字串串接 {{ var | add:"2" }} 
 addslashes 跳脫特殊字元 (前面加上倒斜線 \) {{ var | addslashes }} 
 capfirst 將字串變數的首字元轉為大寫 {{ var | capfirst }}
 center  將字串變數冠上指定空格後置中 {{ var | center: "5" }}
 cut 從字串變數中刪除指定子字串 {{ var | cut: " " }} 刪除空白字元
 date 將 datetime 變數以指定格式顯示 {{ var | date: "D d M Y" }}
 default 當變數為空字串時輸出預設字串 {{ var | default: "預設值" }}
 dictsort 將串列變數中的字典依指定鍵遞增排序 {{ var | dictsort: "鍵" }}
 dictsortreversed 將串列變數中的字典依指定鍵遞減排序 {{ var | dictsortreversed: "鍵" }}
 divisibleby 測試數值變數是否可被指定數整除 {{ var | divisibleby: "7" }} 
 escape 跳脫字串變數內的 HTML 標籤 {{ var | escape }}
 filesizeformat 以 KB/MB 等單位顯示檔案大小 {{ var | fileformat }}
 first 取出串列變數中的第一個元素 {{ var | first }}
 join 將串列變數中的元素以指定字串串接 {{ var | join: "," }} 
 last 取出串列變數中的最後一個元素 {{ var | last }} 
 length 傳回串列變數的元素個數 (長度) {{ var | length }} 
 length_is 測試變數的長度是否為指定之長度 {{ var | length_is: "5" }} 
 linebreaks 將字串中的 \n 轉成 HTML 之 br 與 p {{ var | linebreaks }} 
 linebreaksbr 將字串中的 \n 轉成 HTML 之 br {{ var | linebreaksbr }} 
 linenumbers 將顯示之文字加上行號 {{ var | linenumbers }} 
 ljust 將字串變數冠上指定空格後置左 {{ var | ljust: "5" }}
 lower 將字串變數轉成小寫 {{ var | lower }}
 make_list 將字串拆成字元串列 {{ var | make_list }}
 random 隨機取出串列中的一個元素 {{ var | random }}
 rjust 將字串變數冠上指定空格後置右 {{ var | rjust: "5" }} 
 safe 以 HTML 格式讀取字串 (不須跳脫) {{ var | safe }} 
 slice 取出字串中的指定切片 {{ var | slice:":2" }} 
 slugify 將字串中的空白以 dash '-' 取代 {{ var | slugify }} 
 stringformat 以科學表示法顯示數字 {{ var | stringformat: "E" }} 
 striptags 移除字串中的 HTML 標籤 {{ var | striptags }} 
 title 將字串中每個字的第一字元大寫 {{ var | title }} 
 truncatechars 將字串中超出指定長度部分以 ... 取代 {{ var | truncatechars:"5" }} 
 upper 將字串轉成大寫 {{ var | upper }} 
 wordcount 傳回字串中的字數 {{ var | wordcount }} 
 yesno 依變數值為 True/False/None 轉成指定值 {{ var | yesno:"是, 否, 取消" }} 


有些過濾器有攜帶參數, 注意, 過濾器與參數之間用冒號緊密串接, 中間不可有空格, 否則會出現錯誤.

完整的過濾器用法參考 :

Built-in template tags and filters

以下為綜合分支與迴圈的測試範例, 使用 HTML 表格來彙整測試結果, 關於 HTML 表格用法參考 :

網頁技術速學筆記 (一) : HTML 基礎

模板網頁共有 6 個, 以首頁 home.htm 列舉到各項測試之超連結 :

<!-- 1. home.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <h3>模板測試 2</h3>
  <ul>
    <li><a href="/if_test_1">分支測試 1 (if)</a></li>
    <li><a href="/if_test_2">分支測試 2 (if elif else)</a></li>
    <li><a href="/loop_test_1">迴圈測試 1 (有資料)</a></li>
    <li><a href="/loop_test_2">迴圈測試 2 (無資料)</a></li>
    <li><a href="/filter_test_1">過濾器測試 1</a></li>
    <li><a href="/filter_test_2">過濾器測試 2</a></li>
    <li><a href="/filter_test_3">過濾器測試 3</a></li>
  </ul>
</body>
</html>

第一個分支測試模板網頁 if_test_1.htm :

<!-- 1. if_test_1.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>分支測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
{% for stock in stocks %}
{{ stock }}
{% if not forloop.last %}
,
{% endif %}
{% endfor %}
</body>
</html>

第二個分支測試模板網頁 if_test_2.htm :

<!-- 2. if_test_2.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>分支測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
{% if hour > 18 %}
晚安
{% elif hour < 10 %}
早安
{% else %}
午安
{% endif %}
</body>
</html>

迴圈測試模板網頁 loop_test_1.htm :

<!-- 3. loop_test_1.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>for 迴圈</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="5">
    <caption>股票列表</caption>
    <tr>
      <th>No.</th>
      <th>股號</th>
      <th>公司</th>
      <th>分類</th>
    </tr>
    {% for stock in stocks %}
    <tr>
      <td>{{ forloop.counter }}</td>
      <td>{{ stock.id }}</td>
      <td>{{ stock.name }}</td>
      <td>{{ stock.category }}</td>
    </tr>
    {% empty %}
    <tr>
      <td colspan=4>無股票資料</td>
    </tr>
    {% endfor %}
  </table>
</body>
</html>

第一個過濾器測試模板網頁 filter_test_1.htm :

<!-- 4. filter_test_1.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>過濾器測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="3">
    <caption>過濾器測試 1</caption>
    <tr>
      <th>變數</th>
      <th>過濾器</th>
      <th>輸出</th>
    </tr>
    <tr>
      <td>i1=100</td>
      <td>i1|add:"200"</td>
      <td>{{ i1|add:"200"}}</td>
    </tr>
    <tr>
      <td>i2="100"</td>
      <td>i2|add:"200"</td>
      <td>{{ i2|add:"200"}}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|add:"200"</td>
      <td>{{ s1|add:"200" }}</td>
    </tr>
    <tr>
      <td>s2="I'm Tony."</td>
      <td>s2|addslashes</td>
      <td>{{ s2|addslashes }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|capfirst</td>
      <td>{{ s3|capfirst }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|truncatechars:"6"</td>
      <td>{{ s3|truncatechars:"6" }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|wordcount</td>
      <td>{{ s3|wordcount }}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|center:"5"</td>
      <td>{{ s1|center:"5" }}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|ljust:"5"</td>
      <td>{{ s1|ljust:"5" }}</td>
    </tr>
    <tr>
      <td>s1="A"</td>
      <td>s1|rjust:"5"</td>
      <td>{{ s1|rjust:"5" }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|cut:" "</td>
      <td>{{ s3|cut:" " }}</td>
    </tr>
    <tr>
      <td>now=datetime.now()</td>
      <td>now|date:"Y-m-d"</td>
      <td>{{ now|date:"Y-m-d" }}</td>
    </tr>
    <tr>
      <td>s4=""</td>
      <td>s4|default:"空字串"</td>
      <td>{{ s4|default:"空字串" }}</td>
    </tr>
    <tr>
      <td>d1=[{"id":"2330"},{"id":"2412"}]</td>
      <td>d1|dictsort:"id"</td>
      <td>{{ d1|dictsort:"id" }}</td>
    </tr>
    <tr>
      <td>d1=[{"id":"2330"},{"id":"2412"}]</td>
      <td>d1|dictsortreversed:"id"</td>
      <td>{{ d1|dictsortreversed:"id" }}</td>
    </tr>
  </table>
</body>
</html>

第二個過濾器測試模板網頁 filter_test_2.htm :

<!-- 5. filter_test_2.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>過濾器測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="3">
    <caption><b>過濾器測試 2</b></caption>
    <tr>
      <th>變數</th>
      <th>過濾器</th>
      <th>輸出</th>
    </tr>
    <tr>
      <td>i1=14</td>
      <td>i1|divisibleby:"7"</td>
      <td>{{ i1|divisibleby:"7" }}</td>
    </tr>
    <tr>
      <td>i2=100</td>
      <td>i2|divisibleby:"7"</td>
      <td>{{ i2|divisibleby:"7" }}</td>
    </tr>
    <tr>
      <td>i3=1024</td>
      <td>i3|filesizeformat</td>
      <td>{{ i3|filesizeformat }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|first</td>
      <td>{{ list1|first }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|last</td>
      <td>{{ list1|last }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|join:"/"</td>
      <td>{{ list1|join:"/" }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|slice:":2"</td>
      <td>{{ list1|slice:":2" }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|length</td>
      <td>{{ list1|length }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|length_is:"4"</td>
      <td>{{ list1|length_is:"4" }}</td>
    </tr>
    <tr>
      <td>list1=['Python','Java','C++','Perl']</td>
      <td>list1|random</td>
      <td>{{ list1|random }}</td>
    </tr>
    <tr>
      <td>b1=True</td>
      <td>b1|yesno:"是, 否, 取消"</td>
      <td>{{ b1|yesno:"是, 否, 取消" }}</td>
    </tr>
    <tr>
      <td>b2=False</td>
      <td>b2|yesno:"是, 否, 取消"</td>
      <td>{{ b2|yesno:"是, 否, 取消" }}</td>
    </tr>
    <tr>
      <td>n1=None</td>
      <td>n1|yesno:"是, 否, 取消"</td>
      <td>{{ n1|yesno:"是, 否, 取消" }}</td>
    </tr>
  </table>
</body>
</html>

第三個過濾器測試模板網頁 filter_test_3.htm :

<!-- 6. filter_test_3.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>過濾器測試</title>
  <meta charset="utf-8">
  <style>
  table {background-color:blue;}
  td, th {background-color:white;}
  </style>
</head>
<body>
  <table border="0" cellspacing="1" cellpadding="3">
    <caption><b>過濾器測試 3</b></caption>
    <tr>
      <th>變數</th>
      <th>過濾器</th>
      <th>輸出</th>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|linebreaks</td>
      <td>{{ s1|linebreaks }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|linebreaksbr</td>
      <td>{{ s1|linebreaksbr }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|linenumbers</td>
      <td>{{ s1|linenumbers }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|lower</td>
      <td>{{ s1|lower }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|upper</td>
      <td>{{ s1|upper }}</td>
    </tr>
    <tr>
      <td>s1="Hello\nWorld"</td>
      <td>s1|make_list</td>
      <td>{{ s1|make_list }}</td>
    </tr>
    <tr>
      <td>s2="<b>Hello</b>"</td>
      <td>s2|safe</td>
      <td>{{ s2|safe }}</td>
    </tr>
    <tr>
      <td>s2="<b>Hello</b>"</td>
      <td>s2</td>
      <td>{{ s2 }}</td>
    </tr> 
    <tr>
      <td>s3="hello world"</td>
      <td>s3|title</td>
      <td>{{ s3|title }}</td>
    </tr>
    <tr>
      <td>s3="hello world"</td>
      <td>s3|slugify</td>
      <td>{{ s3|slugify }}</td>
    </tr>
    <tr>
      <td>f1=0.00047108</td>
      <td>f1|stringformat:"e"</td>
      <td>{{ f1|stringformat:"e" }}</td>
    </tr>
    <tr>
      <td>s2="<b>Hello</b>"</td>
      <td>s2|striptags</td>
      <td>{{ s2|striptags }}</td>
    </tr>
  </table>
</body>
</html>

處理 URL 之路由檔 urls.py 內容如下 :

#urls.py
from django.contrib import admin
from django.urls import path
import mysite.views as views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('if_test_1/', views.if_test_1),
    path('if_test_2/', views.if_test_2),
    path('loop_test_1/', views.loop_test_1),
    path('loop_test_2/', views.loop_test_2),
    path('filter_test_1/', views.filter_test_1),
    path('filter_test_2/', views.filter_test_2),
    path('filter_test_3/', views.filter_test_3),
    path('', views.home),
]

視圖處理函數 views.py 內容如下 :

#views.py
from django.shortcuts import render
from datetime import datetime

def if_test_1(request):
    stocks={"台積電","中華電","統一超"}
    return render(request, 'if_test_1.htm', locals())

def if_test_2(request):
    now=datetime.now()
    hour=now.timetuple().tm_hour
    return render(request, 'if_test_2.htm', locals())

def loop_test_1(request):
    stock1={"id":"2330", "name":"台積電", "category":"半導體"}
    stock2={"id":"2412", "name":"中華電", "category":"通信網路"}
    stock3={"id":"2912", "name":"統一超", "category":"貿易百貨"}
    stocks=[stock1, stock2, stock3]
    return render(request, 'loop_test.htm', locals())

def loop_test_2(request):
    stocks=[]
    return render(request, 'loop_test.htm', locals())

def filter_test_1(request):
    i1=100
    i2="100"
    s1="A"
    s2=s2="I'm Tony."
    s3="hello world"
    s4=""
    d1=[{"id":"2330"},{"id":"2412"}]
    now=datetime.now()
    return render(request, 'filter_test_1.htm', locals())

def filter_test_2(request):
    i1=14
    i2=100
    s1="<b>Hello</b>"
    i3=1024
    list1=['Python','Java','C++','Perl']
    b1=True
    b2=False
    n1=None
    return render(request, 'filter_test_2.htm', locals())

def filter_test_3(request):
    s1="Hello\nWorld"
    s2="<b>Hello</b>"
    s3="hello world"
    f1=0.00047108
    return render(request, 'filter_test_3.htm', locals())

def home(request):
    return render(request, 'home.htm', {})

以 python manage.py runserver 指令啟動開發伺服器後, 在伺服器網址列輸入 localhost:8000 拜訪首頁如下 :




點首頁中的超連結即可測試各個功能. 在分支測試 1 中, 傳遞到模板的串列變數為 :

stocks={"台積電","中華電","統一超"}

在模板中使用了 for 迴圈來取出串列元素, 並使用 if 標籤來判斷是否要在元素後面加逗號, 在最後一個元素時 forloop.last 為真, 因此不會加逗號, 網頁輸出為 :

台積電 , 統一超 , 中華電

在分支測試 2 中, 傳送給模板的是目前的時數 hour (0~23) :

now=datetime.now()
hour=now.timetuple().tm_hour

在模板網頁中利用 if~elif~else 分支來判斷要輸出晚安, 早安, 還是午安.

在迴圈測試 1, 傳遞給模板網頁的是一個字典串列 stocks :

stock1={"id":"2330", "name":"台積電", "category":"半導體"}
stock2={"id":"2412", "name":"中華電", "category":"通信網路"}
stock3={"id":"2912", "name":"統一超", "category":"貿易百貨"}
stocks=[stock1, stock2, stock3]

在模板中用 for 迴圈與 forloop 變數於表格中顯示股票資訊. 而在迴圈測試 2 則是傳遞空串列變數 stocks 給相同的模板 loop_test.htm, 利用 empty 標籤來顯示無資料訊息, 結果如下 :




過濾器部分因太多, 所以分成三部分測試, 結果如下 :






關於過濾器摘要如下 :
  1. wordcount 是計算 "字" 數而非 "字元" 數, 亦即以空格為單位做區隔, 例如 "您好嗎?" 的 wordcount 是 1; 而 "您 好 嗎 ?" 的 wordcount 是 4. 
  2. 若直接輸出變數 {{ var }}, 模板引擎會自動將 HTML 標籤的角括號轉成 > 與 <, 但可以用 safe 過濾器阻止自動轉換. 
  3. linebreaksbr 會將字串變數中的跳行字元 "\n" 改為 "<br>", 例如 "Hello\nWorld" 會被轉成 "Hello<br>World". 而 linebreaks 則除了做此轉換外還會在外面套上 p 標籤, 例如 "Hello\nWorld" 會被轉成 "<p>Hello<br>World</p>".
  4. date 過濾器格式與 PHP 的極類似, 如果要製作 "2019-10-03 08:43:12" 這樣的輸出, 則其格式為 date:"Y-m-d H:i:s", 參考 :
    https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#date
  5. slice 的切片用法與 Python 字串或串列切片的用法一樣. 
十年前玩 GAE 時用的也是幾乎一樣的模板語言, 雖然 GAE 當時只支援 Python 2.x 的 Django 0.9 版, 但模板部分那時就很成熟了, 因此也沒甚麼變, Tsung's blog 有整理一篇很不錯的模板語言用法摘要, 參考 :

Google App Engine 初學(8) - 共用 Template (Layout)

沒有留言 :