# Documentation Django 6.0.7

# Écriture de migrations de base de données

Ce document présente la façon de structurer et d’écrire des migrations de base de données pour différents scénarios auxquels vous pourriez être confrontés. Pour du contenu introductif aux migrations, consultez le guide thématique.

## Migrations de données et bases de données multiples

Lorsque vous utilisez plusieurs bases de données, il peut être nécessaire de déterminer si une migration doit être appliquée à une certaine base de données. Par exemple, il est possible qu’une migration ne doive s’appliquer qu’à une seule base de données.

Pour faire cela, il est possible de se baser sur l’alias de base de données de la connexion dans une opération `RunPython` en consultant l’attribut `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),
        ]

Vous pouvez également fournir des indications qui seront transmises à la méthode `allow_migrate()` des routeurs de base de données, dans `**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

Puis, pour s’appuyer sur ce code dans une migration, procédez comme suit :

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

Si votre opération `RunPython` ou `RunSQL` n’affecte qu’un seul modèle, il est de bon aloi de passer `model_name` comme indicateur pour la rendre aussi transparente que possible par rapport au routeur. C’est particulièrement important pour les applications tierces et réutilisables.

## Migrations ajoutant des champs uniques

L’application d’une « simple » migration qui ajoute un champ unique non nul à une table qui contient déjà des lignes va produire une erreur car la valeur utilisée pour remplir les champs existants n’est générée qu’une seule fois, ce qui va à l’encontre de la contrainte d’unicité.

Il est donc nécessaire de suivre les étapes suivantes. Dans cet exemple, nous allons ajouter un champ non nul `UUIDField` avec une valeur par défaut. Modifiez le champ adéquat en fonction de vos propres besoins.

- Ajoutez le champ à votre modèle avec les paramètres `default=uuid.uuid4` et `unique=True` (choisissez une valeur par défaut adéquate au type de champ que vous ajoutez).

- Lancez la commande `makemigrations`. Cela devrait générer une migration contenant une opération `AddField`.

- Générez deux fichiers de migration vides pour la même application en exécutant deux fois `makemigrations monapp --empty`. Nous avons renommé les fichiers de migration dans les exemples ci-dessous pour leur donner des noms plus explicites.

- Copiez l’opération `AddField` à partir de la migration auto-générée (le premier des trois nouveaux fichiers) vers la dernière migration, modifiez `AddField` en `AlterField` et ajoutez des importations pour `uuid` et `models`. Par exemple :

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

- Modifiez le premier fichier de migration. La classe de migration générée devrait ressembler à ceci :

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

  Modifiez `unique=True` en `null=True`, ce qui créera le champ intermédiaire pouvant contenir la valeur `null` et va différer la création de la contrainte d’unicité en attendant que nous ayons terminé de remplir les valeurs uniques pour chaque ligne.

- Dans le premier fichier de mgiration vide, ajoutez une opération `RunPython` ou `RunSQL` pour générer une valeur unique (UUID dans l’exemple) pour chaque ligne existante. Ajoutez aussi une importation pour `uuid`. Par exemple :

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

- Vous pouvez maintenant appliquer les migrations comme d’habitude avec la commande `migrate`.

  Notez qu’il existe un conflit de concurrence si vous permettez de créer des objets lorsque cette migration se déroule. Les objets créés après l’opération `AddField` mais avant `RunPython` verront leur valeur originale `uuid` écrasée.

### Migrations non atomiques

Avec les bases de données qui gèrent les transactions pour les instructions de définition de schéma (SQLite and PostgreSQL), les migrations s’exécuteront par défaut dans une transaction. Pour des cas d’utilisation tels que l’application de migrations de données sur des grosses tables, il est possible d’empêcher qu’une migration s’exécute dans une transaction en définissant l’attribut `atomic` à `False`:

    from django.db import migrations

    class Migration(migrations.Migration):
        atomic = False

Dans une telle migration, toutes les opérations sont lancées sans transaction. Il est possible d’exécuter certaines parties de la migration dans une transaction en utilisant `atomic()` ou en passant `atomic=True` à `RunPython`.

Voici un exemple d’une migration de données non atomique qui met à jour une grosse table en lots plus petits :

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

