编写数据库迁移

这一节介绍你可能遇到的在不同情况下如何分析和编写数据库迁移. 有关迁移的入门资料,请查看 the topic guide.

数据迁移和多数据库

在使用多个数据库时,需要解决是否针对某个特定数据库运行迁移。例如,你可能 只 想在某个特定数据库上运行迁移。

为此你可以在RunPython中通过查看schema_editor.connection.alias 属性来检查数据库连接别名:

  1. from django.db import migrations
  2. def forwards(apps, schema_editor):
  3. if not schema_editor.connection.alias == 'default':
  4. return
  5. # Your migration code goes here
  6. class Migration(migrations.Migration):
  7. dependencies = [
  8. # Dependencies to other migrations
  9. ]
  10. operations = [
  11. migrations.RunPython(forwards),
  12. ]
  1. Django 1.8 中新增。

你也可以提供一个提示作为 **hints参数传递到数据库路由的allow_migrate() 方法:

  1. myapp/dbrouters.py
  2. class MyRouter(object):
  3. def allow_migrate(self, db, app_label, model_name=None, **hints):
  4. if 'target_db' in hints:
  5. return db == hints['target_db']
  6. return True

然后,要在你的迁移中利用,执行以下操作:

  1. from django.db import migrations
  2. def forwards(apps, schema_editor):
  3. # Your migration code goes here
  4. class Migration(migrations.Migration):
  5. dependencies = [
  6. # Dependencies to other migrations
  7. ]
  8. operations = [
  9. migrations.RunPython(forwards, hints={'target_db': 'default'}),
  10. ]

如果你的RunPython或者RunSQL操作只对一个模型有影响,最佳实践是将model_name作为提示传递,使其尽可能对路由可见。这对可复用的和第三方应用极其重要。

添加唯一字段的迁移

如果你应用了一个“朴素”的迁移,向表中一个已存在的行中添加了一个唯一的非空字段,会产生错误,因为位于已存在行中的值只会生成一次。所以需要移除唯一性的约束。

所以,应该执行下面的步骤。在这个例子中,我们会以默认值添加一个非空的UUIDField字段。你可以根据你的需要修改各个字段。

  • 把default=…和unique=True参数添加到你模型的字段中。在这个例子中,我们默认使用uuid.uuid4。
  • 运行 makemigrations 命令。
  • 编辑创建的迁移文件。

生成的迁移类看上去像这样:

  1. class Migration(migrations.Migration):
  2. dependencies = [
  3. ('myapp', '0003_auto_20150129_1705'),
  4. ]
  5. operations = [
  6. migrations.AddField(
  7. model_name='mymodel',
  8. name='uuid',
  9. field=models.UUIDField(max_length=32, unique=True, default=uuid.uuid4),
  10. ),
  11. ]

你需要做三处更改:

  • 从已生成的迁移类中复制,添加第二个AddField操作,并改为AlterField。
  • 在第一个AddField操作中,把unique=True改为 null=True,这会创建一个中间的null字段。
  • 在两个操作之间,添加一个RunPython或RunSQL操作为每个已存在的行生成一个唯一值(例如UUID)。

最终的迁移类应该看起来是这样:

  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.db import migrations, models
  4. import uuid
  5. def gen_uuid(apps, schema_editor):
  6. MyModel = apps.get_model('myapp', 'MyModel')
  7. for row in MyModel.objects.all():
  8. row.uuid = uuid.uuid4()
  9. row.save()
  10. class Migration(migrations.Migration):
  11. dependencies = [
  12. ('myapp', '0003_auto_20150129_1705'),
  13. ]
  14. operations = [
  15. migrations.AddField(
  16. model_name='mymodel',
  17. name='uuid',
  18. field=models.UUIDField(default=uuid.uuid4, null=True),
  19. ),
  20. # omit reverse_code=... if you don't want the migration to be reversible.
  21. migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
  22. migrations.AlterField(
  23. model_name='mymodel',
  24. name='uuid',
  25. field=models.UUIDField(default=uuid.uuid4, unique=True),
  26. ),
  27. ]

现在你可以像平常一样使用migrate命令应用迁移。

注意如果你在这个迁移运行时让对象被创建,就会产生竞争条件(race condition)。在AddField之后, RunPython之前创建的对象会覆写他们原始的uuid。