# Django 5.2.8 documentation

# 如何创建数据库迁移

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

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

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

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

You can also provide hints that will be passed to the `allow_migrate()` method of database routers as `**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` 两次为同一应用生成两个相同的空迁移文件。我们已在以下例子中将迁移文件重命名成有意义的名字。

- 从自动生成的迁移（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

Within such a migration, all operations are run without a transaction. It's possible to execute parts of the migration inside a transaction using `atomic()` or by passing `atomic=True` to `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 不是通过迁移的名字决定迁移执行顺序，而是通过在 `迁移` 类上使用两个属性： `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` 字段。

If you change a `ManyToManyField` to use a `through` model, the default migration will delete the existing table and create a new one, losing the existing relations. To avoid this, you can use `SeparateDatabaseAndState` to rename the existing table to the new table name while telling the migration autodetector that the new model has been created. You can check the existing table name and constraint name through `sqlmigrate` or `dbshell`. You can check the new table name with the through model's `_meta.db_table` property. Your new `through` model should use the same names for the `ForeignKey`s as Django did. Also if it needs any extra fields, they should be added in operations after `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",
                                ),
                            ),
                        ],
                        options={
                            "constraints": [
                                models.UniqueConstraint(
                                    fields=["author", "book"],
                                    name="unique_author_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` 的修改操作不会被执行。