2023年9月30日 星期六

Python 學習筆記 : Django 4 用法整理 (二) 網頁模板

這周都在複習 Django, 我把心得整理在下面的筆記裡 :


今年中秋連假不烤肉, 窩在家繼續複習 Django 整理模板用法. 

在上一篇測試中均使用 django.http.HttpResponse() 函式直接將 HTML 碼回應給用戶端, 但這不僅麻煩, 且不利於前後端分工. Django 內建的模板引擎可解決此問題, 前端設計師負責網頁模板設計, 後端工程師則可利用 django.shortcut.render() 函式將變數傳遞給指定的模板網頁顯示, 架構如下 : 




網頁模板需要放在手動建立的特定資料夾下 (通常取名為 templates), 其位置有二 :
  • 在上層專案目錄下 (專案模板) :
    整個網站與旗下的 App 均可使用, 適用於單一 App 網站  (App 可移植性低).
  • 在 App 目錄下 (App 模板) : 
    每一個 App 都可以有自己的模板資料夾 (App 可移植性高). 
當然也可以兩者均有, 專案 templates 用來存放網站首頁相關的模板; 而 App 的 templates 則用來存放該 App 相關的網頁模板. 在前一篇的測試最後, 得到一個可移植性佳的單一 App 網站, 其架構如下圖所示 :


mysite                                      (上層專案目錄)
     |____ manage.py                (管理程式)
     |____ mysite                        (下層專案目錄)
     |             |____ __init__.py   (形成套件)
     |             |____ settings.py    (專案設定檔)
     |             |____ urls.py           (專案路由模組, 自動建立)
     |             |____ views.py        (專案視圖模組, 手工添加)
     |             |____ wsgi.py         (伺服器佈署設定檔)
     |             |____ asgi.py         (伺服器佈署設定檔, 非同步)
     |____ myapp1                      (應用程式目錄)
                   |____ migrations     (資料庫同步)
                   |____ __init__.py    (形成套件)
                   |____ admin.py       (註冊資料模型)
                   |____ apps.py         (App 設定檔)
                   |____ models.py     (資料模型定義)
                   |____ tests.py          (測試用程式)
                   |____ views.py        (App 視圖模組, 自動建立)
                   |____ urls.py           (App 路由模組, 手工添加)


可見與網站首頁相關的回應由專案的控制器 (mysite 下的 urls.py 與 views.y) 負責, 而與 App 相關的回應則由 App 的控制器 (myapp1 下的 urls.py 與 views.y) 負責. 現在於上層 mysite 與 myapp1 下各自建立一個 templates 資料夾, 架構如下 : 

mysite                                       (上層專案目錄)
     |____ manage.py                 (管理程式)
     |____ mysite                         (下層專案目錄)
     |             |____ __init__.py    (形成套件)
     |             |____ settings.py    (專案設定檔)
     |             |____ urls.py           (專案路由模組, 自動建立)
     |             |____ views.py        (專案視圖模組, 手工添加)
     |             |____ wsgi.py         (伺服器佈署設定檔)
     |             |____ asgi.py         (伺服器佈署設定檔, 非同步)
     |____ templates                   (專案的模板資料夾)
     |____ myapp1                      (應用程式目錄)
                   |____ migrations     (資料庫同步)
                   |____ __init__.py    (形成套件)
                   |____ admin.py       (註冊資料模型)
                   |____ apps.py         (App 設定檔)
                   |____ models.py     (資料模型定義)
                   |____ tests.py          (測試用程式)
                   |____ views.py        (App 視圖模組, 自動建立)
                   |____ urls.py           (App 路由模組, 手工添加)
                   |____ templates      (App 的模板資料夾)


注意, 專案的網頁模板資料夾 templates 是放在上層專案目錄下, 與下層專案目錄, App 等位於同意層; 而 App 的 templates 則放在 App 目錄下. 


一. App 目錄下的模板與控制器設定 : 

此處使用模板網頁來改寫前一篇 App=myapp1 的兩個請求 (根目錄 myapp1/ 與打招呼 myapp1/helloworld), 如前所述, 為了能動態地傳遞變數, 將打招呼的請求 URL 改為 myapp1/hello. 

首先在 myapp1/templates 下建立兩個網頁模板 myapp1_home.htm 與 hello.htm 來改寫上一篇測試中 myapp1/ 與 myapp1/hello 的請求處理. myapp1_home.htm 是 myapp1 的首頁, 內容如下 :

<!-- myapp1_home.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <h2>這是 App1 首頁!</h2>
</body>
</html>

hello.htm 是可傳入變數, 顯示動態資訊的網頁模板, 內容如下 :

<!-- hello.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <h2>Hello, {{name}}, 現在的時間是 {{now}}</h2>
</body>
</html>

