Django 从 0 到 1 打造完整电商平台:Django 模型进阶与数据迁移

Django 从 0 到 1 打造完整电商平台:Django 模型进阶与数据迁移 IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。大家好我是IT策士。上一节我们完成了电商项目的需求分析并把 10 张核心表写进了模型代码成功在数据库里“落了户”。但模型只是建出来还不够实际开发中你会频繁遇到这些场景修改字段类型、加索引、调整关联关系甚至还可能要做数据迁移——把老数据“搬”到新结构中。这些操作如果不理解底层原理早晚会踩坑。今天我们就来一次 Django 模型进阶与数据迁移的深度解析帮你彻底搞懂 ORM 的高级用法并掌握迁移系统的正确打开方式。一、上节回顾与今天目标上节回顾梳理了电商实体关系编写了 users、products、cart、orders、payment 五个 app 的模型代码配置了自定义用户模型AUTH_USER_MODEL users.User执行了makemigrations与migrate把所有表建在了 SQLite 中。今天目标深入 Django 模型字段的高级选项on_delete、related_name、索引、元选项等理解 Django 迁移系统的工作原理实战给已有模型添加索引、修改字段属性、创建数据迁移掌握迁移回滚、查看 SQL、合并迁移等常用操作。二、模型进阶——那些你该掌握的细节2.1on_delete的六种行为外键字段都必须指定on_delete。它决定了被关联对象被删除时当前对象的行为。六种取值如下在我们的项目中Order.sku使用了PROTECT意思是商品在被订单引用时不允许删除防止历史订单出现“幽灵商品”。CartItem.sku使用了CASCADE商品删除购物车里对应的条目也应该消失。Order.user使用PROTECT用户不能在有订单的情况下被直接删除。不同的业务场景需要不同的删除策略这是模型设计的一个关键细节。2.2related_name与反向查询ForeignKey会默认创建一个反向关系名字是模型名小写_set例如user.order_set.all()。为了语义更清晰我们通常会自定义related_name。在我们的Order模型中usermodels.ForeignKey(settings.AUTH_USER_MODEL,on_deletemodels.PROTECT,related_nameorders,# 自定义反向名)这样就能用user.orders.all()直接获取该用户的所有订单。如果你不想创建反向关系可以设置related_name。2.3 模型元选项Meta每个模型都可以通过内部类Meta配置元数据常用的有db_table自定义表名我们统一用了tb_前缀。verbose_name / verbose_name_plural可读名称Admin 后台会用到。ordering默认排序如[-create_time]。unique_together联合唯一约束。我们在购物车模型中用了(user, sku)防止重复添加。indexes声明数据库索引后面详解。2.4 字段属性详解挑几个项目中重要且容易忽略的字段属性说一说decimal_placesDecimalField的精度至关重要。价格我们用max_digits10, decimal_places2表示最大 10 位数其中 2 位小数能表示到 99,999,999.99。auto_now_addvsauto_nowcreate_time用auto_now_add新增时自动取值update_time用auto_now每次保存都自动更新。nullTruevsblankTrue前者是数据库层面的允许 NULL后者是表单验证层面的允许为空。CharField一般不要用nullTrueDjango 推荐用空字符串代替。JSONFieldDjango 3.1 起原生支持 JSON 字段除 SQLite 外需要数据库支持。我们用 JSON 存储商品规格快照和地址快照灵活方便。三、模型优化——给已有表加索引和默认值索引能极大提升查询效率。电商系统里哪些字段经常作为查询条件order_no订单号、user用户外键、create_time时间范围查询等。我们来给它们加上索引。3.1 给订单表添加索引编辑apps/orders/models.py在Order类的Meta中添加indexesclass Order(models.Model):# ... 字段省略 ...class Meta: db_tabletb_orderverbose_name订单verbose_name_pluralverbose_name ordering[-create_time]indexes[models.Index(fields[user,-create_time],nameidx_user_time), models.Index(fields[status],nameidx_status),]我们创建了两个索引联合索引idx_user_time用户 ID 加创建时间降序加速“某用户的订单列表”查询。单列索引idx_status加速按状态筛选订单。3.2 给 SKU 表添加索引编辑apps/products/models.py在SKU的Meta里加上class SKU(models.Model):# ... 字段省略 ...class Meta: db_tabletb_skuverbose_nameSKUverbose_name_pluralverbose_name indexes[models.Index(fields[spu,is_active],nameidx_spu_active),]联合索引idx_spu_active用于加速“查询某个 SPU 下所有上架 SKU”。3.3 给商品 name 字段增加长度上限优化SKU.name目前长度 200对于某些长规格名的商品可能不够我们扩展到 500同时加上数据库级别的默认空字符串避免 NULLnamemodels.CharField(max_length500,default,verbose_nameSKU 名称)四、数据迁移——从原理到实战4.1 Django 迁移系统是如何工作的每次执行makemigrationsDjango 会扫描所有已注册 app 的models.py与上一次迁移后的状态做对比生成一个迁移文件存在各 app 的migrations/目录下。迁移文件里有两个关键部分state_operations描述模型“应该长什么样”。database_operations描述要在数据库上执行的操作ALTER TABLE、CREATE INDEX 等。执行migrate时Django 会根据迁移文件里的操作翻译成对应的数据库 DDL 语句。4.2 生成新的迁移修改完模型后运行python manage.py makemigrations控制台输出Migrationsfororders:apps/orders/migrations/0002_auto_20260519_1430.py - Create index idx_user_time on field(s)user,-create_timeof model order - Create index idx_status on field(s)status of model order Migrationsforproducts: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参数自定义python manage.py makemigrations orders--nameadd_order_indexes4.3 查看迁移对应的 SQL很实用在执行迁移前我们最好先看一眼它会在数据库里执行哪些 SQL。使用sqlmigrate命令python manage.py sqlmigrate orders 0002输出示例SQLite 版BEGIN;-- -- Create index idx_user_time on field(s)user,-create_timeof model order -- CREATE INDEXidx_user_timeONtb_order(user_id,create_timeDESC);-- -- Create index idx_status on field(s)status of model order -- CREATE INDEXidx_statusONtb_order(status);COMMIT;换成 MySQL 或 PostgreSQL生成的 DDL 会相应变化。这个命令可以让你在动手之前“审阅”SQL避免意外。4.4 执行迁移确认无误后执行控制台输出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这时索引已经建立在数据库里了。可以用dbshell进入 SQLite输入.indexes tb_order查看索引列表。五、迁移的“后悔药”——回滚与恢复5.1 撤销最近一次迁移假设刚才的迁移有问题我们可以退回到指定状态python manage.py migrate orders 0001这个命令会将ordersapp 的迁移状态退回到0001对应的表结构也会回退索引会被删除。执行后会提示Operations to perform: Target specific migration: 0001_initial, from orders Running migrations: Rendering model states... DONE Unapplying orders.0002_add_order_indexes... OK5.2 撤销所有迁移慎用如果想回到零迁移状态表清空可以指定zeropython manage.py migrate orders zero但在生产环境千万别这么做除非你真的要删除整个 app 的数据表。六、高级话题数据迁移Data Migration有时候你不仅要改表结构还需要搬移或转换数据。比如我们想在tb_users表里给现有用户批量生成一个默认的“手机号”字段让其不再是 NULL。6.1 创建空迁移作为数据迁移的壳python manage.py makemigrationsusers--empty--namepopulate_user_phone这会生成一个空的迁移文件apps/users/migrations/0002_populate_user_phone.py。我们打开它在operations列表里写上数据迁移逻辑from django.dbimportmigrations def populate_phone_default(apps, schema_editor): Userapps.get_model(users,User)# 将 phone 为空的用户设置一个临时占位手机号实际中可根据业务逻辑处理foruserinUser.objects.filter(phone__isnullTrue): user.phonef1000000{user.id:04d}# 例如 10000000001user.save(update_fields[phone])def reverse_phone(apps, schema_editor):# 回滚逻辑将生成的手机号置空Userapps.get_model(users,User)User.objects.filter(phone__startswith1000000).update(phoneNone)class Migration(migrations.Migration): dependencies[(users,0001_initial),]operations[migrations.RunPython(populate_phone_default, reverse_phone),]然后执行迁移这样既修改了表结构也填充了历史数据。七、常用迁移命令速查八、总结与下集预告今天我们把 Django 模型从“会用”提升到了“懂得内部机制”的水平吃透了on_delete、related_name、Meta选项的用法给订单和商品表添加了关键索引优化查询深入理解了 Django 迁移的原理学会了生成、查看、回滚迁移初次体验了数据迁移处理了历史数据问题。有了扎实的模型基础下一步就该让数据“活”起来了。第 4 天我们将迎来 Django 最让人兴奋的功能之一——Admin 后台管理。我会教你如何在 10 分钟内搭出一个能管理用户、商品、订单的后台并实现一些高级定制比如列表过滤、搜索、自定义表单……让运营人员也能直接上手。想了解更多还可以去其它平台搜索「IT策士」一起升级 IT 思维 本文为《Django 从 0 到 1 打造完整电商平台》系列第 3 篇作者IT策士未经授权禁止转载。