Versione breve
Si dovrebbe NON utilizzare loaddata
il comando gestione direttamente in una migrazione di dati.
# 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),
]
Versione lunga
loaddata
utilizza django.core.serializers.python.Deserializer
quale utilizza i modelli più aggiornati per deserializzare i dati storici in una migrazione. Questo è un comportamento scorretto.
Ad esempio, si suppone che esista una migrazione dei dati che utilizza il loaddata
comando di gestione per caricare i dati da un dispositivo ed è già applicato al proprio ambiente di sviluppo.
Successivamente, decidi di aggiungere un nuovo campo obbligatorio al modello corrispondente, quindi lo fai ed esegui una nuova migrazione rispetto al tuo modello aggiornato (ed eventualmente fornisci un valore una tantum al nuovo campo quando ./manage.py makemigrations
ti viene richiesto).
Esegui la prossima migrazione e tutto va bene.
Infine, hai finito di sviluppare la tua applicazione Django e la distribuisci sul server di produzione. Ora è il momento di eseguire tutte le migrazioni da zero nell'ambiente di produzione.
Tuttavia, la migrazione dei dati non riesce . Questo perché il modello deserializzato dal loaddata
comando, che rappresenta il codice corrente, non può essere salvato con dati vuoti per il nuovo campo obbligatorio aggiunto. L'apparecchio originale non dispone dei dati necessari per farlo!
Ma anche se aggiorni il dispositivo con i dati richiesti per il nuovo campo, la migrazione dei dati non riesce comunque . Quando la migrazione dei dati è in esecuzione, la migrazione successiva che aggiunge la colonna corrispondente al database non viene ancora applicata. Non puoi salvare i dati in una colonna che non esiste!
Conclusione: in una migrazione di dati, illoaddata
comando introduce potenziali incongruenze tra il modello e il database. NON dovresti assolutamenteusarlo direttamente in una migrazione dei dati.
La soluzione
loaddata
Il comando si basa sulla django.core.serializers.python._get_model
funzione per ottenere il modello corrispondente da un dispositivo, che restituirà la versione più aggiornata di un modello. Dobbiamo rattopparlo in modo che ottenga il modello storico.
(Il codice seguente funziona per 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),
]