此模板網頁中以雙重大括號括起來的地方是模板引擎讓視圖模組 views.py 傳出的變數嵌入之處, 模板網頁中可以嵌入變數, 標籤, 以及註解 :


 模板語法元素 說明
 {{ 變數 & 過濾器 }} 來自 views.py 的變數, 用來直接替換該位置內容或過濾
 {% 標籤 %} 標籤用來控制資料顯示邏輯 (迴圈與判斷)
 {# 註解 #} 註解會被模板引擎忽略


但其實真正有作用的模板語法只有 {{}} 與 {%%} 這兩個而已, 其中  {%%} 最重要, 可使用 Python 的 if elif else 語法進行分支判斷, 也可以使用與 Python for 迴圈幾乎一樣的語法來進行迭代. 

分支判斷的模板語法如下 : 
 

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


for 迴圈的模板語法如下 :


 模板 for 迴圈 說明
 {% for 變數 in 可迭代變數 %}
 內含 {{變數}} 的網頁
 {% endfor %}
 用 for in 迭代內含 {{變數}} 的網頁
 {% for 變數 in 可迭代變數 reversed%}
 內含 {{變數}} 的網頁
  {% endfor %}
 
 以倒序方式迭代內含 {{變數}} 的網頁
 {% for 變數 in 可迭代變數%}
 內含 {{變數}} 的網頁 1
 {% empty %}
 迴圈為空時輸出的網頁 2 
 {% endfor %}
 
 可迭代變數不為空時輸出網頁 1, 為空時輸出網頁 2
 注意, empty 必須放在 endfor 前面. 


除此之外, 模板語法還提供迴圈變數 (物件) forloop, 其屬性可用在 for 迴圈中讓 if elif else 分支判斷語法決定是否要輸出特定網頁內容 : 
 

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


詳細用法參考 :


其次編輯 myapp1 的視圖模組 views.py, 注意, 與前一篇測試不同之處是, 因為要透過在 URL 中傳遞變數來動態顯示打招呼的人名, 故 URL 函式名稱由 helloworld 改為 hello :

# views.py of App=myapp1
from django.shortcuts import render   
from datetime import datetime

def hello(request, name='匿名'):   # URL='myapp1/hello/' 的處理函式
    now=datetime.now()
    return render(request, 'hello.htm', {'name': name, 'now': now})

def myapp1_home(request):     # App 根目錄 URL= 'myapp1/' 的處理函式
    return render(request, 'myapp1_home.htm')   

此處須匯入 django.shortcuts.render() 來輸出回應, 此函式可透過模板引擎對模板網頁傳遞變數, 其語法如下 : 

render(request, '模板網頁檔名', 變數字典)    

其中第一個參數為 http.HttpRequest 物件實例, 代表一個 URL 請求, 第二個參數為模板網頁檔名, 例如上面建立的 'myapp1_home.htm' 或 'hello.htm', 第三個參數是要傳給模板網頁的變數字典, 如果要一一列舉太麻煩, 也可呼叫 Python 內建函式 locals(), 此函式會將 views.py 裡面用到的變數組成字典傳回, 所以上面的 views.py 也可以改寫為如下較簡短的程式碼 :

# views.py of App=myapp1
from django.shortcuts import render   
from datetime import datetime

def hello(request, name='匿名'):   # URL='myapp1/hello/<name>' 的處理函式 (name 有預設值)
    now=datetime.now()       # 傳回現在日期時間
    return render(request, 'hello.htm', locals())

def myapp1_home(request):     # App 根目錄 URL= 'myapp1/' 的處理函式
    return render(request, 'myapp1_home.htm')   

注意 這裡 hello() 需列舉 URL 會攜帶的所有參數, 目前只有 1 個 : name, 因為客戶端也可能不會傳參數, 故 name 一個預設值 '匿名', 它會跟 now 變數一起被 locals() 做成變數字典傳給 render(). 

接著改寫 myapp1 的路由模組 urls.py (App 的 uls.py 需自建, 可從下層專案模組目錄 mysite 底下複製專案的 urls.py 來改) :

# urls.py of App=myapp1
from django.urls import path
from . import views      # 匯入 App 的視圖模組

urlpatterns = [
    path('hello/<str:name>/', views.hello),  # 有參數
    path('hello/', views.hello),  # 無參數 
    path('', views.myapp1_home),  # myapp1 的根目錄請求
]

此處主要是從 App 目錄下匯入 myapp1 自己的視圖模組 views.py, 然後分別將 'myapp1/hello' 與 'myapp1/' 的路由請求分別委派給 views.py 裡的 hello() 與 myapp1_home() 函式處理. 注意, 這裡 hellp 有兩個 path, 一個是有帶參數 name 的, 一個則無. 

注意, App 路由模組中的 URL 都是相對於 'myapp1/', 故 'hello/' 在客戶端要用 'myapp1/hello/' 請求; 而 App 跟目錄 '' (空字串) 在客戶端要用 'myapp1/' 請求. 

以上就完成 App 的控制器設定了. 


二. 專案目錄下的模板與控制器設定 : 

專案目錄 (注意是上層) templates 下的網頁模板主要是用來輸出網站首頁 (避免與 App 綁在一起以提升 App 可移植性), 與前一篇只簡單地顯示 ''歡迎來到我的首頁!' 不同的是, 此處要跟上面 myapp1 的 hello 一樣傳遞變數 (此處為傳遞目前的時間資訊) 給模板網頁, 首先在上層專案目錄的 templates 資料夾下建立一個網頁模板 home.htm :

<!-- home.htm -->
<!DOCTYPE html>
<html>
<head>
  <title>首頁</title>
  <meta charset="utf-8">
</head>
<body>
  <h2>歡迎來到我的首頁! 現在的時間是 {{now}}</h2>
</body>
</html>

此首頁除了顯示打招呼用語外, 還會顯示從專案的視圖模組 views.py 傳遞過來的 now 變數. 

接著編輯專案的視圖模組 views.py, 由於建立專案時不會在下層專案目錄中自動建立 views.py, 須自建或從 App 目錄下複製 myapp1 的 views.py 來修改 : 

# views.py of project=mysite
from django.shortcuts import render
from datetime import datetime

def home(request):  
    now=datetime.now()       # 傳回現在日期時間
    return render(request, 'home.htm', locals())     

此處僅定義一個 home() 函式來處理網站首頁 (根目錄) 的請求, 將變數傳給模板網頁回應給客戶端. locals() 會傳回此函式內的區域變數為一個字典 (即 {'now': now}) 傳遞給模板網頁 home.htm 顯示給客戶端. 

接著編輯專案路由模組 urls.py (這模組會在建立專案時自動產生, 不須自建), 內容與前一篇的 urls.py 一樣不用修改 : 

# urls.py of project=mysite
from django.contrib import admin
from django.urls import path, include  # include() 用來含括各 App 的 urls.py
from . import views   # 專案的視圖模組

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home),   # 將網站首頁請求委派給專案的視圖模組
    # 將 URL='myapp1/' 委派給 myapp1 的 urls.py
    path('myapp1/', include('myapp1.urls')),    
]

