跳轉到

Django Model 與 Migrations

開始之前

任務目標

在這個章節中,我們會完成:

  • 建立 blog App
  • 建立 Django Model
  • 了解 migrations 機制
  • 學習建立關聯表

建立 blog App

在開始使用 Model 之前,我們先建立一個新的 App 來練習資料庫相關功能。

建立 App

使用 Django 管理指令建立 blog app:

uv run manage.py startapp blog

這會在專案中建立一個 blog 資料夾,包含以下檔案:

blog/
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
└── views.py

註冊 App

core/settings.pyINSTALLED_APPS 中加入 blog

core/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "practices",
    "blog",  # (1)!
]
  1. 新增 blog app

現在 Django 就能辨識這個新的 App 了。

Django Model

在 Django 中,我們使用 Model 來定義資料表的結構。Model 是一個 Python class,Django 會自動將它轉換成資料庫的表格。

建立第一個 Model

blog/models.py 中建立一個 Article Model:

blog/models.py
from django.db import models


class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

__str__ 方法

__str__ 是 Python 的特殊方法,用來定義物件的「字串表示」。

當你在 Django Shell 或 Admin 中查看物件時,會顯示這個方法的回傳值:

# 沒有定義 __str__
>>> Article.objects.first()
<Article: Article object (1)>

# 有定義 __str__
>>> Article.objects.first()
<Article: 我的第一篇文章>

建議每個 Model 都定義 __str__,讓物件更容易識別。

常用的欄位類型

欄位類型 說明 範例
CharField 短文字(需指定 max_length) 標題、名稱
TextField 長文字 文章內容
IntegerField 整數 數量、年齡
FloatField 浮點數 價格
BooleanField 布林值 是否發布
DateField 日期 生日
DateTimeField 日期時間 建立時間
EmailField Email 電子郵件
URLField URL 網址
FileField 檔案 上傳檔案
ImageField 圖片 上傳圖片

完整欄位類型列表請參考 Django Model field reference

常用的欄位選項

選項 說明 範例
max_length 最大長度 CharField(max_length=200)
default 預設值 BooleanField(default=False)
null 資料庫允許 NULL CharField(null=True)
blank 表單允許空白 CharField(blank=True)
unique 唯一值 EmailField(unique=True)
auto_now_add 建立時自動設定 DateTimeField(auto_now_add=True)
auto_now 更新時自動設定 DateTimeField(auto_now=True)

完整欄位選項列表請參考 Django Field options

Migrations 機制

Django 使用 migrations 來管理資料庫結構的變更。

什麼是 Migrations?

flowchart LR
    A[Model<br/>Python Class] -->|makemigrations| B[Migration File<br/>變更記錄]
    B -->|migrate| C[Database<br/>資料庫]
  • Model:Python 程式碼中的資料結構定義
  • Migration File:記錄 Model 變更的檔案
  • Database:實際的資料庫表格

步驟 1:建立 Migration

當你修改了 Model 後,執行:

uv run manage.py makemigrations

這會在 blog/migrations/ 資料夾中建立一個 migration 檔案,記錄你的變更。

輸出類似:

Migrations for 'blog':
  blog/migrations/0001_initial.py
    + Create model Article

步驟 2:套用 Migration

執行 migration 來更新資料庫:

uv run manage.py migrate

輸出類似:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, blog, practices, sessions
Running migrations:
  Applying blog.0001_initial... OK

範例:新增欄位

假設我們想在 Article Model 中新增一個 is_published 欄位:

blog/models.py
from django.db import models


class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_published = models.BooleanField(default=False)

    def __str__(self):
        return self.title

執行 makemigrations

uv run manage.py makemigrations

輸出:

Migrations for 'blog':
  blog/migrations/0002_article_is_published.py
    + Add field is_published to article

Django 會自動偵測到 Model 的變更,並建立一個新的 migration 檔案。

執行 migrate 套用變更:

uv run manage.py migrate

輸出:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, blog, practices, sessions
Running migrations:
  Applying blog.0002_article_is_published... OK

新增欄位時的預設值

當你對已有資料的表格新增欄位時,Django 會問你如何處理現有資料:

  1. 提供預設值:使用 default=... 參數
  2. 允許 NULL:使用 null=True 參數
  3. 互動式輸入:如果沒有設定,Django 會在 makemigrations 時詢問你

建議在新增欄位時都設定 defaultnull=True,避免 migration 時出錯。

範例:建立關聯表

讓我們新增 AuthorTag Model,來練習資料庫關聯:

blog/models.py
from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    bio = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name


class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name


class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_published = models.BooleanField(default=False)

    author = models.ForeignKey(  # (1)!
        Author,
        on_delete=models.CASCADE,
        related_name="articles",
        null=True,
        blank=True,
    )

    tags = models.ManyToManyField(  # (2)!
        Tag,
        related_name="articles",
        blank=True,
    )

    def __str__(self):
        return self.title
  1. 一對多關聯(一個作者可以有多篇文章)
  2. 多對多關聯(一篇文章可以有多個標籤)

不需要手動建立中間表

在前一章的資料庫關聯中,我們提到多對多關聯需要一個中間表(Junction Table)來連接兩個表。

但在 Django 中使用 ManyToManyField 時,Django 會自動建立這個中間表,你不需要手動定義它。

Django 會在背後建立一個名為 {app}_{model1}_{field_name} 的表(例如:blog_article_tags),包含兩個外鍵分別指向 ArticleTag

如果需要在中間表中儲存額外的欄位(例如:加入時間),可以使用 through 參數自訂中間表,請參考 Extra fields on many-to-many relationships

ForeignKey 參數說明

參數 說明
on_delete 當關聯的物件被刪除時的行為
related_name 反向查詢的名稱(從 Author 查 Article)如果不寫預設會是 {model_name}_set
null=True 允許資料庫中為 NULL
blank=True 允許表單中為空白

on_delete 選項

選項 說明
CASCADE 一起刪除(刪除作者時,刪除所有文章)
PROTECT 保護(如果有文章,無法刪除作者)
SET_NULL 設為 NULL(需要 null=True
SET_DEFAULT 設為預設值(需要 default
DO_NOTHING 不做任何事(可能造成資料不一致)

建立遷移檔並套用

uv run manage.py makemigrations
uv run manage.py migrate

常用的 Migration 指令

指令 說明
makemigrations 根據 Model 變更建立 migration 檔案
migrate 套用 migration 到資料庫
showmigrations 顯示所有 migration 的狀態
sqlmigrate ${app_name} ${migration_version} 顯示 migration 的 SQL 語句

不要手動修改 Migration 檔案

Migration 檔案是自動生成的,除非你非常清楚自己在做什麼,否則不要手動修改。

任務結束

完成!

恭喜你完成了這個章節!現在你已經:

  • 建立 blog App
  • 建立 Django Model
  • 了解 migrations 機制
  • 學習建立關聯表