Django数据迁移中引用外部变量的最佳实践
Django 数据迁移编写最佳实践
核心原则先讲透:迁移文件是数据库变更的永久历史快照,一旦提交合入、被任何环境执行过,它依赖的所有逻辑、取值就必须是固定不变的,不能绑定任何会随业务迭代变动的外部代码。
直接导入业务侧变量/类的风险
开发中这类写法踩坑概率极高:
- 后续业务迭代如果删除、重命名了你导入的枚举、常量、函数,新环境从零搭建执行迁移时会直接抛导入错误,根本跑不完迁移流程。
- 如果后续修改了这些外部变量的取值、调整了导入函数的逻辑,老环境执行迁移回滚、或者补跑历史迁移时,实际执行的逻辑和你当初写迁移时的预期完全不一致,很容易直接改脏生产数据。
不要抱有"这个枚举我肯定不会改"的侥幸,业务迭代周期拉长后,没有任何业务侧的代码是绝对不变的。
正确处理方式
1. 常量、枚举值直接硬编码
所有用到的业务固定取值,直接写死迁移编写时点的实际值,不要导入外部的枚举类、常量模块。
以你举的用户角色迁移为例,如果你写迁移时UserRoles.NORMAL_USER.value实际存储的是字符串normal,UserRoles.ADMIN.value实际是admin,直接把值硬编码到迁移逻辑里即可,担心可读性差可以加一行注释说明取值对应的业务含义,修改后的可长期稳定运行的迁移代码如下:
from django.db import migrations def change_user_role(apps, schema_editor): User = apps.get_model('users', 'User') # 取值对应迁移编写时UserRoles.NORMAL_USER的存储值 users = User.objects.filter(role="normal") for user in users: user.role = "admin" User.objects.bulk_update(users, ["role"]) def revert_user_role_changes(apps, schema_editor): User = apps.get_model('users', 'User') # 取值对应迁移编写时UserRoles.ADMIN的存储值 users = User.objects.filter(role="admin") for user in users: user.role = "normal" User.objects.bulk_update(users, ["role"]) class Migration(migrations.Migration): dependencies = [ ('users', '0015_auto_20220612_0824'), ] operations = [ migrations.RunPython(change_user_role, revert_user_role_changes) ]
2. 复杂逻辑做快照隔离
如果迁移涉及的逻辑非常复杂,硬编码会产生大段冗余代码,也不要直接导入业务层的工具函数、服务方法:
- 优先把需要的逻辑在迁移文件内自包含实现,哪怕和现有业务代码有重复也没关系。迁移是一次性执行的历史代码,少量冗余换长期稳定性性价比极高。
- 如果逻辑实在太长不想复制,就把用到的函数、常量在编写迁移的时点复制一份,放在迁移文件所在目录的独立文件中,后续永远不要修改这份快照代码,保证迁移依赖的所有内容都在迁移目录的可控范围内,不会随业务迭代被改动。
额外避坑提醒
- 迁移中可以正常导入的内容只有两类:Python标准库的稳定API、Django迁移框架明确暴露的稳定方法(比如
apps.get_model()、基础ORM查询/写入接口),不要导入Django内部未公开的API、第三方库的非稳定接口,避免框架/依赖版本升级后迁移失效。 - 不要在迁移中调用模型的自定义方法、业务层封装的服务逻辑,这类代码后续迭代加依赖、改逻辑的概率极高,很容易引发非预期的数据变更。
- 反向回滚逻辑的编写遵循和正向逻辑完全一致的规则,所有取值、逻辑都要固定为写迁移时点的状态,不要依赖外部可变内容。
最后直接回应核心疑问:不需要把所有内容都硬编码,只要你导入的依赖是长期稳定、不会随业务迭代发生变化的就可以用;但凡属于业务代码范畴、未来可能被修改的常量、函数、类,都不要直接导入,要么硬编码取值,要么在迁移侧保留不可变的代码快照。
内容的提问来源于stack exchange,提问作者Kurt Bourbaki
相关产品推荐
相关产品推荐