這樣就完成了專案控制器的設定. 


三. 專案模板資料夾位置設定 : 

最後必須開啟專案設定檔 settings.py, 設定專案模板的位置, 這樣才能正確取得專案模板網頁路徑並順利輸出網站首頁. 

開啟 settings.py 後搜尋 TEMPLATES, 裡面 DIR 鍵的值預設為空串列 : 

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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',
            ],
        },
    },
]

此 DIR 就是用來設定專案模板位置的, 請在空串列中填入 BASE_DIR/'templates' :

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [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',
            ],
        },
    },
]

其中 BASE_DIR 就是上層專案目錄路徑, 在 settings.py 中搜尋 BASE_DIR 即可找到其值如下 :

BASE_DIR = Path(__file__).resolve().parent.parent

__file__ 表示 settings.py 本身, 經呼叫 resolve() 解析其絕對位置後, 因 settings.py 位於下層專案目錄 mysite 下, 故 .parent 為下層專案目錄之路徑, 再一次 .parnt 即取得上層專案目錄路徑, 而我們的專案模板資料夾 templates 即位於上層專案目錄 mysite 底下, 串接後即得專案模板資料夾 templates 路徑. 注意, 這裡必須使用字串 'templates' 而非變數 template. 

至於 App 的模板位置不需特別設定, 因為 'DIR' 鍵底下的 'APP_DIR' 預設為 True, 即 Django 會自動到各 App 資料夾下找尋該 App 的 templates 資料夾. 

至此完成所有網站與 App 設定, 用下列指令啟動開發伺服器 : 

python manage.py runserver   

然後用瀏覽器拜訪上面專案與 App 的 urls.py 所定義之 URL : 

http://127.0.0.1:8000      (網站根目錄: 首頁)
http://127.0.0.1:8000/myapp1       (App 根目錄)
http://127.0.0.1:8000/myapp1/hello       (App 的 hello: 無參數)
http://127.0.0.1:8000/myapp1/hello/Tony       (App 的 hello: 有參數)

結果如下 : 




範例檔可從 GitHub 下載 :


以上的測試總結如下 :
  • 專案與 App 都可以有各自的 templates 網頁模板資料夾, 讓 App 與專案的耦合度降低以提升App 的可移植性, 在開發大型專案時能達成較高的元件可重用性. 
  • 專案的 templates 模板資料夾建立後必須開啟專案設定檔 settings.py, 將 TEMPLATES 變數的 DIR 串列設為 [BASE_DIR/'templates'], 這樣 Django 的模板引擎才找得到專案的模板資料夾路徑. App 的模板資料夾位置不需特別設定, 因為 TEMPLATES 變數的 APP_DIR 屬性預設為 True, 模板引擎會自動到各 App 目錄下尋找 templates 目錄. 
  • 若不考慮 App 的移植性, 也可以只用專案的網頁模板資料夾, 不設 App 的 templates, 亦即全部 App 都使用專案模板資料夾. 


四. 模板網頁的匯入與繼承 : 

為了模板網頁能達成模組化與結構化要求, 模板引擎還提供了模板網頁匯入 (include 指令) 與繼承 (extend 指令) 的語法將其他的模板網頁或片段引入到目前的模板網頁中, 兩者差別如下 :
  • 被 include 的模板網頁通常是被切割後的網頁片段而非完整的網頁, 但被繼承的模板網頁一定是完整的網頁. 
  • 繼承指令 {% extend %} 必須放在模板網頁最前面; 而匯入指令 {% include %} 則可以在模板網頁的任何地方. 
使用模板引擎的 {% include %} 指令可以在模板網頁中匯入其他網頁檔案, 語法如下 : 

{% include "其他模板網頁.htm" %} 

注意, 這裡 "其他模板網頁.htm" 檔名必須用引號括起來 (單雙均可), 例如下列模板網頁 :

<!--index.htm-->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  <h1>Hello World</h1>   
</body>
</html>

可將其中 body 的內容單獨寫成一個 helloworld.htm 檔 : 

<!--helloworld.htm-->
<h1>Hello World</h1>

然後修改模板 index.htm, 在其中用 include 匯入 helloworld.htm :

<!--index.htm-->
<!DOCTYPE html>
<html>
<head>
  <title>Hello</title>
  <meta charset="utf-8">
</head>
<body>
  {% include "helloworld.htm" %}    
</body>
</html>

注意, 被匯入的 hellowrld.htm 不是完整的 html 格式, 而只是一個網頁片段. 但這種將網頁片段作為一個 htm 檔的作法有點怪, 模板網頁模組化主要還是靠繼承 (extend). 

模板網頁的繼承與類別的繼承概念類似, 被繼承的模板稱為父模板, 繼承者稱為子模板. 在父模板中會用下列語法定義若干讓子模板嵌入網頁片段的區塊, 並為這些區塊命名 :

{% block 區塊名稱 %}{% endblock %}    

子模板則必須在檔案一開頭便使用下列語法繼承父模板 :

{% extends '父模板.htm' %}    

