Opérations de migration¶
Les fichiers de migration sont composés de un ou plusieurs objets Operation
qui enregistrent de manière déclarative les opérations à appliquer à la base de données.
Django utilise également ces objets Operation
pour recomposer un état historisé de vos modèles et pour calculer les modifications effectuées aux modèles depuis la dernière migration afin de pouvoir écrire automatiquement les migrations ; c’est pourquoi ils sont déclaratifs, ce qui permet à Django de facilement tous les charger en mémoire et de les parcourir sans devoir accéder à la base de données pour trouver à quoi le projet devrait ressembler.
Certains objets Operation
plus spécialisés sont destinés à des opérations comme les migrations de données et pour des manipulations manuelles avancées de la base de données. Vous pouvez même écrire vos propres classes Operation
si vous le voulez pour englober des modifications que vous effectuez fréquemment.
Si vous avez besoin d’un fichier de migration vide pour y écrire vos propres objets Operation
, exécutez simplement python manage.py makemigrations --empty nom_application
, mais soyez conscient que l’ajout manuel d’opérations modifiant le schéma peut perturber l’autodétecteur de migrations et provoquer du code incorrect durant les prochaines exécutions de makemigrations
.
Toutes les opérations Django de base se trouvent dans le module django.db.migrations.operations
.
Pour du contenu d’initiation, consultez le guide thématique des migrations.
Opérations liées au schéma¶
CreateModel
¶
Crée un nouveau modèle dans l’historique du projet et une table correspondante dans la base de données.
name
est le nom du modèle tel qu’il est écrit dans le fichier models.py
.
fields
est une liste de tuples binaires (nom_champ, instance_champ)
. L’instance de champ doit être un champ non lié (« unbound ») (donc simplement models.CharField(…)
, plutôt qu’un champ repris d’un autre modèle).
options
est un dictionnaire facultatif de valeurs tirées de la classe Meta
du modèle.
bases
est une liste facultative d’autres classes dont ce modèle doit hériter ; elle peut contenir aussi bien des objets classes que des chaînes au format "nomapp.NomModele"
si vous voulez dépendre d’un autre modèle (vous héritez donc de sa version historique). En cas d’absence, la valeur par défaut ne fait qu’hériter du modèle standard models.Model
.
managers
accepte une liste de tuples binaires (nom_gestionnaire, instance_gestionnaire)
. Le premier gestionnaire de la liste sera le gestionnaire par daéfut de ce modèle pour les migrations.
DeleteModel
¶
Supprime le modèle de l’historique du projet et ses tables de la base de données.
RenameModel
¶
Renomme le modèle d’un ancien nom vers un nouveau nom.
Il peut arriver que vous deviez ajoutez manuellement cette opération si vous modifiez à la fois le nom du modèle et plusieurs de ses champs ; pour l’autodétecteur, il paraîtra que l’ancien modèle a été supprimé et qu’un nouveau modèle a été ajouté avec un nom différent, ce qui fera que la migration créée perdra toutes les données de l’ancienne table.
AlterModelTable
¶
Modifie le nom de la table du modèle (l’option db_table
de la classe interne Meta
).
AlterUniqueTogether
¶
Modifie l’ensemble de contraintes d’unicité du modèle (l’option unique_together
de la classe interne Meta
).
AlterIndexTogether
¶
Modifie l’ensemble d’index personnalisés du modèle (l’option index_together
de la classe interne Meta
).
AlterOrderWithRespectTo
¶
Crée ou supprime la colonne _order
nécessaire à l’option order_with_respect_to
de la classe interne Meta
.
AlterModelOptions
¶
Stocke les modifications de diverses options de modèles (attributs de la classe Meta
d’un modèle) comme permissions
ou verbose_name
. N’affecte pas la base de données, mais fait persister ces modifications à l’usage des instances RunPython
. options
doit être un dictionnaire faisant correspondre des noms d’options à leurs valeurs.
AlterModelManagers
¶
Modifie les gestionnaires disponibles durant les migrations.
AddField
¶
Ajoute un champ à un modèle. model_name
est le nom du modèle, name
est le nom du champ et field
est une instance de champ Field
non liée (ce que vous placeriez dans une déclaration de champ dans models.py
, par exemple models.IntegerField(null=True)
).
Le paramètre preserve_default
indique si la valeur par défaut du champ est permanente et doit être incluse dans l’état du projet (True
) ou si elle n’est que temporaire et juste pour la migration (False
), généralement parce que la migration ajoute un champ non nul à une table et qu’elle a besoin d’une valeur par défaut pour remplir les lignes existantes. Cela ne concerne pas le comportement de définition de valeurs par défaut au sein même de la base de données, car Django ne définit jamais de valeurs par défaut pour la base de données et les applique toujours au niveau du code Django de l’ORM.
RemoveField
¶
Supprime un champ d’un modèle.
Gardez en tête que lors de l’inversion de l’opération, un nouveau champ est ajouté au modèle. L’opération est réversible (sans prendre en compte la perte de données qui est évidemment irréversible) à la condition que le champ puisse valoir null
ou qu’il possède une valeur par défaut utilisable pour remplir la colonne recréée. Dans le cas contraire, l’opération est irréversible.
AlterField
¶
Modifie la définition d’un champ, y compris les modifications de son type, des attributs null
, unique
, db_column
ou des autres attributs de champ.
Le paramètre preserve_default
indique si la valeur par défaut du champ est permanente et doit être incluse dans l’état du projet (True
) ou si elle n’est que temporaire et juste pour la migration (False
), généralement parce que la migration modifie un champ nul vers un champ non nul à une table et qu’elle a besoin d’une valeur par défaut pour remplir les lignes existantes. Cela ne concerne pas le comportement de définition de valeurs par défaut au sein même de la base de données, car Django ne définit jamais de valeurs par défaut pour la base de données et les applique toujours au niveau du code Django de l’ORM.
Notez qu’il n’est pas toujours possible d’effectuer toutes les modifications, aussi en fonction de la base de données. Par exemple, il n’est pas possible de transformer un champ de type texte comme models.TextField()
en un champ de type numérique comme models.IntegerField()
dans la plupart des bases de données.
Opérations spéciales¶
RunSQL
¶
Permet d’exécuter du code SQL arbitraire dans la base de données, pratique pour des fonctionnalités plus avancées des moteurs de base de données que Django ne prend pas directement en charge, comme par exemple les index partiels.
sql
, et reverse_sql
s’il est fourni, doivent être des chaîne de SQL à exécuter dans la base de données. Pour la plupart des moteurs de base de données (tous sauf PostgreSQL), Django sépare le code SQL en instructions individuelles avant de les exécuter. Cela exige la présence de la bibliothèque Python sqlparse.
Vous pouvez aussi transmettre une liste de chaînes ou de tuples binaires. Cette dernière option est utilisée pour transmettre des requêtes et des paramètres sur le même principe que pour cursor.execute(). Ces trois opérations sont équivalentes :
migrations.RunSQL("INSERT INTO musician (name) VALUES ('Reinhardt');")
migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)])
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])])
Si vous voulez inclure des signes « pour cent » littéraux dans la requête, vous devez les doubler si vous transmettez des paramètres.
Les requêtes reverse_sql
sont exécutées lorsque la migration est défaite, afin que vous puissiez inverser les changements effectués dans les requêtes de migration avant :
migrations.RunSQL(
[("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])],
[("DELETE FROM musician where name=%s;", ['Reinhardt'])],
)
Le paramètre state_operations
sert à fournir des opérations qui sont équivalentes au code SQL en terme d’état du projet ; par exemple, si vous créez une colonne manuellement, vous devriez passer une liste contenant une opération AddField
afin que l’autodétecteur puisse maintenir un état à jour du modèle (sinon, au prochain lancement de makemigrations
, il ne verra aucune opération ajoutant ce champ et va donc essayer de le recréer une nouvelle fois). Par exemple :
migrations.RunSQL(
"ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",
state_operations=[
migrations.AddField(
'musician',
'name',
models.CharField(max_length=255),
),
],
)
Le paramètre facultatif hints
sera transmis comme **hints
à la méthode allow_migrate()
des routeurs de base de données afin de les aider à prendre les décisions de routage. Voir Indications (hints) pour plus de détails sur les indications de base de données.
Le paramètre facultatif elidable
détermine si l’opération sera supprimée (éludée) lors de la fusion des migrations.
-
RunSQL.
noop
¶ Transmettez l’attribut
RunSQL.noop
àsql
oureverse_sql
lorsque vous voulez que l’opération ne fasse rien dans une direction donnée. C’est particulièrement utile pour rendre une opération réversible.
RunPython
¶
Exécute du code Python personnalisé dans un contexte historisé. code
(et reverse_code
s’il est renseigné) doivent être des objets exécutables acceptant deux paramètres ; le premier est une instance de django.apps.registry.Apps
contenant les modèles historisés qui correspondent à la position de l’opération dans l’historique du projet et la seconde une instance de SchemaEditor
.
Le paramètre reverse_code
est appelé lors de l’inversion des migrations. Cet objet exécutable doit défaire ce qui a été fait par l’objet exécutable code
afin que cette migration soit réversible.
Le paramètre facultatif hints
sera transmis comme **hints
à la méthode allow_migrate()
des routeurs de base de données afin de les aider à prendre une décision de routage. Voir Indications (hints) pour plus de détails sur les indications de base de données.
Le paramètre facultatif elidable
détermine si l’opération sera supprimée (éludée) lors de la fusion des migrations.
Il est conseillé d’écrire le code sous forme de fonction distincte au-dessus de la classe Migration
dans le fichier de migration et de simplement transmettre celle-ci à RunPython
. Voici un exemple d’emploi de RunPython
pour créer quelques objets initiaux d’un modèle Country
:
from django.db import migrations
def forwards_func(apps, schema_editor):
# We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version
Country = apps.get_model("myapp", "Country")
db_alias = schema_editor.connection.alias
Country.objects.using(db_alias).bulk_create([
Country(name="USA", code="us"),
Country(name="France", code="fr"),
])
def reverse_func(apps, schema_editor):
# forwards_func() creates two Country instances,
# so reverse_func() should delete them.
Country = apps.get_model("myapp", "Country")
db_alias = schema_editor.connection.alias
Country.objects.using(db_alias).filter(name="USA", code="us").delete()
Country.objects.using(db_alias).filter(name="France", code="fr").delete()
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.RunPython(forwards_func, reverse_func),
]
Il s’agit généralement de l’opération que l’on utilise pour créer des migrations de données, pour lancer des mises à jour et modifications de données personnalisées ou pour toute autre opération qui nécessite le recours à l’ORM ou à du code Python.
Si vous mettez à jour à partir de South, c’est essentiellement le modèle South sous forme d’opération, une ou deux méthodes pour migrer en avant et en arrière avec un ORM et des opérations de schéma à disposition. La plupart du temps, vous devriez pouvoir traduire les références orm.Model
ou orm["nomapp", "Model"]
de South directement en références apps.get_model("nomapp", "Model")
tout en laissant la plupart du reste du code tel quel pour les migrations de données. Cependant, apps
ne contient des références aux modèles que pour l’application en cours, sauf si des migrations d’autres applications sont ajoutées aux dépendances de cette migration.
Tout comme pour RunSQL
, si vous faites ici des modifications au schéma, prenez soin de le faire en dehors de la portée du systèmes des modèles Django (par ex. avec des déclencheurs) ou utilisez SeparateDatabaseAndState
pour insérer des opérations qui reflètent vos modifications sur l’état des modèles. Sinon, l’ORM versionné et l’autodétecteur risquent de ne plus fonctionner correctement.
Par défaut, RunPython
va exécuter son contenu à l’intérieur d’une transaction pour les bases de données qui ne prennent pas en charge les transactions DDL (instructions de modification du schéma), par exemple MySQL ou Oracle. Cela se passe en principe bien, mais pourrait provoquer un plantage si vous essayez d’utiliser l’objet schema_editor
fournit par ces moteurs ; dans ce cas, passez atomic=False
à l’opération RunPython
.
Pour les bases de données qui prennent en charge les transactions de schéma DDL (SQLite et PostgreSQL), aucune transaction n’est automatiquement rajoutée pour les opérations RunPython
en dehors des transactions créées pour chaque migration. Ainsi, par exemple avec PostgreSQL, vous devriez éviter de combiner des modifications de schéma avec des opérations RunPython
dans la même migration, au risque de recevoir des erreurs du genre OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events
.
Si votre base de données est différente et que vous n’êtes pas sûr si elle prend en charge les transactions DDL, consultez l’attribut django.db.connection.features.can_rollback_ddl
.
Si l’opération RunPython
fait partie d’une migration non atomique, l’opération ne sera exécutée dans une transaction que si atomic=True
est transmis à l’opération RunPython
.
Avertissement
RunPython
ne modifie pas de façon magique la connexion des modèles à votre place ; toute méthode de modèle que vous appelez sera dirigée vers la base de données par défaut, sauf si vous leur indiquez l’alias de base de données à utiliser (disponible dans schema_editor.connection.alias
, où schema_editor
est le second paramètre reçu par votre fonction).
SeparateDatabaseAndState
¶
Une opération hautement spécialisée qui permet de mélanger des opérations touchant aux aspects de modification de schéma de base de données avec des opérations de changement d’état (en lien avec l’autodétecteur).
Elle accepte deux listes d’opérations ; quand on lui demande d’appliquer les changements d’état, elle applique la liste d’état, et quand on lui demande d’appliquer les modifications de base de données, elle applique la liste de base de données. N’utilisez pas cette opération si vous n’êtes pas certain de savoir ce que vous faites.
Écriture d’opérations personnalisées¶
Les opérations possèdent une API relativement simple et sont conçues pour pouvoir facilement écrire des sous-classes pour compléter celles que Django fournit. La structure de base d’une Operation
ressemble à ceci :
from django.db.migrations.operations.base import Operation
class MyCustomOperation(Operation):
# If this is False, it means that this operation will be ignored by
# sqlmigrate; if true, it will be run and the SQL collected for its output.
reduces_to_sql = False
# If this is False, Django will refuse to reverse past this operation.
reversible = False
def __init__(self, arg1, arg2):
# Operations are usually instantiated with arguments in migration
# files. Store the values of them on self for later use.
pass
def state_forwards(self, app_label, state):
# The Operation should take the 'state' parameter (an instance of
# django.db.migrations.state.ProjectState) and mutate it to match
# any schema changes that have occurred.
pass
def database_forwards(self, app_label, schema_editor, from_state, to_state):
# The Operation should use schema_editor to apply any changes it
# wants to make to the database.
pass
def database_backwards(self, app_label, schema_editor, from_state, to_state):
# If reversible is True, this is called when the operation is reversed.
pass
def describe(self):
# This is used to describe what the operation does in console output.
return "Custom Operation"
Vous pouvez prendre ce modèle et l’étendre, même si nous suggérons d’examiner les opérations fournies par Django dans django.db.migrations.operations
. Elles sont simples à lire et couvrent une grande partie des exemples d’utilisation des aspects semi-internes du système des migrations, tels que ProjectState
ainsi que les mécanismes utilisés pour obtenir les modèles historisés, tout comme ModelState
et les motifs utilisés pour faire évoluer les modèles historisés dans state_forwards()
.
Quelques éléments à signaler :
Vous n’avez pas besoin d’en apprendre beaucoup sur
ProjectState
pour écrire de simples migrations ; il suffit de savoir qu’il possède une propriétéapps
qui donne accès à un registre d’applications (sur lequel vous pouvez appelerget_model
).database_forwards
etdatabase_backwards
reçoivent tous deux deux états ; ils représentent simplement la différence par rapport à ce que la méthodestate_forwards
aurait appliqué, mais vous sont transmis par commodité et pour des raisons de vitesse.Si vous souhaitez manipuler des classes ou instances de modèles à partir du paramètre
from_state
dansdatabase_forwards()
oudatabase_backwards()
, il faut produire les états de modèles en utilisant la méthodeclear_delayed_apps_cache()
afin de rendre disponible les modèles liés :def database_forwards(self, app_label, schema_editor, from_state, to_state): # This operation should have access to all models. Ensure that all models are # reloaded in case any are delayed. from_state.clear_delayed_apps_cache() ...
to_state
dans la méthodedatabase_backwards
est l’état plus ancien ; c’est-à-dire celui qui représentera l’état actuel au moment où la migration aura terminé son travail d’annulation.Vous pouvez trouver des implémentations de
references_model
dans les opérations fournies par Django ; cela fait partie du code d’autodétection et ne joue pas de rôle pour les opérations personnalisées.
Avertissement
Pour des raisons de performance, les instances Field
dans ModelState.fields
sont réutilisées d’une migration à l’autre. Vous ne devez jamais toucher aux attributs de ces instances. Si vous avez besoin de faire évoluer un champ dans state_forwards()
, vous devez enlever l’ancienne instance de ModelState.fields
et ajouter une nouvelle instance à sa place. Le même principe vaut pour les instances Manager
dans ModelState.managers
.
Comme exemple simple, créons une opération qui charge des extensions PostgreSQL (qui contiennent certaines des fonctionnalités les plus enthousiasmantes de PostgreSQL). C’est relativement simple ; il n’y a pas de changement d’état de modèle, et tout ce qu’elle fait, c’est d’exécuter une commande :
from django.db.migrations.operations.base import Operation
class LoadExtension(Operation):
reversible = True
def __init__(self, name):
self.name = name
def state_forwards(self, app_label, state):
pass
def database_forwards(self, app_label, schema_editor, from_state, to_state):
schema_editor.execute("CREATE EXTENSION IF NOT EXISTS %s" % self.name)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
schema_editor.execute("DROP EXTENSION %s" % self.name)
def describe(self):
return "Creates extension %s" % self.name