2023年9月16日 星期六

Python 學習筆記 : Django 4 用法整理 (一) 網站結構

因下周三要去文藻參加高雄 Python 社群實體聚會, 這次高老師要講 Django, 我想趁此機會將 Django 的學習筆記寫完 (我從玩 GAE 開始用 Django v1.17 一直學到現在的 v4, 拖好久了), 今天先把之前寫的七篇筆記做個摘要整理以利重新出發 : 


參考書籍 :
本篇主要是濃縮整理之前的七篇筆記. 首先摘要一下 Django 的核心概念 :
  • Django 是採用 MTV 架構的快速架站框架 :
    M : Models, 定義資料庫與類別對應 (models.py)
    T : Templates, 可動態嵌入變數用來回應的網頁模板 (templates 目錄下的網頁檔)
    V : Views, 控制資料庫存取與 URL 處理的控制器 (urls.py + views.py) 
  • Django 是 Python 資料庫驅動網站框架, 它使用 ORM (Object Relational Model) 來處理資料庫介接問題, 使用者只要定義與資料表對應之模型類別, ORM 會透過同步自動於資料庫中建立資料表與代理執行 CRUD 操作, 使用者無需接觸底層的 SQL 指令. 
  • Django 採用鬆耦合架構, 整個網站專案由可重複使用的 App 組成 
Django 框架主要由下列功能組成 :
  • 網站管理工具 : 
    包含建立網站, 測試伺服器, 資料模型中介檔遷移, 靜態資料維護等命令列指令.
  • 模型層工具 :
    內建資料模型類別, 提供資料表欄為對映與 CRUD 操作指令.
  • 視圖層工具 :
    封裝了 HTTP Request 與 Response 操作流程, 提供 URL 映射與範本綁定功能.
  • 網頁模板引擎 :
    提供靜態檔案載入, 範本繼承, 以及過濾器 filter 等頁面內容生成指令. 
  • 表單工具 :
    可依據模型的資料類型與控制項產生 HTML 表單. 
  • 資料庫管理工具 :
    內建 admin 資料庫管理網站.

一. 安裝 Django 套件 : 
   
pip install django     

如果已經安裝過 Django, 可以加 -U 參數更新為最新版 :

pip install django -U

>>> import django
>>> django.VERSION
(4, 2, 5, 'final', 0)
>>> django.__version__
'4.2.5'


二. 建立網站專案 : 

建立 Django 網站專案的指令如下 : 

django-admin startproject <<project_name>>      

以建立名為 mysite 的專案為例, 先開啟命令提示字元視窗, 切換到要架站的工作目錄, 執行下列指令來建立 mysite 網站 : 

django-admin startproject mysite        (這在網站工作目錄下)

此指令會自動在目前工作目錄下建立兩層 mysite 同名資料夾的網站雛形 : 


mysite (上層專案目錄 : 網站管理與根目錄)
     |____ manage.py
     |____ mysite  (下層專案目錄 : 全站設定)
                   |_____ __init__.py
                   |_____ settings.py
                   |_____ urls.py       (路由器 : URL 解析與處理函式指派)
                   |_____ wsgi.py     (網站佈署時設定用)
                   |_____ asgi.py      (網站佈署時設定用)


下層專案目錄的各檔案用途如下表 :


 下層專案目錄預設檔案 說明
 __init.py__ 空檔案, 使下層專案目錄成為一個套件 (package)
 settings.py 專案的設定檔 (網站功能設定)
 ursl.py 專案路由模組 (定義 URL 與委派處理函數)
 wsgi.py 網頁伺服器與 Django 之介面設定檔 (Django 的入口)
 asgi.py 網頁伺服器與 Django 之介面設定檔 (非同步)


其中路由模組 urls.py 負責 URL 解析與處理函式委派 (即 URL 映射), 預設內容如下 :

# urls.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

串列 urlspatterns 的元素為 path() 函式所建立的 Path 物件, 用來對映 URL (第一參數) 與所委派之處理函式 (第二參數), 預設的 Path 物件為 Django 內建管理程式 admin, 其處理函式是特定的 admin.site.urls (這不要改). 

接下來要手動於下層專案目錄 mysite 底下添加一個視圖模組 views.py,  在裡面匯入 django.http.HttpResponse 函式來回應客戶端的 /helloworld 網址要求, 將 HTML 碼傳給 HttpResponse() 即可輸出回應頁面給客戶端, 例如 :

#views.py
from django.http import HttpResponse