然後用下列語法在所繼承之父模板中的指定區塊嵌入網頁片段 : 

{% block 區塊名稱 %}
要嵌入之網頁片段
{% endblock %}    

例如我們可以先建立一個基礎網頁模板 base.htm 當作所有網頁模板的父模板 :

<!--base.htm-->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{% block title %}{% endblock %}</title>
  {% block link %}{% endblock %}
  {% block script %}{% endblock %}
  <style>
  {% block style %}{% endblock %}
  </style>
</head>
<body>
 {% block body %}{% endblock %}
</body>
</html>

上面的黃色部分就是讓繼承 base.htm 的子模板網頁用 block 指令於該處嵌入樣式, 程式檔, 或網頁片段的地方, 這樣子模板網頁就不需要重複寫 HTML 相同的部分, 結構就變得很簡單 :

{% extends "base.htm" %}
{% block title %}網頁標題{% endblock %}
{% block link %}
   link 樣式檔
{% endblock %}
{% block script %}
   script 程式檔
{% endblock %}
{% block body %}
   網頁內容 + script 程式檔
{% endblock %}

注意, extends 指令一定要放在模板網頁最前面 (忽略註解). 其次, Javascript 程式檔在網頁中的位置可以在 head 內, 也可以在 body 內, 在 body 中匯入時不另外做一個嵌入區塊, 而是與網頁內容合在一起嵌入. 

例如上面的 index.htm  就可以寫成 : 

<!--index.htm-->
{% extends "base.htm" %}
{% block title %}Hello{% endblock %}
{% block body %}
   <h1>Hello World</h1>   
{% endblock %}
  
注意, base.htm 裡面的 link 與 script 區塊若沒用到就不用嵌入任何資料. 

這樣就可以用模板繼承方式來改寫上面的 mysite 網站了, 首先在上層專案目錄的模板資料夾 templates 底下新增基底網頁模板 base.htm, 然後修改網站的首頁檔 home.htm 為如下 :

<!-- home.htm -->
{% extends "base.htm" %}
{% block title %}首頁{% endblock %}
{% block body %}
  <h4>歡迎來到我的首頁! 現在的時間是 {{now}}</h4>
{% endblock %}

然後切換到應用程式 myapp1 下的 templates 資料夾下, 將原本的兩個模板網頁改成繼承自專案基底模板 base.htm, 下面是修改後的 hello.htm 模板 : 

<!-- hello.htm -->
{% extends "base.htm" %}
{% block title %}Hello{% endblock %}
{% block body %}
  <h4>Hello, {{name}}, 現在的時間是 {{now}}</h4>
{% endblock %}

下面是修改後的 myapp1_home.htm 模板 : 

<!-- myapp1_home.htm -->
{% extends "base.htm" %}
{% block title %}myapp1 首頁{% endblock %}
{% block body %}
  <h4>這是 App1 首頁!</h4>
{% endblock %}

結果與上面未使用模板繼承的網站是一樣的. 以上修改後的範例下載網址 :



五. 利用模板繼承使用網頁框架 : 

利用上面的模板繼承功能可以讓網站在使用框架時更方便, 此處以 Bootstrap 4 為例, 使用此框架必須匯入 Bootstrap 的程式與 CSS 樣式表, 以及 jQuery 框架, 因為 Bootstrap 的互動功能是依賴 jQuery 達成的 : 

  <link rel="stylesheet" href="https://unpkg.com/bootstrap@4.0.0/dist/css/bootstrap.min.css">
  <script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
  <script src="https://unpkg.com/bootstrap@4.0.0/dist/js/bootstrap.min.js"></script>

關於 Bootstrap 用法參考 :


同樣地, 模板網頁可以繼承專案 templates 的父模板, 也可以繼承應用程式本身 templates 下的父模板, 首先在專案 templates 下建立一個繼承 base.htm 的 bootstrap.htm 模板 :

<!--bootstrap.htm-->
{% extends "base.htm" %}
{% block link %}
  <link rel="stylesheet" href="https://unpkg.com/bootstrap@4.0.0/dist/css/bootstrap.min.css">
{% endblock %}
{% block script %}
  <script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
  <script src="https://unpkg.com/bootstrap@4.0.0/dist/js/bootstrap.min.js"></script>
{% endblock %}
 
此 Bootstrap 的框架模板在繼承 base.htm 時僅在 head 中嵌入 link 與 script 這兩個區塊, 而 title 與 body 區塊則未嵌入, 而是讓繼承 bootstrap.htm 的子模板來嵌入. 然後修改網站首頁 home.htm, 讓它從直接繼承 base.htm 改為繼承 bootstrap.htm, 並且使用 Bootstrap 的 alert-info 樣式來顯示首頁內容 :

<!-- home.htm -->
{% extends "bootstrap.htm" %}
{% block title %}首頁{% endblock %}
{% block body %}
<div class="container">
  <div class="alert alert-info" role="alert">
歡迎來到我的首頁! 現在的時間是 {{now}}
  </div>
</div>
{% endblock %}

然後修改應用程式 templates 下的 hello.htm 與 myapp1_home.htm, 同樣也是繼承專案 templates 下的 bootstrap.htm : 

<!-- hello.htm -->
{% extends "bootstrap.htm" %}
{% block title %}Hello{% endblock %}
{% block body %}
<div class="container">
  <div class="alert alert-warning" role="alert">
  Hello, {{name}}, 現在的時間是 {{now}}
  </div>
</div>
{% endblock %}

<!-- myapp1_home.htm -->
{% extends "bootstrap.htm" %}
{% block title %}myapp1 首頁{% endblock %}
{% block body %}
<div class="container">
  <div class="alert alert-danger" role="alert">
  這是 App1 首頁!
  </div>
