IT策士 10余年一線大廠經(jīng)驗,專注 IT 思維、架構、職場進階。我會在公眾號、今日頭條持續(xù)發(fā)布最新文章,助你少走彎路。
大家好,我是IT策士。上一節(jié)我們完成了電商項目的需求分析,并把 10 張核心表寫進了模型代碼,成功在數(shù)據(jù)庫里“落了戶”。但模型只是建出來還不夠,實際開發(fā)中你會頻繁遇到這些場景:修改字段類型、加索引、調整關聯(lián)關系,甚至還可能要做數(shù)據(jù)遷移——把老數(shù)據(jù)“搬”到新結構中。這些操作如果不理解底層原理,早晚會踩坑。
今天我們就來一次 Django 模型進階與數(shù)據(jù)遷移的深度解析,幫你徹底搞懂 ORM 的高級用法,并掌握遷移系統(tǒng)的正確打開方式。
一、上節(jié)回顧與今天目標
上節(jié)回顧:
梳理了電商實體關系,編寫了 users、products、cart、orders、payment 五個 app 的模型代碼;
配置了自定義用戶模型
AUTH_USER_MODEL = 'users.User';執(zhí)行了
makemigrations與migrate,把所有表建在了 SQLite 中。
今天目標:
深入 Django 模型字段的高級選項(
on_delete、related_name、索引、元選項等);理解 Django 遷移系統(tǒng)的工作原理;
實戰(zhàn):給已有模型添加索引、修改字段屬性、創(chuàng)建數(shù)據(jù)遷移;
掌握遷移回滾、查看 SQL、合并遷移等常用操作。
二、模型進階——那些你該掌握的細節(jié)
2.1 on_delete 的六種行為
外鍵字段都必須指定 on_delete。它決定了 被關聯(lián)對象被刪除時,當前對象的行為。六種取值如下:
在我們的項目中:
Order.sku使用了PROTECT,意思是商品在被訂單引用時不允許刪除,防止歷史訂單出現(xiàn)“幽靈商品”。CartItem.sku使用了CASCADE,商品刪除,購物車里對應的條目也應該消失。Order.user使用PROTECT,用戶不能在有訂單的情況下被直接刪除。
不同的業(yè)務場景需要不同的刪除策略,這是模型設計的一個關鍵細節(jié)。
2.2 related_name 與反向查詢
ForeignKey 會默認創(chuàng)建一個反向關系,名字是 模型名小寫_set,例如 user.order_set.all()。為了語義更清晰,我們通常會自定義 related_name。
在我們的 Order 模型中:
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
related_name='orders', # 自定義反向名
)這樣就能用 user.orders.all() 直接獲取該用戶的所有訂單。如果你不想創(chuàng)建反向關系,可以設置 related_name='+'。
2.3 模型元選項 Meta
每個模型都可以通過內部類 Meta 配置元數(shù)據(jù),常用的有:
db_table:自定義表名,我們統(tǒng)一用了tb_前綴。verbose_name / verbose_name_plural:可讀名稱,Admin 后臺會用到。ordering:默認排序,如['-create_time']。unique_together:聯(lián)合唯一約束。我們在購物車模型中用了('user', 'sku')防止重復添加。indexes:聲明數(shù)據(jù)庫索引(后面詳解)。
2.4 字段屬性詳解
挑幾個項目中重要且容易忽略的字段屬性說一說:
decimal_places:DecimalField的精度至關重要。價格我們用max_digits=10, decimal_places=2,表示最大 10 位數(shù),其中 2 位小數(shù),能表示到 99,999,999.99。auto_now_addvsauto_now:create_time用auto_now_add(新增時自動取值),update_time用auto_now(每次保存都自動更新)。null=Truevsblank=True:前者是數(shù)據(jù)庫層面的允許 NULL,后者是表單驗證層面的允許為空。CharField一般不要用null=True,Django 推薦用空字符串代替。JSONField:Django 3.1 起原生支持 JSON 字段(除 SQLite 外需要數(shù)據(jù)庫支持)。我們用 JSON 存儲商品規(guī)格快照和地址快照,靈活方便。
三、模型優(yōu)化——給已有表加索引和默認值
索引能極大提升查詢效率。電商系統(tǒng)里哪些字段經(jīng)常作為查詢條件?order_no(訂單號)、user(用戶外鍵)、create_time(時間范圍查詢)等。我們來給它們加上索引。
3.1 給訂單表添加索引
編輯 apps/orders/models.py,在 Order 類的 Meta 中添加 indexes:
class Order(models.Model):
# ... 字段省略 ...
class Meta:
db_table = 'tb_order'
verbose_name = '訂單'
verbose_name_plural = verbose_name
ordering = ['-create_time']
indexes = [
models.Index(fields=['user', '-create_time'], name='idx_user_time'),
models.Index(fields=['status'], name='idx_status'),
]我們創(chuàng)建了兩個索引:
聯(lián)合索引
idx_user_time:用戶 ID 加創(chuàng)建時間降序,加速“某用戶的訂單列表”查詢。單列索引
idx_status:加速按狀態(tài)篩選訂單。
3.2 給 SKU 表添加索引
編輯 apps/products/models.py,在 SKU 的 Meta 里加上:
class SKU(models.Model):
# ... 字段省略 ...
class Meta:
db_table = 'tb_sku'
verbose_name = 'SKU'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['spu', 'is_active'], name='idx_spu_active'),
]聯(lián)合索引 idx_spu_active 用于加速“查詢某個 SPU 下所有上架 SKU”。
3.3 給商品 name 字段增加長度上限優(yōu)化
SKU.name 目前長度 200,對于某些長規(guī)格名的商品可能不夠,我們擴展到 500,同時加上數(shù)據(jù)庫級別的默認空字符串(避免 NULL):
name = models.CharField(
max_length=500,
default='',
verbose_name='SKU 名稱'
)四、數(shù)據(jù)遷移——從原理到實戰(zhàn)
4.1 Django 遷移系統(tǒng)是如何工作的?
每次執(zhí)行 makemigrations,Django 會掃描所有已注冊 app 的 models.py,與上一次遷移后的狀態(tài)做對比,生成一個遷移文件(存在各 app 的 migrations/ 目錄下)。遷移文件里有兩個關鍵部分:
state_operations:描述模型“應該長什么樣”。database_operations:描述要在數(shù)據(jù)庫上執(zhí)行的操作(ALTER TABLE、CREATE INDEX 等)。
執(zhí)行 migrate 時,Django 會根據(jù)遷移文件里的操作,翻譯成對應的數(shù)據(jù)庫 DDL 語句。
4.2 生成新的遷移
修改完模型后,運行:
python manage.py makemigrations控制臺輸出:
Migrations for 'orders':
apps/orders/migrations/0002_auto_20260519_1430.py
- Create index idx_user_time on field(s) user, -create_time of model order
- Create index idx_status on field(s) status of model order
Migrations for 'products':
apps/products/migrations/0002_alter_sku_name_add_index.py
- Alter field name on sku
- Create index idx_spu_active on field(s) spu, is_active of model sku如果你對遷移文件名不滿意,可以用 --name 參數(shù)自定義:
python manage.py makemigrations orders --name add_order_indexes4.3 查看遷移對應的 SQL(很實用?。?/span>
在執(zhí)行遷移前,我們最好先看一眼它會在數(shù)據(jù)庫里執(zhí)行哪些 SQL。使用 sqlmigrate 命令:
python manage.py sqlmigrate orders 0002輸出示例(SQLite 版):
BEGIN;
--
-- Create index idx_user_time on field(s) user, -create_time of model order
--
CREATE INDEX "idx_user_time" ON "tb_order" ("user_id", "create_time" DESC);
--
-- Create index idx_status on field(s) status of model order
--
CREATE INDEX "idx_status" ON "tb_order" ("status");
COMMIT;換成 MySQL 或 PostgreSQL,生成的 DDL 會相應變化。這個命令可以讓你在動手之前“審閱”SQL,避免意外。
4.4 執(zhí)行遷移
確認無誤后,執(zhí)行:
控制臺輸出:
Operations to perform:
Apply all migrations: orders, products
Running migrations:
Applying orders.0002_add_order_indexes... OK
Applying products.0002_alter_sku_name_add_index... OK這時索引已經(jīng)建立在數(shù)據(jù)庫里了??梢杂?dbshell 進入 SQLite,輸入 .indexes tb_order 查看索引列表。
五、遷移的“后悔藥”——回滾與恢復
5.1 撤銷最近一次遷移
假設剛才的遷移有問題,我們可以退回到指定狀態(tài):
python manage.py migrate orders 0001這個命令會將 orders app 的遷移狀態(tài)退回到 0001,對應的表結構也會回退(索引會被刪除)。
執(zhí)行后會提示:
Operations to perform:
Target specific migration: 0001_initial, from orders
Running migrations:
Rendering model states... DONE
Unapplying orders.0002_add_order_indexes... OK5.2 撤銷所有遷移(慎用)
如果想回到零遷移狀態(tài)(表清空),可以指定 zero:
python manage.py migrate orders zero但在生產(chǎn)環(huán)境千萬別這么做,除非你真的要刪除整個 app 的數(shù)據(jù)表。
六、高級話題:數(shù)據(jù)遷移(Data Migration)
有時候你不僅要改表結構,還需要搬移或轉換數(shù)據(jù)。比如我們想在 tb_users 表里給現(xiàn)有用戶批量生成一個默認的“手機號”字段,讓其不再是 NULL。
6.1 創(chuàng)建空遷移作為數(shù)據(jù)遷移的殼
python manage.py makemigrations users --empty --name populate_user_phone這會生成一個空的遷移文件 apps/users/migrations/0002_populate_user_phone.py。我們打開它,在 operations 列表里寫上數(shù)據(jù)遷移邏輯:
from django.db import migrations
def populate_phone_default(apps, schema_editor):
User = apps.get_model('users', 'User')
# 將 phone 為空的用戶設置一個臨時占位手機號,實際中可根據(jù)業(yè)務邏輯處理
for user in User.objects.filter(phone__isnull=True):
user.phone = f"1000000{user.id:04d}" # 例如 10000000001
user.save(update_fields=['phone'])
def reverse_phone(apps, schema_editor):
# 回滾邏輯:將生成的手機號置空
User = apps.get_model('users', 'User')
User.objects.filter(phone__startswith='1000000').update(phone=None)
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.RunPython(populate_phone_default, reverse_phone),
]然后執(zhí)行遷移:
這樣既修改了表結構,也填充了歷史數(shù)據(jù)。
七、常用遷移命令速查
八、總結與下集預告
今天我們把 Django 模型從“會用”提升到了“懂得內部機制”的水平:
吃透了
on_delete、related_name、Meta選項的用法;給訂單和商品表添加了關鍵索引,優(yōu)化查詢;
深入理解了 Django 遷移的原理,學會了生成、查看、回滾遷移;
初次體驗了數(shù)據(jù)遷移,處理了歷史數(shù)據(jù)問題。
有了扎實的模型基礎,下一步就該讓數(shù)據(jù)“活”起來了。第 4 天,我們將迎來 Django 最讓人興奮的功能之一——Admin 后臺管理。我會教你如何在 10 分鐘內搭出一個能管理用戶、商品、訂單的后臺,并實現(xiàn)一些高級定制,比如列表過濾、搜索、自定義表單……讓運營人員也能直接上手。
想了解更多還可以去公眾號、今日頭條搜索「IT策士」,一起升級 IT 思維 !
本文為《Django 從 0 到 1 打造完整電商平臺》系列第 3 篇,作者:IT策士,未經(jīng)授權禁止轉載。