def helloworld(request):   # 傳入參數為 HttpRequest 物件, 預設用 request 承接
    return HttpResponse('<b>Hello World! <i>您好</i></b>')


mysite (上層專案目錄 : 網站管理與根目錄)
     |____ manage.py
     |____ mysite  (下層專案目錄 : 全站設定)
                   |_____ __init__.py
                   |_____ settings.py
                   |_____ urls.py         (URL 解析與處理函式指派)
                   |_____ wsgi.py
                   |_____ views.py    (手動新增視圖模組來處理 URL 請求)


然後修改路由模組 urls.py, 在 urlpatterns 串列中添加 'helloworld/' 網址與所對應之處理函式 views.hello(), 首先要從下層 mysite 目錄匯入 views.py 模組 :

# urls.py
from django.contrib import admin
from django.urls import path
from mysite import views       # 或 from . imports views 亦可

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', views.helloworld),    
]

其他的 URL 也是依此方式先在 views.py 撰寫 URL 處理函式, 然後將 URL 與對應之處理函式加入 urlpatterns 串列中. 注意, 網址最後面的倒斜線 / 是必須的, 因為伺服器會自動在後面添加 /, 故 URL 字串最後面必須有 /, 否則解析 URL 時會因找不到匹配的字串而導致錯誤. 


三. 啟動開發伺服器 :

用下列指令啟動 Django 內建的開發伺服器, 按 CTRL+BREAK 可關閉此伺服器 :

python manage.py runserver         (這在上層專案目錄下)

預設會開啟 localhost (127.0.0.1) 的 8000 埠 : 

D:\django\mysite>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
September 16, 2023 - 12:33:38
Django version 4.2.5, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/   
Quit the server with CTRL-BREAK.

用瀏覽器開啟 127.0.0.1:8080/helloworld 即可看到所輸出的網頁 :


上面這個 127.0.0.1 只能用在本機而已, 如果要讓區網內其他主機也可存取網頁, 則必須在啟動 Django 內建的開發伺服器時指定本機在區網中的 IP 位址, 也可以指定埠號, 不一定要用預設之 8000 埠, 例如 :

python manage.py runserver 192.168.2.105 8080           (這在上層專案目錄下)

上面建立的網站只能瀏覽 127.0.0.1:8080/helloworld 這個網址如果連線網站的根目錄 (也就是首頁) 127.0.0.1:8000 會出現 Page not found 錯誤, 這是因為 URL 解析模組 urls.py 裡面找不到根目錄網址 "" 之故, 解決之道是先在視圖模組 views.py 裡添加一個輸出首頁的函式例如 home : 

#views.py
from django.http import HttpResponse

def helloworld(request):   # 傳入參數為 HttpRequest 物件, 預設用 request 承接
    return HttpResponse('<b>Hello World! <i>您好</i></b>')

def home(request):  
    return HttpResponse('<b>歡迎來到我的首頁!</b>')   

然後在路由模組 urls.py 中的 urlpatterns 串列中添加根目錄網址 "" (注意, 是空字串, 不是 "/") 與 views.home 的對應元素 : 

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', views.helloworld),
    path('', views.home),    # 根目錄的 URL 字串是空字串, 不是 "/"
]

這樣連線根目錄網址時就會顯示首頁頁面了 :



用 Django 建立一個可運行的基本網站就是這麼簡單. 


四. 建立應用程式 (App) :

上面的基本網站架構是將網站的所有邏輯功能 (即 URL 處理函式) 都寫在專案目錄下自行建立的視圖模組 views.py 中, 實務上為了提升元件可重用性, 會將不同功能的邏輯獨立為個別的 App, 每一個 App 都有自己的路由模組 urls 與視圖模組 views.py, 這樣每個 App 可以不須修改移植到其他專案重複使用. 

建立 App 的指令如下 : 

python manage.py startapp <AppName>           (這在上層專案目錄下)

例如要將上面的 helloworld 做成 App, 那就在建立 mysite 專案後, 可在第一層專案目錄下執行下列指令建立一個例如名為 myapp1 的 App : 

python manage.py startapp myapp1

這會在第一層專案目錄 mysite 底下建立一個 myapp1 的 App 目錄 :

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


