0
点赞
收藏
分享

微信扫一扫

【Django文档转译】第2章:模型层——第3节:迁移(模块3: 编写迁移)


写入数据库迁移¶

本文档解释了如何为您可能遇到的不同场景构造和编写数据库迁移。有关迁移的介绍性材料,请参阅主题指南.

数据迁移和多个数据库¶
在使用多个数据库时,可能需要确定是否对特定数据库运行迁移。例如,您可能想只在特定数据库上运行迁移。

为此,可以在RunPython操作通过查看schema_editor.connection.alias属性:

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),
]

您还可以提供将传递给allow_migrate()数据库路由器的方法**hints:

myapp/dbrouters.py¶

class MyRouter:

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'}),
]

如果你RunPython或RunSQL操作只影响一种模式,通过是很好的做法model_name作为对路由器尽可能透明的提示。这对于可重用和第三方应用尤其重要。

添加唯一字段的迁移¶
向具有现有行的表中添加唯一的非空字段的“普通”迁移将引发错误,因为用于填充现有行的值只生成一次,从而打破了唯一约束。

因此,应采取以下步骤。在本例中,我们将添加一个非空的UUIDField有一个默认值。根据您的需要修改相应的字段。

将字段添加到您的模型上default=uuid.uuid4和unique=True参数(为要添加的字段类型选择适当的默认值)。

运行makemigrations命令。这将生成一个带有AddField行动。

为同一个应用程序生成两个空迁移文件makemigrations myapp --empty两次。在下面的示例中,我们重新命名了迁移文件,以给它们赋予有意义的名称。

复制AddField从自动生成的迁移(三个新文件中的第一个)到最后一个迁移的操作,请更改。AddField到AlterField,并添加uuid和models…例如:

0006_Remove_UUID_null.py¶
# Generated by Django A.B on YYYY-MM-DD HH:MM
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),
),
]

编辑第一个迁移文件。生成的迁移类应该类似于以下内容:

0004_add_UUID_field.py¶
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…例如:

0005_Popate_UUID_value.py¶

# Generated by Django A.B on YYYY-MM-DD HH:MM
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命令。

注意,如果允许在运行迁移时创建对象,则存在争用条件。之后创建的AddField在此之前RunPython会有他们的原版uuid被覆盖了。

非原子迁移¶

在支持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)没有影响。(MySQL的原子DDL语句支持引用单个语句,而不是包装在可以回滚的事务中的多个语句。)

控制迁徙秩序¶
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替换。

为了实现这一点,将所有应该依赖于您的迁移放在run_before属性的Migration班级:

class Migration(migrations.Migration):
...

run_before = [
('third_party_app', '0001_do_awesome'),
]

更喜欢使用dependencies过关run_before如果可能的话。你只应该用run_before如果不希望或不实际地指定dependencies在您想要运行的迁移中,您正在编写的迁移。

在第三方应用程序中迁移数据¶
你可以使用数据迁移把数据从一个第三方应用程序中转移到另一个.

如果你计划要移除旧应用程序,则需要根据是否安装旧应用程序来设置依赖属性.否则,一旦你卸载旧应用程序,就会缺失依赖项.同样,你需要在app.get_model()捕获:主管:查找错误来从旧应用程序中检索模型.这种途径允许你在任何地方部署项目,而无需先安装并且卸载旧应用程序.

这是一个迁移示例:

myapp/migrations/0124_move_old_app_to_new_app.py¶

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的修改操作不会被执行.


举报

相关推荐

0 条评论