Version courte
Vous ne devez PAS utiliser loaddata
la commande de gestion directement dans une migration de données.
# Bad example for a data migration
from django.db import migrations
from django.core.management import call_command
def load_fixture(apps, schema_editor):
# No, it's wrong. DON'T DO THIS!
call_command('loaddata', 'your_data.json', app_label='yourapp')
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(load_fixture),
]
Version longue
loaddata
utilise django.core.serializers.python.Deserializer
qui utilise les modèles les plus à jour pour désérialiser les données historiques dans une migration. C'est un comportement incorrect.
Par exemple, supposons qu'il existe une migration de données qui utilise loaddata
la commande de gestion pour charger les données d'un appareil, et qu'elle est déjà appliquée sur votre environnement de développement.
Plus tard, vous décidez d'ajouter un nouveau champ obligatoire au modèle correspondant, donc vous le faites et effectuez une nouvelle migration par rapport à votre modèle mis à jour (et éventuellement fournissez une valeur unique au nouveau champ lorsque ./manage.py makemigrations
vous y êtes invité).
Vous exécutez la prochaine migration et tout va bien.
Enfin, vous avez terminé de développer votre application Django et vous la déployez sur le serveur de production. Il est maintenant temps pour vous d'exécuter toutes les migrations à partir de zéro sur l'environnement de production.
Cependant, la migration des données échoue . En effet, le modèle désérialisé de la loaddata
commande, qui représente le code actuel, ne peut pas être enregistré avec des données vides pour le nouveau champ obligatoire que vous avez ajouté. L'appareil d'origine ne dispose pas des données nécessaires pour cela!
Mais même si vous mettez à jour l'appareil avec les données requises pour le nouveau champ, la migration des données échoue toujours . Lorsque la migration des données est en cours d'exécution, la prochaine migration qui ajoute la colonne correspondante à la base de données n'est pas encore appliquée. Vous ne pouvez pas enregistrer de données dans une colonne qui n'existe pas!
Conclusion: dans une migration de données, laloaddata
commande introduit une éventuelle incohérence entre le modèle et la base de données. Vous ne devez certainement PAS l' utiliser directement dans une migration de données.
La solution
loaddata
La commande s'appuie sur la django.core.serializers.python._get_model
fonction pour obtenir le modèle correspondant à partir d'un appareil, qui renverra la version la plus à jour d'un modèle. Nous devons le patcher de manière singulière pour qu'il obtienne le modèle historique.
(Le code suivant fonctionne pour Django 1.8.x)
# Good example for a data migration
from django.db import migrations
from django.core.serializers import base, python
from django.core.management import call_command
def load_fixture(apps, schema_editor):
# Save the old _get_model() function
old_get_model = python._get_model
# Define new _get_model() function here, which utilizes the apps argument to
# get the historical version of a model. This piece of code is directly stolen
# from django.core.serializers.python._get_model, unchanged. However, here it
# has a different context, specifically, the apps variable.
def _get_model(model_identifier):
try:
return apps.get_model(model_identifier)
except (LookupError, TypeError):
raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)
# Replace the _get_model() function on the module, so loaddata can utilize it.
python._get_model = _get_model
try:
# Call loaddata command
call_command('loaddata', 'your_data.json', app_label='yourapp')
finally:
# Restore old _get_model() function
python._get_model = old_get_model
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(load_fixture),
]