因下周三要去文藻參加高雄 Python 社群實體聚會, 這次高老師要講 Django, 我想趁此機會將 Django 的學習筆記寫完 (我從玩 GAE 開始用 Django v1.17 一直學到現在的 v4, 拖好久了), 今天先把之前寫的七篇筆記做個摘要整理以利重新出發 :
# Python 學習筆記 : Django 2 測試 (一) : 請求與回應處理
# Python 學習筆記 : Django 2 測試 (二) : 模板基礎與靜態檔案
# Python 學習筆記 : Django 2 測試 (三) : 模板的匯入與繼承
# Python 學習筆記 : Django 2 測試 (四) : 本機前端框架環境配置
# Python 學習筆記 : Django 2 測試 (五) : 在虛擬環境中佈署網站
# Python 學習筆記 : Django 2 測試 (二) : 模板基礎與靜態檔案
# Python 學習筆記 : Django 2 測試 (三) : 模板的匯入與繼承
# Python 學習筆記 : Django 2 測試 (四) : 本機前端框架環境配置
# Python 學習筆記 : Django 2 測試 (五) : 在虛擬環境中佈署網站
參考書籍 :
- 快速學會Python架站技術:活用Django 4建構動態網站的16堂課 (博碩, 2023)
- Python架站特訓班 : django 3最強實戰 (碁峰, 2021)
- It's Django:用Python迅速打造Web應用 (碁峰, 2021)
- Python網頁框架超集合 : 在Django、Tornado、Flask、Twisted全面應用 (深智, 2022)
本篇主要是濃縮整理之前的七篇筆記. 首先摘要一下 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 (網站佈署時設定用)
|____ 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>')
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
|____ 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 字串是空字串, 不是 "/"
]
這樣連線根目錄網址時就會顯示首頁頁面了 :
四. 建立應用程式 (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 下載 :
# https://github.com/tony1966/tony1966.github.io/blob/master/test/django/django-review-structure.zip
總結此架構如下所示 :
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 與專案的耦合度才自行建立的.
沒有留言:
張貼留言