L’attribut `atomic` n’a pas d’effet pour les bases de données qui ne gèrent pas les transactions DDL (par ex. MySQL, Oracle). (La prise en charge par MySQL des instructions DDL atomiques <https://dev.mysql.com/doc/refman/en/atomic-ddl.html>`_ concerne des instructions individuelles et non pas une série d’instructions à l’intérieur d’une transaction qui pourrait être annulée.)

## Contrôle de l’ordre des migrations

Django détermine l’ordre dans lequel les migrations doivent être appliquées non pas selon le nom de fichier des migrations, mais en construisant un graphe basé sur deux propriétés de la classe `Migration`: `dependencies` et `run_before`.

Si vous avez déjà utilisé la commande `makemigrations`, vous avez probablement vu la présence de `dependencies` car les migrations créées automatiquement définissent cet attribut dans le cadre du processus de création.

La propriété `dependencies` est déclarée comme ceci :

    from django.db import migrations

    class Migration(migrations.Migration):
        dependencies = [
            ("myapp", "0123_the_previous_migration"),
        ]

Cela suffit en général, mais de temps à autre, il peut être nécessaire de s’assurer que la migration s’exécute avant une autre. C’est par exemple utile pour que des migrations d’applications tierces s’exécutent après celles du modèle de remplacement `AUTH_USER_MODEL`.

Pour faire cela, placez toutes les migrations qui doivent dépendre des vôtres dans l’attribut `run_before` de votre classe `Migration`:

    class Migration(migrations.Migration):
        ...

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

Privilégiez autant que possible l’usage de `dependencies` par rapport à `run_before`. `run_before` ne devrait être utilisé que dans le cas où il n’est pas souhaitable ou pas possible de définir `dependencies` dans la migration que vous voulez faire exécuter après celle que vous êtes en train d’écrire.

## Migration de données entre applications tierces

Vous pouvez utiliser une migration de données pour déplacer des données d’une application tierce à une autre.

Si vous prévoyez d’enlever l’ancienne application plus tard, il vous faudra définir la propriété `dependencies` en fonction de la présence de l’ancienne application dans les applications installées. Sinon, des dépendances vont manquer au moment de désinstaller l’ancienne application. De même, il faudra intercepter `LookupError` dans l’appel à `apps.get_model()` qui récupère les modèles de l’ancienne application. Cette approche vous permet de déployer votre projet partout sans devoir d’abord installer puis désinstaller l’ancienne application.

Voici un exemple de migration :

`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"))

Considérez également ce qui va se passer lorsque la migration sera désappliquée. Vous pouvez soit ne rien faire (comme dans l’exemple ci-dessus), soit enlever certaines ou toutes les données de la nouvelle application. Ajustez le second paramètre de l’opération `RunPython` en fonction de votre choix.

## Modification d’un champ `ManyToManyField` pour utiliser un modèle intermédiaire

Si vous modifiez un champ `ManyToManyField` pour utiliser un modèle `through` intermédiaire, la migration par défaut va supprimer la table existante et en créer une nouvelle, ce qui effacera les relations existantes. Pour éviter cela, vous pouvez utiliser `SeparateDatabaseAndState` pour renommer la table existante avec le nouveau nom tout en indiquant à l’autodétecteur de migrations que le nouveau modèle a été créé. Le nom actuel de la table et celui de la contrainte sont visibles avec les commandes `sqlmigrate` ou `dbshell`. Le nouveau nom de table est celui de la propriété `_meta.db_table` du modèle intermédiaire. Votre nouveau modèle intermédiaire doit utiliser les mêmes noms de clés étrangères que Django. De plus, si vous ajoutez des champs supplémentaires, ils doivent être ajoutés dans des opérations de migration après `SeparateDatabaseAndState`.

Par exemple, si nous avions un modèle `Book` avec un champ `ManyToManyField` le liant avec `Author`, on pourrait ajouter un modèle intermédiaire `AuthorBook` avec un nouveau champ `is_primary` comme ceci

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

## Modification d’un modèle non géré en géré

Si vous souhaitez modifier un modèle non géré (`managed=False`) en modèle géré, vous devez enlever `managed=False` et générer une migration avant de faire d’autres modifications de schéma du modèle, car les modifications de schéma figurant dans la même migration que celle contenant l’opération de modification de `Meta.managed` pourraient ne pas être appliquées.