Django 從 0 到 1 打造完整電商平臺:Django 模型進階與數(shù)據(jù)遷移

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í)行了 makemigrationsmigrate,把所有表建在了 SQLite 中。

今天目標:

  1. 深入 Django 模型字段的高級選項(on_delete、related_name、索引、元選項等);

  2. 理解 Django 遷移系統(tǒng)的工作原理;

  3. 實戰(zhàn):給已有模型添加索引、修改字段屬性、創(chuàng)建數(shù)據(jù)遷移;

  4. 掌握遷移回滾、查看 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_placesDecimalField 的精度至關重要。價格我們用 max_digits=10, decimal_places=2,表示最大 10 位數(shù),其中 2 位小數(shù),能表示到 99,999,999.99。

  • auto_now_add vs auto_nowcreate_timeauto_now_add(新增時自動取值),update_timeauto_now(每次保存都自動更新)。

  • null=True vs blank=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,在 SKUMeta 里加上:

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_indexes

4.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... OK

5.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_nameMeta 選項的用法;

  • 給訂單和商品表添加了關鍵索引,優(yōu)化查詢;

  • 深入理解了 Django 遷移的原理,學會了生成、查看、回滾遷移;

  • 初次體驗了數(shù)據(jù)遷移,處理了歷史數(shù)據(jù)問題。

有了扎實的模型基礎,下一步就該讓數(shù)據(jù)“活”起來了。第 4 天,我們將迎來 Django 最讓人興奮的功能之一——Admin 后臺管理。我會教你如何在 10 分鐘內搭出一個能管理用戶、商品、訂單的后臺,并實現(xiàn)一些高級定制,比如列表過濾、搜索、自定義表單……讓運營人員也能直接上手。

想了解更多還可以去公眾號、今日頭條搜索「IT策士」,一起升級 IT 思維 !


本文為《Django 從 0 到 1 打造完整電商平臺》系列第 3 篇,作者:IT策士,未經(jīng)授權禁止轉載。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容