</div>
{% endblock %}

結果如下 :




可見 Bootsrap 的效果樣式效果有出來了. 此範例下載網址 :


Bootstrap 框架的模板不一定要放在專案的 templates 下, 也可以放在應用程式的 templates 資料夾下, 專門給該 App 的其他子模板網頁繼承之用, 這樣可以提高 App 的可移植性, 首先在 myapp1/templates 下建立應用程式的 Bootstrap 模板 : 

<!--myapp1_bootstrap.htm-->
{% extends "base.htm" %}
{% block link %}
  <link rel="stylesheet" href="https://unpkg.com/bootstrap@4.0.0/dist/css/bootstrap.min.css">
{% endblock %}
{% block script %}
  <script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
  <script src="https://unpkg.com/bootstrap@4.0.0/dist/js/bootstrap.min.js"></script>
{% endblock %}

然後修改 myapp1 的兩個子模板, 將其父模板從專案的 bootstrap.htm 改成應用程式自己的 Bootstrap 模板 myapp1_bootstrap.htm : 

<!-- myapp1_home.htm -->
{% extends "myapp1_bootstrap.htm" %}
{% block title %}myapp1 首頁{% endblock %}
{% block body %}
<div class="container">
  <div class="alert alert-success" role="alert">
  這是 App1 首頁!
  </div>
</div>
{% endblock %}

此處除了父模板改成 myapp1_bootstrap.htm 外, 還將 alert 樣式改成 alert-success, 以與前一個範例作區別. 結果如下 : 




此例只動了 myapp1 的模板網頁, 網站首頁沒變, 仍然是 alert-info, 但 myapp1 的模板網頁都改成 alert-success, 可見這兩個網頁確實是繼承 myapp1_bootstrap.htm, 此範例下載網址 : 


上面範例雖然演示了 Bootstrap 框架父模板網頁既可以放在專案的 templates 目錄中供專案本身與所有 App 繼承使用; 也可以放在各 App 的 templates 目錄下供自己使用, 但一般來說網站會有統一的風格樣式, 故實務上會放在專案 templates 底下. 

2023年9月28日 星期四

好站 : 從零開始學 Python

今天在整理 Swift 5 筆電 (現在變成桌電) 的 Chrome 頁籤時發現下面這個之前在搜尋 Python 資料時找到的 iT 邦幫忙 Python 教學系列文章 :  


全系列共 30 篇文章, 範圍涵蓋基礎與中高階用法, 還包括了 Pillow, tkinter, Matplotlib, Numpy 等套件用法, 值得新手拜讀, 老手溫故知新. 
 

2023年9月27日 星期三

標到 TEXDON 四行程發電機 TX11000i

周日晚上在 fb 太陽能社群看到標售一台黑色四行程發電機 TX11000i (它有好幾種顏色, 很久以前就注意到漂亮的綠色與藍色機種), 這次試著出價居然被我以 6640 元標到了 :




雖然不若藍色與綠色好看, 但黑色好處是耐髒. 這台市價大約 12000 元左右, 參考 : 


昨天去仁武取貨, 老闆說機油要加 280 CC, 汽油滿箱兩公升可運轉約 7~8 小時, 最好每次啟動後都把油耗完停機, 如果油箱有油放太久容易阻塞油管 (不然就要抽出來為宜). 此發電機最大功率 800W, 足以負荷照明, 行動電源或筆電充電, 音響等負載. 

我打算放在鄉下頂樓當備用電源, 然後於西面牆拉一條電線到一樓. 

辦公電腦換新 (桌電改 ASUS 筆電)

辦公室上週新進一批電腦, 這次終於輪到我換電腦了, 考量去年菁菁確診我在家上班時要搬桌用電腦回家的麻煩, 這次我選擇筆電, 另一個好處是筆電不怕電力例行維護時電源瞬斷問題. 

這台筆電是 ASUS ExpertBook 14 吋, i5-1250U 1.3 GHz CPU, 8GB DRAM, 512GB SSD, 搭載 Win 10 作業系統, 算是低配款的 ExpertBook. 周邊介面有三個 USB Type A, 一個 Type C (PD), 一個 HDMI, 一個 VGA, 一個乙太網插槽, 以及一個 TF 卡槽. 由於辦公室必須用網路線連網, 所以機身較厚重. 







經過兩天調校 (安裝資安監控軟體, 印表機驅動器) 終於可以使用了. 雖然是低配款筆電, 但比起用了 7 年像烏龜車般, 反應緩慢的桌機而言, 這台簡直是跑車了. 

不過筆電沒有大讀卡機, 連線某些系統需要插員工證才能允入, 這幾天要上 momo 或露天找看看. 另外現在筆電幾乎都只有一個 C 碟, 但可以插一個 TF 卡來當 D 碟, 與我的 LG gram 不同的是, ASUS 筆電的 TF 卡沒有彈簧, 因此插入卡槽時會露出一小截屁股. 


2023-10-06 補充 :

手機也可以下載 Outlook : 




2023年9月25日 星期一

料理實驗 : 竹筍排骨湯

昨天水某從市場帶回來兩條竹筍, 又去全聯買排骨, 說要做竹筍排骨湯, 晚上我就來按照阿慶師的食譜來實作這道非常簡單的湯 :





材料 :
  • 竹筍 2 條
  • 排骨 1~2 盒
  • 米酒 2 茶匙
  • 鹽 適量
  • 鮮味粉 適量
