🚀 Day 7:資料庫與模型設計(Model)
5 天營隊第二週 (Day 7 / 總時數 6 Hours) | 告別 JSON 文字檔,擁抱 SQLite 資料庫與 Django ORM 站長後台!
📖 為什麼學 these?——「理財咖啡館打造資料庫與站長後台」關卡冒險故事線
雖然 expenses.json 很好用,但如果同時有 100 個人一起記帳,檔案會因為被搶著讀寫而鎖死損毀!今天我們要幫理財咖啡館升級為 **關聯式資料庫 (SQLite)**,使用 Django ORM (Object-Relational Mapping) 在 `models.py` 中設計 Schema,並登入超有質感的內建 Admin 站長後台管理所有資料!
| 單元名稱 | 記帳程式關卡角色 | 為什麼這個單元不可或缺?(技術關聯) |
|---|---|---|
| 單元 1:從 JSON 到真正的資料庫 | 🗄️ 升級專業防鎖死資料庫檔案櫃 | 瞭解關聯式資料庫 (RDBMS) 的好處,說明為什麼多使用者併發寫入時需要 SQLite 來替代 Day 4 的 JSON 檔案。 |
| 單元 2:定義 Django Model | 📐 資料庫表格建構藍圖 (models.py) | 在 models.py 撰寫 Python 類別 (Class),定義品項、金額、分類與時間欄位,對應 Day 2 的 List of Dicts 結構。 |
| 單元 3:資料庫遷移 (Migrations) | 🏗️ SQL 翻譯官與工程施工 (migrate) | 執行 makemigrations 與 migrate,將 Python 藍圖翻譯成真正的資料庫表格,對應 Day 4 的防呆建檔。 |
| 單元 4:超級管理員與 Admin 後台 | 👑 網頁版站長後台 (admin.py) | 建立 createsuperuser 並註冊 Model,不用寫前端就能登入高質感網頁後台進行完整的網頁 CRUD 操作! |
| 單元 5:ORM 基礎操作 (Shell) | 🔮 不用寫 SQL 的 Python 魔法 (Shell) | 開啟 manage.py shell,用純 Python 語法(.objects.all(), .filter())對 SQLite 進行資料新增與查詢。 |
| 單元 6:資料轉移黑客松 | 🚚 無痛移民:JSON 自動搬家腳本 | 撰寫一小段 Python 轉移腳本,自動讀取 Day 4 的 expenses.json,一秒匯入 SQLite 資料庫中! |
📌 課程前導與簡介說明
- 營隊定位:從純檔案讀寫過渡到專業資料庫 (ORM)。讓高中生明白不用背 SQL 語法也能掌控 SQLite 資料庫。
- 高中生心理亮點:當學生登入
127.0.0.1:8000/admin/,看到自己設計的欄位出現在高質感後台,還能親手點擊新增、修改時,會獲得極高成就感! - 教學銜接點:不斷對照舊觀念(Model 對應 Day 2 的 Dictionary Keys;ORM 對應 Day 2 的
filter迴圈;後台 CRUD 對應 Day 4 & Day 5)。
歡迎來到 Day 7!今天我們要把記帳資料庫真正建立起來!請閱讀每個單元的 **故事單元說明**,點擊 **延伸閱讀** 展開深層觀念與 Google 搜尋,並善用每區塊的 **AI 協作除錯提示詞** 來練習寫碼!
💻 Day 7 專案總成果:Django Model 定義與 JSON 資料匯入腳本
【教師參考解答】學生於 Day 7 結束時將完成的 Model 與匯入腳本:
1. 記帳資料模型 (`expenses/models.py`):
from django.db import models
class Expense(models.Model):
""" 記帳模型 (對應 DB 的 Table) """
item = models.CharField(max_length=100, verbose_name="品項名稱")
price = models.IntegerField(verbose_name="消費金額")
category = models.CharField(max_length=50, default="一般", verbose_name="消費分類")
date = models.DateField(auto_now_add=True, verbose_name="紀錄日期")
def __str__(self):
return f"{self.item} - ${self.price} ({self.category})"
class Meta:
verbose_name = "消費紀錄"
verbose_name_plural = "消費紀錄清單"
2. 後台註冊 (`expenses/admin.py`):
from django.contrib import admin
from .models import Expense
@admin.register(Expense)
class ExpenseAdmin(admin.ModelAdmin):
list_display = ('id', 'item', 'price', 'category', 'date') # 定義列表中顯示的欄位
list_filter = ('category', 'date') # 側邊過濾器
search_fields = ('item',) # 搜尋列
3. JSON 資料轉移腳本 (`import_json.py` 或在 `manage.py shell` 執行):
import json
from expenses.models import Expense
def migrate_json_to_db():
try:
with open("expenses.json", "r", encoding="utf-8") as f:
data = json.load(f)
count = 0
for r in data:
# 使用 ORM 建立資料列
Expense.objects.create(
item=r.get("item", r.get("name", "未命名")),
price=r.get("price", 0),
category=r.get("category", "一般")
)
count += 1
print(f"🎉 成功將 {count} 筆舊 JSON 資料匯入 SQLite 資料庫!")
except FileNotFoundError:
print("⚠️ 找不到舊的 expenses.json 檔案!")
# 執行函式
migrate_json_to_db()
【學生自主實作框架】請根據今日各單元所學,將空缺的 `____` 填入正確的 Django Model 語法:
# 🎯 Day 7 自主挑戰:請填入 models.py 與 admin.py 空缺的 ____ 關鍵字!
# --- [檔案: expenses/models.py] ---
from django.db import models
class Expense(models.____): # 提示:繼承 Django 的 Model 類別
item = models.____(max_length=100) # 提示:文字字串欄位 CharField
price = models.____() # 提示:整數數字欄位 IntegerField
category = models.CharField(max_length=50, default="一般")
def __str__(self):
return f"{self.item} - ${self.price}"
# --- [檔案: expenses/admin.py] ---
from django.contrib import admin
from .models import ____ # 提示:匯入剛建好的 Model
admin.site.____(Expense) # 提示:註冊 Model 到 admin 後台的函式 register
⏰ 單元 1:從 JSON 到真正的資料庫(09:00 - 10:00)
在 Week 1 我們使用 expenses.json 儲存資料,這就像把帳記在「純文字筆記本」上。但如果有多個使用者同時寫入,文字檔會因為被搶著存取而鎖死損毀!關聯式資料庫 (SQLite) 就像是「帶有智能防鎖鎖頭的防火保險箱」,能確保多線程讀寫的安全(ACID 特性),並且查詢速度快上幾百倍!
JSON 檔案與專業資料庫 (SQLite/PostgreSQL) 的根本差異:
- ACID 安全特性:原子性 (Atomicity)、一致性 (Consistency)、隔離性 (Isolation)、持久性 (Durability),保證交易不會算錯錢。
- SQLite 檔案型資料庫:Django 預設的 SQLite 是無需設定伺服器的輕量級 SQL 資料庫,整個資料庫就是一個
db.sqlite3檔案。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請向高中生說明為什麼大型 Web 應用程式不能只用 JSON 檔案儲存資料,而需要採用 SQLite 或 PostgreSQL 關聯式資料庫,並解釋什麼是資料庫的 ACID 特性。
- 講解 JSON 檔案併發讀寫被鎖死 (Lock) 的局限。
- 介紹關聯式資料庫 (RDBMS) 與 SQLite 的優勢。
- 概念連結:Day 4 的 JSON 持久化寫檔進化為資料庫。
# myproject/settings.py 預設使用 SQLite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3', # 專案根目錄下的 db.sqlite3 檔案
}
}
- 直接用記事本打開 db.sqlite3 看到亂碼:
db.sqlite3是二進位 SQLite 資料庫檔案,需透過 Django ORM 或專用工具 (如 SQLite Viewer) 開啟。
我用 VS Code 純文字編輯器打開 db.sqlite3 檔案發現裡面全是亂碼。請告訴我為什麼 SQLite 是二進位檔案,以及該如何在 VS Code 安裝套件來查看裡面的 Table。
⏰ 單元 2:定義 Django Model(10:00 - 11:00)
我們要怎麼告訴資料庫「記帳表格有哪些欄位」呢?在 Django 中,我們不需要撰寫複雜的 SQL 指令,只要在 expenses/models.py 撰寫一個 Python 類別 class Expense(models.Model)!文字欄位用 CharField,金額欄位用 IntegerField,時間用 DateField,完美對應 Day 2 的 Dictionary Keys!
Django Model 透過類別屬性定義表格欄位規格:
- 常用欄位型別:
CharField(max_length=...)(短字串)、IntegerField()(整數)、DateField(auto_now_add=True)(自動填入當前日期)。 - 魔術方法
__str__(self):定義物件在被print()或在 Admin 後台顯示時的純文字代表字串。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請向初學者說明 Django models.py 中 CharField, IntegerField 與 DateField 的使用方法,並解釋為什麼要在 Model 類別裡面定義 __str__() 魔術方法。
- 編輯
expenses/models.py定義Expense類別。 - 介紹
CharField,IntegerField,DateField與__str__。 - 概念連結:對應 Day 2 List of Dicts 結構 (Keys 變為 Model Fields)。
from django.db import models
class Expense(models.Model):
item = models.CharField(max_length=100) # 品項名稱
price = models.IntegerField() # 消費金額
category = models.CharField(max_length=50, default="一般") # 分類
date = models.DateField(auto_now_add=True) # 自動寫入建立時的日期
def __str__(self):
return f"{self.item} - ${self.price}"
- CharField 漏寫 max_length:
CharField必須指定max_length參數,否則執行遷移時會拋出TypeError: __init__() missing 1 required positional argument: 'max_length'。
我在 models.py 中寫 CharField 時出現了 TypeError: missing 1 required positional argument: 'max_length'。請教我如何正確加上 max_length 參數。
⏰ 單元 3:資料庫遷移 (Migrations)(11:00 - 12:00)
寫完 models.py 之後,SQLite 資料庫還看不懂 Python 程式碼!我們需要兩道神奇的指令:
1. python manage.py makemigrations:把 Python 類別翻譯成建築施工藍圖(Migration 檔案)
2. python manage.py migrate:按照藍圖,真正跑去 SQLite 資料庫裡面把表格實體建造出來!
Migration 是 Django 追蹤與同步 Model 變更的版控機制:
makemigrations:檢查models.py的修改,在expenses/migrations/目錄產出如0001_initial.py的藍圖檔。migrate:將藍圖轉化為 SQL 語法 (如 `CREATE TABLE`) 並在資料庫執行,完成 Schema 更新。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請詳細說明 Django 的 makemigrations 與 migrate 兩者之間的差別,並用『建築施工藍圖』與『實際工地建造』來做比喻。
- 執行
python manage.py makemigrations。 - 執行
python manage.py migrate。 - 帶學生觀察生成檔
expenses/migrations/0001_initial.py與db.sqlite3。
# 1. 產出資料庫變更藍圖檔 (Migration File)
python manage.py makemigrations
# 2. 正式施工套用至 db.sqlite3 資料庫
python manage.py migrate
- No changes detected:修改了 `models.py` 執行 `makemigrations` 顯示無變更 $\rightarrow$ 檢查忘記把 App 註冊到 `settings.py` 的 `INSTALLED_APPS` 中。
我在執行 makemigrations 時顯示 No changes detected 但我明明寫好了 models.py。請幫我檢查 settings.py 的 INSTALLED_APPS 是否忘記註冊 App。
⏰ 單元 4:超級管理員與 Admin 後台(13:00 - 14:00)
Django 最吸引人的黑科技就是內建了「超級站長管理後台」!我們執行 python manage.py createsuperuser 設定管理員密碼,接著在 expenses/admin.py 加上 admin.site.register(Expense)。前往 http://127.0.0.1:8000/admin/ 登入,不用寫半行 HTML 前端,就能像站長一樣在網頁畫面上對記帳資料進行完整的 CRUD 增刪查改!
Django Admin 是生產力極高的開箱即用工具:
admin.site.register(Model):將 Model 註冊給 Admin 後台託管。- 自訂
ModelAdmin類別:透過list_display控制顯示欄位,透過list_filter啟用側邊過濾列,透過search_fields啟用搜尋框。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請說明如何將自訂的 Django Model 註冊到 admin.py,並示範使用 list_display, search_fields 與 list_filter 來美化站長管理後台列表。
- 在 Terminal 執行
python manage.py createsuperuser設定帳密。 - 在
expenses/admin.py中註冊Expense模型。 - 帶學生開啟
http://127.0.0.1:8000/admin/登入體驗 UI 後台 CRUD!
# 1. Terminal 建立管理員帳號
# python manage.py createsuperuser
# 2. 編輯 expenses/admin.py 註冊 Model
from django.contrib import admin
from .models import Expense
@admin.register(Expense)
class ExpenseAdmin(admin.ModelAdmin):
list_display = ('id', 'item', 'price', 'category', 'date')
list_filter = ('category',)
search_fields = ('item',)
- 忘記密碼或密碼太簡單:`createsuperuser` 輸入密碼時螢幕不會顯示任何字元(保護機制),直接敲完按 Enter 即可。
我在執行 createsuperuser 輸入密碼時螢幕完全沒有顯示字元。請告訴我這是 Linux/Terminal 的安全特性,以及忘記密碼時如何用 changepassword 指令重置。
⏰ 單元 5:ORM 基礎操作 (Shell)(14:00 - 15:00)
我們不用去死背複雜的 SQL 語法 SELECT * FROM ...!Django 提供了強大的 **ORM (Object-Relational Mapping)**,讓你可以直接用 Python 語法對資料庫進行控制!執行 python manage.py shell,輸入 Expense.objects.all() 抓出所有資料,或輸入 Expense.objects.filter(category='餐飲') 進行過濾,體驗 Python 指令直接操作 SQLite 的快感!
ORM 充當了 Python 物件與關聯式資料庫之間的橋樑:
- 常用 QuerySet 方法:
.all()(取全部)、.filter(key=val)(條件過濾)、.get(id=1)(取單筆)、.create(...)(新建並寫入)。 - 惰性求值 (Lazy Evaluation):QuerySet 在建立時不會立刻查詢資料庫,直到你真正需要印出或走訪它時才會執行 SQL 查詢。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請向初學者解釋什麼是 Django ORM,並示範在 manage.py shell 中使用 .objects.all(), .filter(), .get() 與 .create() 進行資料庫 CRUD 操作。
- 在 Terminal 執行
python manage.py shell進入 Python 互動環境。 - 示範 ORM CRUD 指令:
Expense.objects.create(...)、Expense.objects.all()與.filter(...)。 - 概念連結:對應 Day 2 清單演練與
filter分類搜尋。
# 在 python manage.py shell 內部執行:
from expenses.models import Expense
# 1. [C]reate 新增一筆資料
Expense.objects.create(item="排骨飯", price=110, category="餐飲")
# 2. [R]ead 查詢所有資料 (回傳 QuerySet)
all_data = Expense.objects.all()
print(all_data)
# 3. 條件過濾 (Filter)
food_data = Expense.objects.filter(category="餐飲")
print(food_data)
- 忘記 import Model:直接在 shell 輸入 `Expense.objects.all()` 跳出 `NameError: name 'Expense' is not defined` $\rightarrow$ 記得先寫 `from expenses.models import Expense`。
我在 manage.py shell 裡面執行 Expense.objects.all() 時跳出 NameError: name 'Expense' is not defined。請提醒我如何先從 expenses.models 匯入 Expense 類別。
⏰ 單元 6:資料轉移黑客松 (JSON 到 DB 自動匯入)(15:00 - 16:00)
前一週大家在 expenses.json 裡面記下的舊帳務資料怎麼辦?難道要手動重新輸入嗎?當然不!在這個黑客松單元中,我們寫一小段 Python 轉移腳本:結合 Day 4 的 json.load() 讀取檔案,並搭配今天剛學的 Expense.objects.create(),一秒鐘自動把所有歷史資料搬家移籍進 SQLite 資料庫裡!
將外部 JSON/CSV 搬家至 DB 是常見的工程需求:
- 欄位相容處理
dict.get('key', default):預防舊 JSON 檔案缺欄位造成的KeyError。 - 批量寫入
bulk_create():一次性送出多筆 SQL `INSERT`,避免在迴圈中做數千次資料庫連線 I/O。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
我有一份舊的 expenses.json 記帳資料,請教我如何撰寫一段 Python/Django 腳本,讀取 JSON 後自動透過 ORM 的 Expense.objects.create() 寫入 SQLite 資料庫。
- 輔導學生撰寫轉移腳本(可在 `manage.py shell` 中貼上執行)。
- 結合 Day 4 的
json.load()讀檔與今日 ORMExpense.objects.create()。 - 帶學生開
127.0.0.1:8000/admin/驗證舊資料成功移民至 SQLite!
# 轉移腳本關鍵邏輯 (在 manage.py shell 中執行)
import json
from expenses.models import Expense
with open("expenses.json", "r", encoding="utf-8") as f:
old_data = json.load(f)
for row in old_data:
Expense.objects.create(
item=row.get("item", row.get("name")),
price=row.get("price", 0),
category=row.get("category", "一般")
)
print("✅ 所有歷史 JSON 帳務資料已無痛匯入 SQLite 資料庫!")
- 舊 JSON 字典 Key 欄位不對應:例如舊檔寫 `"name"` 但 Model 欄位定義為 `item` $\rightarrow$ 善用
row.get("item", row.get("name"))進行安全轉換。
我在匯入舊 JSON 資料至 Django Model 時跳出了 KeyError: 'item'。請教我如何用 dict.get() 來預防欄位名稱不一致的問題。