# Django 3.2.11.dev 文档

# 编写数据库迁移语句

本文介绍了如何为可能遇到的不同场景组织和编写数据库迁移。关于迁移的介绍性资料，参考 专题指南。

## 数据迁移和多种数据库

使用多种数据库时，你可能需要指定是否为特定数据库运行迁移。例如，你可能 只想 为特定数据库运行迁移。

为此，你可以在 `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),
        ]

你也能提供会以 `**hints` 传递给数据库路由器的 `allow_migrate()` 方法的提示：

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` 两次为同一应用生成两个相同的空迁移文件。我们已在以下例子中将迁移文件重命名成有意义的名字。

- 从自动生成的迁移（3个新文件中的第一个）中将 `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`——这将创建中间 null 字段，并延迟创建唯一性约束，直到我们已为所以行填充了唯一值。

- 在第一个空的迁移文件中，添加一个 `RunPython` 或 `RunSQL` 操作，为每个已存在的行创建一个唯一值（本例中 UUID）。同时添加 `uuid` 的导入。例子：

  0005_populate_uuid_values.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 and PostgreSQL)，迁移默认运行在事务内。对于类似在大数据表上运行数据迁移的场景，你可以通过将 `atomic` 属性置为 `False` 避免在事务中运行迁移:

    from django.db import migrations

    class Migration(migrations.Migration):
        atomic = False

在这样的迁移种，所有的操作运行时都不含事务。通过使用 `atomic()` 或为 `RunPython` 传入 `atomic=True` 能将部分迁移置于事务之中。

这是一个例子，关于非原子性数据迁移操作，将更新大数据表的操作分为数个小批次:

    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 不是通过迁移的名字决定迁移执行顺序，而是通过在 `迁移` 类上使用两个属性： `dependencies` 和 `run_before`。

若你用过 `makemigrations` 命令，你可能早已在运行时见过 `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`。

## 在第三方应用程序中迁移数据

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

如果你计划要移除旧应用程序，则需要根据是否安装旧应用程序来设置 `依赖` 属性。否则，一旦你卸载旧应用程序，就会缺失依赖项。同样，你需要在调用 `app.get_model()` 时捕获 `LookupError`，前者在旧应用程序中检索模型。这种方法允许你在任何地方部署项目，而无需先安装并且卸载旧应用程序。

这是一个迁移示例：

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` 操作的第二个参数。

## 通过使用 `through` 模型来更改 `ManyToManyField` 字段。

如果您将类 `ManyToManyField` 更改为使用 `through` 模型，默认迁移将删除现有表并创建新表，从而丢失现有关系。 为避免这种情况，您可以使用类 `SeparateDatabaseAndState` 将现有表重命名为新表名，同时告诉迁移自动检测器已创建新模型。 您可以通过 `sqlmigrate` 或 `dbshell` 检查现有表名。您可以使用直通模型的 `_meta.db_table` 属性检查新表名称。 您的新 `through` 模型应该使用与 Django 相同的 `ForeignKey`s 名称。 此外，如果它需要任何额外的字段，它们应该在类 `SeparateDatabaseAndState` 之后添加到操作中。

例如，假如你有一个 `Book` 模型，它通过 `ManyToManyField` 链接 `Author` 模型，我们可以通过像下面这样添加一个带有新字段 `is_primary` 的中间模型 `AuthorBook`

    from django.db import migrations, models
    import django.db.models.deletion

    class Migration(migrations.Migration):
        dependencies = [
            ('core', '0001_initial'),
        ]

        operations = [
            migrations.SeparateDatabaseAndState(
                database_operations=[
                    # Old table name from checking with sqlmigrate, new table
                    # name from AuthorBook._meta.db_table.
                    migrations.RunSQL(
                        sql='ALTER TABLE core_book_authors RENAME TO core_authorbook',
                        reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors',
                    ),
                ],
                state_operations=[
                    migrations.CreateModel(
                        name='AuthorBook',
                        fields=[
                            (
                                'id',
                                models.AutoField(
                                    auto_created=True,
                                    primary_key=True,
                                    serialize=False,
                                    verbose_name='ID',
                                ),
                            ),
                            (
                                'author',
                                models.ForeignKey(
                                    on_delete=django.db.models.deletion.DO_NOTHING,
                                    to='core.Author',
                                ),
                            ),
                            (
                                'book',
                                models.ForeignKey(
                                    on_delete=django.db.models.deletion.DO_NOTHING,
                                    to='core.Book',
                                ),
                            ),
                        ],
                    ),
                    migrations.AlterField(
                        model_name='book',
                        name='authors',
                        field=models.ManyToManyField(
                            to='core.Author',
                            through='core.AuthorBook',
                        ),
                    ),
                ],
            ),
            migrations.AddField(
                model_name='authorbook',
                name='is_primary',
                field=models.BooleanField(default=False),
            ),
        ]

## 将非托管模型变为托管的

如果你想要将非托管模型 (`managed=False`) 变为托管的，你必须移除 `managed=False` 并且在对此模型做其他模式相关的改变前生成一次迁移，因为如果迁移中出现模式改变，对 `Meta.managed` 的修改操作不会被执行。