作法 : 
  1. 排骨川燙洗淨備用. 
  2. 竹筍切片放入燉鍋, 再放入川燙好的排骨與米酒, 加水至七分滿, 大火滾開後轉小火燉 1~1.5 小時, 最後加入適量的鹽與鮮味粉調味即可. 




真的如阿慶師說的那樣, 慢燉 1.5 小時後才會夠入味. 

高科大還書 2 本

今天下班去母校還下面兩本書以便取回預約書 : 
No.1 市圖有第二版再兩個月就會到館 (我都預約超過半年了); No.2 目前沒時間玩樹莓派就先還吧, 換目前更想讀的書. 

回家時經過行政大樓, 聽說最近有一位學妹為情所困從那棟六樓躍下, 傻呀這孩子. 20 歲時走過青澀年代才懂情網是甚麼, 但三十年後回頭一看人生如夢, 很多那時在意的東西已然煙消雲散. 

前陣子被郭老闆挑選為副手的賴佩霞回憶 20 歲時母親曾帶她去美國尋父, 可惜她父親已經另組家庭, 她問母親有甚麼感想, 賴母回說 "還好沒嫁給他, 頭都禿成那樣了", 當帥哥變成頭禿肚肥的大叔時, 當時的撕心裂肺突然變得好笑. 

失戀時就去看看梅艷芳與張國榮演的 "胭脂扣" 吧.

2023年9月24日 星期日

2023 年第 38 周記事

本周因為要補班 (中秋節) 上班六天, 昨日午休時走到附近的吳記和平店買了兩盒綠豆椪, 其中一盒是要給小舅家的. 下班時繞到中正三路錦福去買菁菁很想吃的白雪蘇, 還真巧, 回家半路接到婷婷表妹電話, 問我何時回鄉下, 說有東西要給我. 昨晚近九點回到鄉下沒多久婷婷就來了, 原來是要送月餅給我, 剛好把中午買的吳記讓她帶回去, 交換禮物哈哈.

上周發布人事令, 我與兩位上司名列升遷名單, 離退休不到 8 年, 雖然心態上對升遷已從以前的狂喜縮水成小喜, 但畢竟升官才能加薪, 只要不是調離原單位, 還是認真努力爭取表現機會才是正途. 週五上頭要求將上個月上線的 CSR 專案填報研發獎勵, 雖然填資料很費思量, 但看在錢的份上還是值得花點時間寫文件.  

上周收到威剛寄來的增資繳款單, 威剛我十幾年前買賣過幾次賺了一些錢, 最後剩下一張因海嘯套住了一直放到現在, 此次增資可認 85 股, 原本覺得這麼少算了, 後來一查發現股價衝高, 似乎還蠻有賺頭, 周二去第一銀行五福分行繳款 5200 元, 到時與原持股清一清唄. 

本周終於將韓劇 "Moving 異能" 看完, 我看網路評論對結局安排看法不一, 與前面的戲劇張力比起來, 我覺得完結篇似乎平淡許多, 埋的伏筆只有閔容俊次長的上司居然是喜秀為其打抱不平的同學申惠媛! (據說她也是異能者, 不老凍齡), 不過還是非常期待第二季啦! 

今天早上沿水圳騎去美泰買了一個感應燈, 因為晚上要上下頂樓沒有燈光有點危險, 傍晚拿上去測試 OK, 順便量了一下電表數據, 好久沒紀錄了 : 




2023年9月22日 星期五

好站 : pdf 線上翻譯工具

水某最近接了新的臨床案子, 要先讀完整份英文版 protocol, 問我能否翻成中文, 我先一頁頁貼到 chateverywhere 去翻但覺得好累, 好在找到下面這個網站, 可以直接上傳整份 pdf, 它會利用 Google 翻譯將其翻成繁體中文的 pdf 下載, 文件格式沒變, 真是太好用了 :


按 "上傳文件" 鈕點選來源文件上傳 : 




選擇來源語言與目的語言後按 "翻譯" 鈕 : 





完成後過一會兒就會自動下載翻好的文件了 : 



如何更改 Win 11 的滑鼠游標樣式

Win 11 的預設滑鼠游標是黑色細框箭頭, 在畫面很複雜時比較不明顯, 要如何改成比較醒目樣式呢? 按底下工作列的開始 (藍色四方塊) 點選設定 : 




點選 "協助工具/滑鼠指標與觸控" :




預設是最左邊的白色黑框樣式, 改選最右邊有填色那個, 選取想要的顏色即可 : 




我選最左邊的藍色, 也可以設定右邊的拉稈將游標放大, 這樣會更醒目. 

Google 傳統協作平台網站內容下載

 Google 傳統協作平台在今年 1/30 日正式關閉, 無法瀏覽內容, 參考 : 




我以前將協作平台當作私人雜記簿, 例如各種網站服務之帳號密碼 (用提示語紀錄) 與學習筆記, 沒法再瀏覽實在很不方便. Google 建議改用以雲端硬碟為基礎的新版協作平台, 但用起來沒有舊版好用. 我原以為錯過了下載的時限, 前幾天發現其實還是可以下載舊網站內容就先匯出, 但不是匯出當天就能下載, 大概要 1~2 天後. 

先到舊協作平台的 dashboard : 





點左下角 "傳統版協作平台管理工具", 在下面頁面中點選舊協作平台名稱 :




這裡會顯示之前申請的匯出作業, 點右下角 "在雲端硬碟中開啟" :




按左邊的下載鈕, 就會將匯出的舊協作平台網頁壓縮檔下載到本機 : 




解開所下載的 zip 檔就可以看到網頁檔了 : 




開啟 html 檔, 哈哈, 熟悉的版面設計滿血回歸 : 