App 目錄下個檔案用途如下表 :


 App 檔案目錄 說明
 migrations 紀錄與資料庫版本與資料模型變更相關之檔案 (用來同步模型與資料庫)
 __init__.py 空檔案, 使 App 目錄形式上符合 Python 套件要求
 admin.py 用來註冊資料模型以便能於管理網頁中管理資料表
 apps.py 儲存與此 App 相關之設定
 models.py 儲存 App 資料模型定義以及資料間之關係
 tests.py 儲存測試程式碼
 views.py 為 urls.py 處理 HTTP 要求與回應之 App 視圖模組


建立 App 後必須修改下層專案目錄下的專案設定檔 settings.py 添加此 App : 


1. 編輯專案設定檔 settings.py : 

開啟第二層專案目錄 mysite 底下的設定檔 settings.py, 搜尋 INSTALLED_APPS, 在這串列末尾添加上面新建的 App=myapp1 :

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp1', 
]

順便也搜尋 LANGUAGE_CODE 與 TIME_ZONE, 將這兩個變數分別從 'en-us' 與 'UTC' 改成 'zh-Hant' 與 'Asia/Taipei' : 

LANGUAGE_CODE = 'zh-Hant'

TIME_ZONE = 'Asia/Taipei'

然後將改好的 settings.py 存檔即可. 


2. 控制器設定 (urls.py + views.py) :

接著處理控制器的部分, 也就是專案路由模組 urls.py 與 App 視圖模組 views.py 要如何搭配, 依據 App 與專案耦合程度的不同, 有兩種做法 :


(1). 由專案路由模組 urls.py 集中委派所有 URL 處理函式 : 

此作法使用專案的路由模組 urls.py 集中委派所有 App 的 URL 處理函式. 

先來處理 myapp1 的視圖模組 views.py, 每個 App 在建立時在其目錄下都會自動建立其視圖模組來處理 URL 請求, 其預設內容如下 :

from django.shortcuts import render

# Create your views here.

可見預設使用較高階的捷徑回應函式 django.shortcut.render() 來輸出回應給客戶端, 此函式主要是配合網頁模板 (Template) 用的, 此處尚未用到, 為了簡單起見仍是用上面範例中的低階回應函式  django.http.HttpResponse(), 程式修改如下 : 

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

def helloworld(request):          # URL='myapp1/helloworld/' 的處理函式
    return HttpResponse('<b>Hello World! <i>您好</i></b>')

def myapp1_home(request):    # URL='myapp1/' 的處理函式
    return HttpResponse('<b>這是 App1 首頁!</b>')  

def project_home(request):    # URL='' 的處理函式
    return HttpResponse('<b>歡迎來到我的首頁!</b>') 

這裡的 project_name() 用來處理專案的根目錄請求 (即網站首頁), 這當然也可以由專案自己的視圖模組處理 (預設不會自動建立, 須自行產生), 此處係委由 myapp1 處理網站首頁. 

接著編輯專案路由模組 urls.py 來委派請求之處理函式, 委派方式寫法有兩種, 第一種寫法是從各 App 的 views.py 匯入所有處理函式 :

# urls.py of project=mysite
from django.contrib import admin
from django.urls import path
from myapp1.views import project_home, myapp1_home, helloworld  # 需匯入 App 所有函式

urlpatterns = [
    path('admin/', admin.site.urls),
    path('myapp1/helloworld/', helloworld),
    path('myapp1/', myapp1_home),                   # App 首頁
    path('', project_home),                                   # 專案首頁
]

這種寫法優點是簡單直觀, 但缺點是新增 URL 時容易忘掉匯入其處理函式, 且各 App 的處理函式不可同名. 注意, 此處專案網站根目錄 (首頁) 的請求是委派給 myapp1 的視圖函式 project_home() 來處理. 

另一種寫法是將 App 的 views.py 模組整個匯入並賦予一個簡名 (例如 app1views), 委派處理函式時就用 '簡名.函式' 方式, 這樣做的好處是能清楚分辨此函式屬於哪一個 App, 增加程式可讀性, 改寫如下 : 

# urls.py of project=mysite
from django.contrib import admin
from django.urls import path
import myapp1.views as app1views      # 匯入整個 views.py 並取簡名

urlpatterns = [
    path('admin/', admin.site.urls),
    path('myapp1/helloworld/', app1views.helloworld),  
    path('myapp1/', app1views.myapp1_home),                # App 首頁
    path('', app1views.project_home),                                # 專案首頁
]

注意, 因為 App 是在專案目錄下的子目錄, 因此瀏覽該 App 下的資源時, URL 必須加上 App 的名稱 (此處為 myapp1), 例如 helloworld 這個資源要用 myapp1/helloworld/ 去瀏覽, 事實上 Django 採用如下的 RestFul 網址結構 :

