小记 | 在 django 项目中使用 migration 管理初始数据
最近接触的项目中自带一些初始数据,像页面菜单的管理,默认用户、默认配置等等,在初始化数据库表结构后,将相关数据写入表中,方便用户使用。之前的处理方式是将这些数据存放到一个 json
文件中,再在执行完 python manage.py migrate
之后,使用命令 python manage.py loaddata <json-file>
将这些数据导入到数据库对应的表中。
随着项目进展和需求变更,这个文件也越来越大,同时也出现了很多问题,比如:
- 多次更新时有的数据会覆盖掉之前的数据,有的则不会,取决于主键字段是否存在,而主键又是自增的,随着用户的使用可能还会产生新的内容,如果不处理这种情况的话很可能会覆盖掉用户自己的使用数据
- 有的数据需要覆盖,有的不需要,单凭一个
loaddata
命令很难处理复杂的情况 - 文件内容太多导致分支合并出现冲突时不小心就会错误的处理掉部分冲突的数据,导致合并后部分数据丢失
通过查阅 Django 官方文档后,发现 Django 本身就提供了一种更好的方式用来管理初始数据(参考链接:https://docs.djangoproject.com/zh-hans/5.0/intro/tutorial02/),那就是将这些数据的变更放到 migrations
迁移文件中,每次执行 python manage.py migrate
的时候,这些数据就会自动被应用到数据库,而且相当的便于追溯,比如某个时刻做了哪些改动,无需再去提交记录中查看了。具体的使用方法为:
-
创建一个空的迁移文件,该文件命名为
initial_data
python manage.py makemigrations <app-name> --empty --name initial_data
-
该文件会在应用的
migrations
目录下生成一个新的迁移文件,内容如下:# Generated by Django A.B on YYYY-MM-DD HH:MM from django.db import migrations class Migration(migrations.Migration): dependencies = [ ("yourappname", "0001_initial"), ] operations = []
现在,就可以在里面创建一个函数,用来加载和写入数据,然后放到
operations
里面即可,注意这个函数需要接收两个参数:apps, schema_editor
,比如:# Generated by Django A.B on YYYY-MM-DD HH:MM import os.path as op from django.conf import settings from django.core.management import call_command from django.db import migrations def load_initial_data(apps, schema_editor): filepath = op.join(settings.BASE_DIR, 'data/initial_data.json') call_command('loaddata', filepath) class Migration(migrations.Migration): dependencies = [ ("yourappname", "0001_initial"), ] operations = [ migrations.RunPython(load_initial_data), ]
直接简单的调用
loaddata
命令可能会存在一些问题,比如,可能会有覆盖掉原有数据的风险,或是由于表名、表结构变更还没有被应用而导致的写入异常等(关于这种情况,可以参考 https://stackoverflow.com/a/39743581/16499496),这里更推荐的是结合实际情况编写具体的逻辑代码。 -
后续的迁移和变更
在完成第二步后,后续的初始数据变更就会简单很多,因为现在已经可以基于之前的内容做操作了,比如,现在我们想把之前的初始数据中的
User
表原来的admin
的用户名改为root
,可以通过python manage.py makemigrations myapp --empty --name change_admin_to_root
新建一个迁移文件,然后编写相关的处理逻辑:# Generated by Django A.B on YYYY-MM-DD HH:MM from django.db import migrations def change_username(apps, schema_editor): User = apps.get_model('myapp', 'User') User.objects.filter(name='admin').update(name='root') class Migration(migrations.Migration): dependencies = [ ("yourappname", "0002_initial_data"), ] operations = [ migrations.RunPython(change_username), ]