这一节介绍你可能遇到的在不同情况下如何分析和编写数据库迁移. 有关迁移的入门资料,请查看 主题指南.
在使用多个数据库时,需要解决是否针对某个特定数据库运行迁移。 比如说你 只是 想在特定的数据库上做迁移。
为此你可以在schema_editor.connection.alias
中通过查看RunPython
属性来检查数据库连接别名:
from django.db import migrations
def forwards(apps, schema_editor):
if schema_editor.connection.alias != 'default':
return
# Your migration code goes here
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards),
]
你也可以提供一个提示作为 **hints
参数传递到数据库路由的allow_migrate()
方法:
class MyRouter(object):
def allow_migrate(self, db, app_label, model_name=None, **hints):
if 'target_db' in hints:
return db == hints['target_db']
return True
然后,要在你的迁移中利用,执行以下操作:
from django.db import migrations
def forwards(apps, schema_editor):
# Your migration code goes here
...
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(forwards, hints={'target_db': 'default'}),
]
如果你的model_name
或者RunSQL
操作只对一个模型有影响,最佳实践是将RunPython
作为提示传递,使其尽可能对路由可见。 这对可复用的和第三方应用极其重要。
如果你应用了一个“朴素”的迁移,向表中一个已存在的行中添加了一个唯一的非空字段,会产生错误,因为位于已存在行中的值只会生成一次。所以需要移除唯一性的约束。
所以,应该执行下面的步骤。 在这个例子中,我们会以默认值添加一个非空的UUIDField
字段。 你可以根据你的需要修改各个字段。
使用default=uuid.uuid4
和unique=True
参数添加模型中的字段(为添加的字段类型选择适当的默认值)。
运行 makemigrations
命令。 这应该使用AddField
操作生成迁移。
通过运行makemigrations myapp - empty
,为同一个应用程序生成两个空的迁移文件。 我们已经重命名迁移文件,以便在下面的示例中给出有意义的名称。
将AddField
操作从自动生成的迁移(三个新文件中的第一个)复制到最后一次迁移,将AddField
更改为AlterField
,并添加导入uuid
和models
。 像这样:
# -*- coding: utf-8 -*-
# Generated by Django A.B on YYYY-MM-DD HH:MM
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('myapp', '0005_populate_uuid_values'),
]
operations = [
migrations.AlterField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
编辑第一个迁移文件。 生成的迁移类看上去像这样:
class Migration(migrations.Migration):
dependencies = [
('myapp', '0003_auto_20150129_1705'),
]
operations = [
migrations.AddField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]
将unique=True
更改为null=True
- 这将创建中间空字段,并推迟创建唯一约束,直到我们在所有行上填充唯一值。
在第一个空迁移文件中,添加一个RunPython
或RunSQL
操作,以生成每个现有行的唯一值(在示例中为UUID)。 还添加导入uuid
。 像这样:
# -*- coding: utf-8 -*-
# Generated by Django A.B on YYYY-MM-DD HH:MM
from __future__ import unicode_literals
from django.db import migrations
import uuid
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
for row in MyModel.objects.all():
row.uuid = uuid.uuid4()
row.save(update_fields=['uuid'])
class Migration(migrations.Migration):
dependencies = [
('myapp', '0004_add_uuid_field'),
]
operations = [
# omit reverse_code=... if you don't want the migration to be reversible.
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
]
现在,您可以照常使用migrate
命令应用迁移。
注意如果你在这个迁移运行时让对象被创建,就会产生竞争条件(race condition)。 在uuid
之后, RunPython
之前创建的对象会覆写他们原始的AddField
。
在支持DDL事务(SQLite和PostgreSQL)的数据库上,默认情况下迁移将在事务中运行。 对于在大型表上执行数据迁移的用例,您可能希望通过将atomic
属性设置为False
来防止迁移在事务中运行:
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
在这样的迁移中,所有操作都在没有事务的情况下运行。 可以使用atomic()
或通过将atomic=True
传递给RunPython
来执行事务内部迁移的部分。
以下是一个非原子数据迁移的示例,可以更小批量更新大型表:
import uuid
from django.db import migrations, transaction
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
while MyModel.objects.filter(uuid__isnull=True).exists():
with transaction.atomic():
for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
row.uuid = uuid.uuid4()
row.save()
class Migration(migrations.Migration):
atomic = False
operations = [
migrations.RunPython(gen_uuid),
]
atomic
属性对不支持DDL事务的数据库(例如MySQL,Oracle)没有影响。
Django确定迁移的顺序不是每个迁移的文件名,而是通过使用Migration
类中的两个属性构建一个图:dependencies
和run_before
如果您使用makemigrations
命令,您可能已经看到dependencies
,因为自动创建的迁移将其定义为创建过程的一部分。
dependencies
属性声明如下:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('myapp', '0123_the_previous_migration'),
]
通常这将是足够的,但是您可能需要确保您的迁移在其他迁移之前运行。 例如,在您的AUTH_USER_MODEL
替换后,使第三方应用的迁移运行非常有用。
要实现这一点,请将您所需要的所有迁移置于Migration
类的run_before
属性中:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_do_awesome'),
]
如果可能,最好使用dependencies
超过run_before
。 如果在要编写的迁移中要运行的迁移中指定dependencies
是不合需要或不切实际的,那么您应该只使用run_before
。
您可以使用数据迁移将数据从一个第三方应用程序移动到另一个应用程序。
如果以后计划删除旧应用程序,则需要根据是否安装旧应用程序来设置dependencies
属性。 否则,卸载旧应用程序后,您将丢失依赖关系。 同样,您需要在apps.get_model()
调用中捕获来自旧应用程序的模型的LookupError
。 这种方法允许您将项目部署在任何地方,而无需先安装然后卸载旧应用程序。
以下是迁移示例:
from django.apps import apps as global_apps
from django.db import migrations
def forwards(apps, schema_editor):
try:
OldModel = apps.get_model('old_app', 'OldModel')
except LookupError:
# The old app isn't installed.
return
NewModel = apps.get_model('new_app', 'NewModel')
NewModel.objects.bulk_create(
NewModel(new_attribute=old_object.old_attribute)
for old_object in OldModel.objects.all()
)
class Migration(migrations.Migration):
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop),
]
dependencies = [
('myapp', '0123_the_previous_migration'),
('new_app', '0001_initial'),
]
if global_apps.is_installed('old_app'):
dependencies.append(('old_app', '0001_initial'))
还要考虑当迁移未应用时想要发生什么。 您可以不做任何事情(如上面的示例)或从新应用程序中删除部分或全部数据。 相应地调整RunPython
操作的第二个参数。
如果要将非托管模型(managed=False
)更改为托管,则必须删除managed=False
,并在对模型进行其他模式相关更改之前生成迁移,因为迁移中出现的模式更改包含要更改Meta.managed
的操作可能不适用。
2017年9月6日