Chargement des données initiales avec Django 1.7 et migration des données
je suis récemment passé de Django 1.6 à 1.7, et j'ai commencé à utiliser les migrations (Je n'ai jamais utilisé le Sud).
avant 1.7, je chargeais les données initiales avec un fichier fixture/initial_data.json
, qui était chargé avec la commande python manage.py syncdb
(lors de la création de la base de données).
maintenant, j'ai commencé à utiliser les migrations, et ce comportement est déprécié:
si une application utilise des migrations, il n'y a pas de chargement automatique des fixtures. Puisque les migrations seront nécessaires pour les applications dans Django 2.0, ce comportement est considéré comme déprécié. Si vous voulez charger les données initiales pour une application, pensez à le faire dans une migration de données. ( https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures )
la documentation officielle n'a pas d'exemple clair sur la façon de le faire, donc ma question Est :
Quelle est la meilleure façon d'importer de telles données initiales en utilisant les migrations de données:
- Ecrire du code Python avec plusieurs appels à
mymodel.create(...)
, - utiliser ou écrire une fonction Django ( comme appeler
loaddata
) pour charger des données à partir d'un fichier fixe JSON.
je préfère la deuxième option.
Je ne veux pas utiliser le Sud, comme Django semble être en mesure de le faire nativement maintenant.
7 réponses
Update : voir le commentaire de @GwynBleidD ci-dessous pour les problèmes que cette solution peut causer, et voir la réponse de @Rockallite ci-dessous pour une approche plus durable pour les changements de modèles futurs.
en supposant que vous avez un fichier d'installation dans <yourapp>/fixtures/initial_data.json
-
créez votre migration vide:
In Django 1.7:
python manage.py makemigrations --empty <yourapp>
dans Django 1.8+, Vous pouvez fournir un nom:
python manage.py makemigrations --empty <yourapp> --name load_intial_data
-
modifier votre fichier de migration
<yourapp>/migrations/0002_auto_xxx.py
2.1. Mise en œuvre sur mesure, inspirée de Django '
loaddata
(réponse initiale):import os from sys import path from django.core import serializers fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) fixture_filename = 'initial_data.json' def load_fixture(apps, schema_editor): fixture_file = os.path.join(fixture_dir, fixture_filename) fixture = open(fixture_file, 'rb') objects = serializers.deserialize('json', fixture, ignorenonexistent=True) for obj in objects: obj.save() fixture.close() def unload_fixture(apps, schema_editor): "Brutally deleting all entries for this model..." MyModel = apps.get_model("yourapp", "ModelName") MyModel.objects.all().delete() class Migration(migrations.Migration): dependencies = [ ('yourapp', '0001_initial'), ] operations = [ migrations.RunPython(load_fixture, reverse_code=unload_fixture), ]
2.2. Une solution plus simple pour
load_fixture
(suggestion de @juliocesar):from django.core.management import call_command fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) fixture_filename = 'initial_data.json' def load_fixture(apps, schema_editor): fixture_file = os.path.join(fixture_dir, fixture_filename) call_command('loaddata', fixture_file)
utile si vous voulez utiliser un répertoire personnalisé.
2.3. le plus simple: appelant
loaddata
avecapp_label
chargera les fixtures du<yourapp>
'sfixtures
dir automatiquement:from django.core.management import call_command fixture = 'initial_data' def load_fixture(apps, schema_editor): call_command('loaddata', fixture, app_label='yourapp')
si vous ne spécifiez pas
app_label
, loaddata va essayer de chargerfixture
nom de fichier à partir de tous applications fixtures répertoires (que vous ne voulez probablement pas). -
Exécuter
python manage.py migrate <yourapp>
version courte
Vous devriez PAS utiliser loaddata
gestion de la commande directement dans la migration des 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 récents pour désérialiser des données historiques dans une migration. C'est un comportement incorrect.
par exemple, suppose qu'il y a une migration de données qui utilise la commande de gestion loaddata
pour charger des données à partir d'un appareil, et elle est déjà appliquée sur votre environnement de développement.
plus tard, vous décidez d'ajouter un nouveau champ requis au modèle correspondant, donc vous le faites et effectuez une nouvelle migration par rapport à votre modèle mis à jour (et éventuellement fournir une valeur unique au nouveau champ lorsque ./manage.py makemigrations
vous invite).
vous dirigez la prochaine migration, et tout va bien.
enfin, vous avez fini de développer votre application Django, et vous l'avez déployée sur le serveur de production. Maintenant, il est temps pour vous d'effectuer toutes les migrations à partir de zéro sur l'environnement de production.
Toutefois, la migration des données échoue . C'est parce que le modèle désérialisé de la commande loaddata
, qui représente le code actuel, ne peut pas être sauvegardé avec des données vides pour le nouveau requis le champ que vous avez ajouté. Le montage d'origine manque de données nécessaires pour cela!
mais même si vous mettez à jour le fixture avec les données requises pour le nouveau champ, la migration de données échoue toujours . Lorsque la migration des données est en cours, la migration suivante qui ajoute la colonne correspondante à la base de données, n'est pas encore appliquée. Vous ne pouvez pas enregistrer les données dans une colonne qui n'existe pas!
Conclusion: dans une migration de données, la commande loaddata
introduit une incohérence potentielle entre le modèle et la base de données. Vous devez certainement pas l'utiliser directement dans une migration de données.
La Solution
loaddata
commande s'appuie sur django.core.serializers.python._get_model
fonction pour obtenir le modèle correspondant à partir d'un fixe, qui retournera la version la plus à jour d'un modèle. Nous avons besoin de rapiétissez-le 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),
]
inspiré par certains des commentaires (à savoir n__o) et le fait que j'ai beaucoup de fichiers initial_data.*
répartis sur plusieurs applications, j'ai décidé de créer une application Django qui faciliterait la création de ces migrations de données.
L'utilisation de django-migration-fixture vous pouvez simplement exécuter la commande de gestion suivante et il va chercher dans tous vos INSTALLED_APPS
pour initial_data.*
les fichiers et les transformer en migrations de données.
./manage.py create_initial_data_fixtures
Migrations for 'eggs':
0002_auto_20150107_0817.py:
Migrations for 'sausage':
Ignoring 'initial_data.yaml' - migration already exists.
Migrations for 'foo':
Ignoring 'initial_data.yaml' - not migrated.
voir django-migration-fixture pour les instructions d'installation/d'utilisation.
la meilleure façon de charger les données initiales dans les applications migrées est par migration de données (comme également recommandé dans les docs). L'avantage est que le montage est ainsi chargé aussi bien pendant les essais que pendant la production.
@n _ _ O suggère de réimplémenter la commande loaddata
dans la migration. Dans mes tests, cependant, appeler loaddata
commande directement fonctionne aussi bien. Tout le processus est ainsi:
-
fichier dans
<yourapp>/fixtures/initial_data.json
-
créez votre migration vide:
python manage.py makemigrations --empty <yourapp>
-
modifier votre fichier de migration /migrations/0002_auto_xxx.py
from django.db import migrations from django.core.management import call_command def loadfixture(apps, schema_editor): call_command('loaddata', 'initial_data.json') class Migration(migrations.Migration): dependencies = [ ('<yourapp>', '0001_initial'), ] operations = [ migrations.RunPython(loadfixture), ]
afin de donner à votre base de données des données initiales, écrivez une migration de données . Dans la migration de données, utilisez la fonction RunPython pour charger vos données.
N'écrivez aucune commande loaddata car cette façon est dépréciée.
vos migrations de données ne seront effectuées qu'une seule fois. Les migrations sont une séquence ordonnée des migrations. Lorsque le 003_xxxx.py les migrations sont gérées, écrit django migrations dans le base de données que cette application est migrée jusqu'à celle-ci (003), et exécutera les migrations suivantes seulement.
les solutions présentées ci-dessus n'ont malheureusement pas fonctionné pour moi. J'ai découvert que chaque fois que je change de modèle, je dois mettre à jour mes appareils. Idéalement, j'écrirais plutôt des migrations de données pour modifier les données créées et les données chargées de façon similaire.
pour faciliter ce j'ai écrit une fonction rapide qui regardera dans le répertoire fixtures
de l'application courante et chargera un appareil. Mettre cette fonction dans une migration dans le point du modèle l'histoire qui correspond aux champs de la migration.
à mon avis, les appareils sont un peu mauvais. Si votre base de données change fréquemment, les garder à jour sera venu un cauchemar bientôt. En fait, ce n'est pas seulement mon avis, dans le livre "deux Scoops de Django" il est expliqué beaucoup mieux.
à la place, je vais écrire un fichier Python pour fournir la configuration initiale. Si vous avez besoin de plus, je vous suggère de regarder Factory boy .
Si vous avez besoin de migrer des données, vous devez utiliser les migrations de données .
il y a aussi " brûlez vos appareils, Utilisez des usines de Modèles " à propos de l'utilisation des appareils.