不過 html 檔名裡的中文字元都改用拼音表示, 有空想改回中文. 

2023年9月21日 星期四

母親 9 周年忌日

今天是母親 9 周年忌日, 誦阿彌陀經一卷迴向.  

好站 : 1111 生成式創新學院 (科技島) 課程影片

1111 人力銀行的生成式創新學院自從 ChatGPT 於去年底風行後, 便定期舉辦免費的線上課程, 邀請業界名師講授相關技術與知識, 如果時間不允許, 也可以在其 Youtube 頻道回看, 網址如下 :


如果要報名未來的講座, 可先註冊 1111 帳號 (或用 Goole/Meta 帳號快速登入), 再到科技島網站點選要上的課程報名 :


參加高雄 Python 社群 Django 分享會

昨天晚上去文藻參加高雄 Python 社群 Django 分享會收穫頗豐, 但回家後沉溺在韓劇 "Moving 異能" 第一季最後三集忘了寫, 今日補記一下. 

這幾天為了這堂課複習 Django 時, 在 App 架構這邊遇到鬼打牆, 經過 Victor 老師講解終於豁然開朗 (結論 : templates 與 static 最好都放在 App 之下各自獨立, 別放在專案目錄下, Django 的架構雖然有彈性, 但即使是小專案還是養成 reusable 的架構習慣為宜), 但兩個小時實在太短, 後面最重要的 Class-based View 只能簡介而已, 續集大力敲碗中..., 在這之前可到 Victor 老師放在 GitHub 的 Django 筆記腦補一下  :


後續可能會講 Dash, Gradio, Streamlit (我的最愛), .... 等 Python 網頁技術, 參考 :


高老師曾數次被邀請來我司內訓課程授課, 講課方式輕鬆詼諧, 內容由淺入深現場實作 (不怕 error 當場 debug), 讓初學者能快速理解上手, 我每次都施展吸星大法吸飽吸滿, 滿載而歸. 

2023年9月20日 星期三

市圖還書 2 本

今天去文藻參加高雄 Python 社群 Django 分享會時順路去河堤還下面兩本書 : 
No.1 借很久了才被預約, 先記下來以後有空要學日文時再回借. No.2 目前用不到了 (專案已寫完) 先還, 反正母校還有新版可借. 

2023年9月19日 星期二

貓與狗的隨機圖

今天早上忙完問題處理後剛好看到臉書有茶米老師發的訊息, 早上九點開始在金門大學線上授課, 講題是 Python API 應用, 我因還有工作要做, 只聽了前面兩個單元 (且第一次邊聽課邊實作), 蠻有收穫的. 這兩個單元是製作貓與狗的隨機圖, 老師介紹的兩個隨機圖網站如下 :

https://dog.ceo (狗)

透過 JSON Viewer 網站解析就能找出擷取圖檔的 API 網址 :


這樣就能利用 requests 去爬這兩個網站取得一張隨機圖, 可以用 Hugging Face 的 gradio 套件於 Colab 上製作網頁應用程式, 讓所有人都能透過連結網址使用 gradio 做出來的 App. 先暫時記下來, 有空重作後再來整理筆記. 

茶米老師也曾受邀來我司的內訓課程當講師, 他講課非常有條理, 一步步循序漸進帶學生實作, 只要課堂跟著做一定收穫滿滿, 我非常喜歡上他的課. 


2023-09-21 補充 :

茶米老師此次上課簡報 :


二哥換手機 (Samsung S23 256GB)

昨天二哥傳訊息說他手機故障, 問家裡有無備用機可寄給他, 我找出他高中時用的 HTC One, 此機功能一切正常, 但電池不夠力了, 用到下午就得充電, 在學校沒手機不方便 (這學期老師指定當電子學助教, 還要去 TSRI 做 IC 量測), 乾脆上 momo 幫他買新機算了, 原以為他還要繼續用 Pixel, 結果這次要換三星 S23, 二哥說 128GB 應該就可以, 但 256GB 才差 2000 而已 :







因學校超商沒代收, 宿舍要指定 momo 晚上送貨有困難, 就借他們實驗室同學在校外租處地址, 他同學房東會代收, 還好 momo 可指定收件人與收件地址, 明天就會到貨 (momo 速度就是快). 

2023年9月18日 星期一

Windows 如何暫時放大縮小螢幕

最近在上內訓課程時發現有些老師很貼心, 在說明程式碼時會把螢幕放大, 這樣我們遠端就不用將臉湊近螢幕, 不過我看老師並未更改螢幕顯示設定, 那他們是怎麼做到的呢? 原來是透過快捷鍵開啟放大鏡功能 (這與螢幕顯示設定不同), 參考 : 


按 Windows 與 + 就可開啟放大鏡, 按 Windows 與 - 就可關閉放大鏡, 也可以按住 CTRL+ALT, 然後滑動滑鼠滾輪, 向前滑會放大, 向後滑則縮小, 原來這麼簡單. 等我 Streamlit 筆記整理好也需要用 Google Meet 或 Teams 幫二哥遠端教學, 所以就能派上用場啦. 

2023年9月17日 星期日

2023 年第 37 周記事

這周公務較忙, 因週五有重要服務上線, 週二重新檢視執行步驟, 預先製作好指令, 週五順利完成任務. 這都借助於六~七月時所打造的維運自動化軟體, 所謂工欲善其事必先利其器, 有了為自己量身製作的好工具做起事來才順手.

二哥 12 日回學校上課了, 周四跟我要他郵局帳號, 老師指派他當電子學助教要匯款, 我說那不錯, 可以順便複習微電子學. 室友是台北人, 週五就回家, 所以周末宿舍就他一個人很自在. 

