# Django 5.0.9.dev20240806133106 ドキュメント

# データベースのマイグレーションの作成方法

このドキュメントでは、遭遇する可能性のあるいくつかのシナリオに対する、データベースのマイグレーションの構成方法と書き方について説明します。マイグレーションに関する入門的な資料を探しているなら、トピック別ガイド を読んでください。

## データのマイグレーションと複数のデータベース

複数のデータベースを使用している場合、特定のデータベースに対してマイグレーションを実行するかどうかを判断する必要があるかもしれません。例えば、特定のデータベースに対してのみマイグレーションを実行したい場合です。

これを行うには、 `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` 操作が 1 つのモデルにしか影響しないのであれば、 `model_name` をヒントとして渡すことで、ルーターに対して最大限、透過的になります。これは再利用可能なサードパーティアプリにとって特に重要です。

## ユニークなフィールドを追加するマイグレーション

既存の行を持つテーブルにユニークな非 Null フィールドを追加する「プレーン」なマイグレーションを適用するとエラーが発生します。これは、既存の行すべてを埋めるために使用される値がただ一つだけ生成され、これがユニーク制約に違反するためです。

そのため、次の手順を踏む必要があります。この例では、デフォルト値を持つ非 Null の `UUIDField` を追加します。必要に応じて、該当するフィールドを変更してください。

- モデルに `default=uuid.uuid4` と `unique=True` 引数を持つフィールドを追加します（追加するフィールドのタイプに適切なデフォルトを選択してください）。

- `makemigrations` コマンドを実行します。これにより、 `AddField` 操作を含むマイグレーションが生成されます。

- 同じアプリに対して `makemigrations myapp --empty` を2回実行して、2つの空のマイグレーションファイルを生成します。以下の例では、マイグレーションファイルにわかりやすい名前を付けるためにファイル名を変更しています。

- 自動生成されたマイグレーション(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 と 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の atomic DDL statement support は、ロールバック可能なトランザクションに包まれた複数のステートメントではなく、個々のステートメントを指します)。

## マイグレーションの順序をコントロールする

Django はマイグレーションを適用する順番を、マイグレーションのファイル名ではなく、 `Migration` クラスの 2 つのプロパティである `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"),
        ]

可能な限り、 `run_before` よりも `dependencies` を使うことを推奨します。 `run_before` を使うのは、書いているマイグレーションの後に実行したいマイグレーションで `dependencies` を指定するのが望ましくないか、現実的でない場合のみです。

## サードパーティのアプリ間でデータをマイグレーションする

データマイグレーションを使って、あるサードパーティアプリケーションから別のアプリケーションにデータを移動できます。

後で古いアプリを削除する予定がある場合は、古いアプリがインストールされているかどうかに基づいて `dependencies` プロパティを設定する必要があります。そうしないと、古いアプリをアンインストールしたときに依存関係がなくなってしまいます。同様に、古いアプリからモデルを取得する `apps.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` 操作の2番目の引数を調整します。

## `ManyToManyField` を中間 (`through`) モデルを使うように変更する

`ManyToManyField` を `through` モデルに変更すると、デフォルトのマイグレーションは既存のテーブルを削除して新しいテーブルを作成し、既存のリレーションを失います。これを避けるには、`SeparateDatabaseAndState` を使用して、マイグレーションの自動検出器に新しいモデルが作成されたことを伝えながら、既存のテーブル名を新しいテーブル名に変更します。既存のテーブル名は `sqlmigrate` または `dbshell` で確認できます。新しいテーブル名は中間モデルの `_meta.db_table` プロパティで確認できます。新しい `through` モデルは `ForeignKey` に Django と同じ名前を使うべきです。また、追加のフィールドが必要な場合は、 `SeparateDatabaseAndState` の後の操作で追加してください。

例えば、 `Author` にリンクする `ManyToManyField` を持つ `Book` モデルがあった場合、次のように新しいフィールド `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` を変更する操作を含むマイグレーションに含まれるスキーマ変更が適用されない可能性があるためです。