Protocol://主機網址:埠號/App名稱/App網址/參數1/參數2/參數3... 

所以要瀏覽此網站的三個 URL 如下 :

http://127.0.0.1:8000/     (網站根目錄-首頁)
http://127.0.0.1:8000/myapp1/     (myapp1 的根目錄)
http://127.0.0.1:8000/myapp1/helloworld/      (myapp1 裡的 helloworld)


(2). 由 App 自己的路由模組 urls.py 處理與該App 相關的 URL 請求 :

上面的作法 App 與專案耦合度較高, 因為每一個 App 的請求都由專案的路由模組 urls.py 集中委派, App 本身並沒有自己的路由配置檔, 當 App 要移植到別的專案中使用時, 必須將專案 urls.py 裡面與該 App 有關的 URL 委派設定也複製到另一個專案, 而且當 App 數量多時, 專案 urls,py 會塞滿各個 App 的 URL 委派設定而顯得凌亂. 

解決辦法是讓 App 有自己的路由模組 urls.py, 與該 App 相關的請求就由自己的 urls.py 委派, 專案 urls.py 中只要將各 App 的路由模組含括進去即可, 這樣不僅可以降低 App 與專案的耦合度, 而且專案 urls.py 也會變得簡單明瞭, 結構如下 :




首先改寫上面 App 的視圖模組 views.py : 

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

def helloworld(request):   # URL='myapp1/helloworld/' 的處理函式 
    return HttpResponse('<b>Hello World! <i>您好</i></b>')

def myapp1_home(request):     # App 根目錄 URL= 'myapp1/' 的處理函式
    return HttpResponse('<b>這是 App1 首頁!</b>')   

此處將專案根目錄 (網站首頁) 的處理函式 project_home() 移除改由專案自己的視圖模組 views.py 處理 (專案預設沒有 views.py, 須自行建立), 可進一步降低專案與 App 的耦合度. 然後在各 App 目錄下手動建立一個 urls.py (可將專案的 urls.py 複製一份到 App 目錄下加以修改), 編輯如下 :

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

urlpatterns = [
    path('helloworld/', views.helloworld),
    path('', views.myapp1_home), 
]

這裡是從 App 目錄下匯入視圖模組 views.py, 然後將對此 App 的 URL 請求委派給視圖模組內的處理函式. 注意第二個 path() 的 URL 是 App 的根目錄, 不是專案根目錄. 

這樣 App 的控制器便處理好了. 接下來處理專案的控制器, 首先要在下層專案目錄 mysite 下新增一個視圖模組 (不會自動建立, 可複製 App 的 views.py 上來改), 編輯如下 :

# views.py of project
from django.shortcuts import render
from django.http import HttpResponse

def home(request):  
    return HttpResponse('<b>歡迎來到我的首頁!</b>') 

此處是將上例中委派給 App 的專案根目錄請求移回來專案自行處理. 接下來是編輯專案的路由模組, 這裡重點是為了要將 App 的路由模組含括到專案的路由模組裡, 需要用到 django.urls.include() 函式, 編輯如下 : 

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

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

注意, include() 的傳入參數是字串 'myapp1.urls', 不是 myapp1.urls. 

上面這種作法稱為分散式 URL 映射, 在大型網站專案中包含很多 App, 如果將所有 App 的 URL 映射都集中在專案的路由模組中不利於網站維護, 且會降低 App 的可移植性, 故 Django 用 include() 提供了分散式 URL 映射功能, 即使是小專案最好也採用這種結構 (養成習慣).

事實上, 資料庫管理 admin/ 的 URL 映射也可以用 include() 含括進來, 寫法如下 :

 path('admin/', include('admin.site.urls')),

注意, 傳入 include() 的必須是字串, 故這裡應傳入 'admin.site.urls'. 

這樣就完成全部設定了, 結果與上面第一種作法相同, 差別是最後一種架構的 App 與專案的耦合度最低, 可移植性較高, 後續測試將採用此架構. 專案程式碼可從 GitHub 下載 :


總結此架構如下所示 :

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 路由模組, 手工添加)


其中下層專案目錄的 urls.py 與 App 目錄的 views.py 都是建立專案與 App 時自動建立的; 而專案的 views.py 與 App 的 urls.py 則是為了降低 App 與專案的耦合度才自行建立的. 

沒有留言 :