農曆七月在本周結束, 接著要迎接中秋節到來, 雖然還有兩周, 但今天去全聯發現肉品較空, 連金針菇居然也不見了, 難不成有些人提前烤肉啦? 

昨天水某自己去逛好市多, 發現貓砂特價 384 降 95 元, 我說那我周日提早一小時回高雄去買, 結果菁菁說晚上八點媽媽要去給她接睫毛, 不然就會員卡給我去買. 我費了九牛二虎力氣將五箱貓砂抬上推車, 結帳才發現原來要水某本人才可以, 所以白跑一趟. 

最近追的韓劇 "Moving 異能" 已看到第 15 集, 越來越精彩, 高允貞是我的新偶像. 



Win 10 系統更新出現 "您的裝置缺少重要的安全性和品質的修正" 問題

最近兩周給鄉下家的 LEMEL 老電腦做系統更新時總是顯示 "您的裝置缺少重要的安全性和品質的修正", 重開機好幾次還是沒效 :




我找到下面這個影片, 原來要執行 gpedit.msc 叫出 "群組原則編輯器" 去設定才行 : 





但我搜尋 gpedit.msc 居然找不到, 後來搜尋到下面這篇文章, 才知道可能是我的  Win10 太老舊  (我這台的 Win10 是燦坤購機時搭機器的家用版) :


我依照該文作法, 用記事本編輯一個 .bat 批次檔 (主檔名隨意, 此處用 gpeditmsc.bat) :

@echo off
pushd "%~dp0"
dir /b %SystemRoot%\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt
dir /b %SystemRoot%\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientTools-Package~3*.mum >>List.txt
for /f %%i in ('findstr /i . List.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i"
pause

存檔後 (注意存檔類型要選 "所有檔案"), 用系統管理員身分開啟一個命令提示字元視窗 : 




切換到存放該批次檔的目錄, 執行 .bat 檔, 直到出現 "操作順利完成。請按任意鍵繼續 . . ." 即可. 執行過程如下 : 

Microsoft Windows [版本 10.0.18363.1082]
(c) 2019 Microsoft Corporation. 著作權所有,並保留一切權利。

C:\WINDOWS\system32>d:

D:\>gpeditmsc.bat

部署映像服務與管理工具
版本: 10.0.18362.900

映像版本: 10.0.18363.1082

正在處理 1 的 1 - 正在新增封裝 Microsoft-Windows-GroupPolicy-ClientExtensions-Package~31bf3856ad364e35~amd64~zh-TW~10.0.18362.1
[==========================100.0%==========================]
操作順利完成。

部署映像服務與管理工具
版本: 10.0.18362.900

映像版本: 10.0.18363.1082

正在處理 1 的 1 - 正在新增封裝 Microsoft-Windows-GroupPolicy-ClientExtensions-Package~31bf3856ad364e35~amd64~~10.0.18362.959
[==========================100.0%==========================]
操作順利完成。

部署映像服務與管理工具
版本: 10.0.18362.900

映像版本: 10.0.18363.1082

正在處理 1 的 1 - 正在新增封裝 Microsoft-Windows-GroupPolicy-ClientTools-Package~31bf3856ad364e35~amd64~zh-TW~10.0.18362.1082
[==========================100.0%==========================]
操作順利完成。

部署映像服務與管理工具
版本: 10.0.18362.900

映像版本: 10.0.18363.1082

正在處理 1 的 1 - 正在新增封裝 Microsoft-Windows-GroupPolicy-ClientTools-Package~31bf3856ad364e35~amd64~~10.0.18362.1082
[==========================100.0%==========================]
操作順利完成。
請按任意鍵繼續 . . .




這樣再次搜尋 gpedit.msc 就可以找到了 (解除封印啦) :




點擊執行 gpedit.msc 就會開啟 "群組原則編輯器" 了. 點選 "電腦設定/系統管理範本/Window 元件" 這項目 : 




再繼續往下拉找到 "資料收集與預覽版". 點選右邊的 "允許遙測" 後點選中間的 "編輯原則設定" (或雙擊 "允許遙測" 亦可) :



 
這會跳出一個允許遙測視窗, 請勾選左上角的 "已啟用", 中間的 "選項" 下拉選單中點選 "加強", 然後按右下角的 "套用", 再按 "確定" 關閉允許遙測視窗 : 




最好再次點選 "編輯原則設定" 進去檢查設定是否已更改, 這樣便完成設定, 可以關閉 "群組原則編輯器", 然後將電腦重開機, 但是 .... 很可惜. 還是無法進行系統更新 (但收穫是至少將 gpedit.msc 封印解開了). 

後來我找到下面這篇, 原來可以到微軟下載 "Windows 10 更新小幫手" 來解決 :


更新小幫手下載網址 :





按 "立即更新" 鈕會下載一個約 3.2MB 的執行檔 Windows10Upgrade9252.exe, 點擊執行會出現下列視窗, 點右下角的 "立即更新" 即開始下載安裝更新檔 : 






安裝完後要重開機, 繼續下載其他更新項目 : 




途中叫我移除 VirtualBox : 





安裝完畢後必須重開機, 再到 Windows Update 檢查更新, 果然可以下載安裝更新檔 :




檢查控制台/系統, 發現原先 Microsoft 商標前的 2019 字樣消失了 :




之前的舊版有 2019 字樣 : 





再次檢查又發現 .Net 相關的更新 :




下載安裝後再檢查已是最新 : 




從昨晚奮戰到今天終於成功啦, 不過檢查是否可更新至 Win11 答案是否定的 :




畢竟這是一台老電腦了, 只要 Win10 還可以更新就好.