Version corta
NO debe usar el loaddata
comando de administración directamente en una migración de datos.
# 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),
]
Versión larga
loaddata
utiliza el django.core.serializers.python.Deserializer
que utiliza los modelos más actualizados para deserializar datos históricos en una migración. Ese es un comportamiento incorrecto.
Por ejemplo, suponga que hay una migración de datos que utiliza loaddata
un comando de administración para cargar datos desde un dispositivo y ya está aplicado en su entorno de desarrollo.
Más tarde, decide agregar un nuevo campo obligatorio al modelo correspondiente, así que lo hace y realiza una nueva migración contra su modelo actualizado (y posiblemente proporcione un valor único al nuevo campo cuando se le ./manage.py makemigrations
solicite).
Ejecuta la siguiente migración y todo está bien.
Finalmente, ha terminado de desarrollar su aplicación Django y la implementa en el servidor de producción. Ahora es el momento de que ejecute todas las migraciones desde cero en el entorno de producción.
Sin embargo, la migración de datos falla . Eso es porque el modelo deserializado de loaddata
comando, que representa el código actual, no se puede guardar con datos vacíos para el nuevo campo obligatorio que agregó. ¡El aparato original carece de los datos necesarios para ello!
Pero incluso si actualiza el dispositivo con los datos necesarios para el nuevo campo, la migración de datos aún falla . Cuando se está ejecutando la migración de datos, la siguiente migración que agrega la columna correspondiente a la base de datos aún no se aplica. ¡No puede guardar datos en una columna que no existe!
Conclusión: en una migración de datos, elloaddata
comando introduce una posible inconsistencia entre el modelo y la base de datos. Definitivamente NO debeusarlo directamente en una migración de datos.
La solución
loaddata
El comando se basa en la django.core.serializers.python._get_model
función para obtener el modelo correspondiente de un dispositivo, que devolverá la versión más actualizada de un modelo. Necesitamos parchearlo para que obtenga el modelo histórico.
(El siguiente código funciona para 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),
]