Saltar al contenido

Cargando datos iniciales con Django 1.7 y migraciones de datos

Hola, hallamos la respuesta a lo que andabas buscando, continúa leyendo y la obtendrás a continuación.

Solución:

Actualizar: Vea el comentario de @ GwynBleidD a continuación para conocer los problemas que esta solución puede causar, y vea la respuesta de @ Rockallite a continuación para obtener un enfoque que es más duradero para futuros cambios de modelo.


Suponiendo que tiene un archivo de accesorio en /fixtures/initial_data.json

  1. Crea tu migración vacía:

    En Django 1.7:

    python manage.py makemigrations --empty 
    

    En Django 1.8+, puede proporcionar un nombre:

    python manage.py makemigrations --empty  --name load_intial_data
    
  2. Edite su archivo de migración /migrations/0002_auto_xxx.py

    2.1. Implementación personalizada, inspirada en Django ‘ loaddata (respuesta inicial):

    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. Una solución más sencilla para load_fixture (por sugerencia 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) 
    

    Útil si desea utilizar un directorio personalizado.

    2.3. Más simple: vocación loaddata con app_label cargará accesorios desde el ‘s fixtures dir automáticamente:

    from django.core.management import call_command
    
    fixture = 'initial_data'
    
    def load_fixture(apps, schema_editor):
        call_command('loaddata', fixture, app_label='yourapp') 
    

    Si no especifica app_label, loaddata intentará cargar fixture nombre de archivo de todos directorios de accesorios de aplicaciones (que probablemente no quieras).

  3. Ejecutarlo

    python manage.py migrate 
    

Version corta

Debería NO usar loaddata comando de gestió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 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, supongamos que hay una migración de datos que utiliza loaddata 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 requerido campo 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 ./manage.py makemigrations le pide).

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 ejecutar 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 requerido campo que agregó. ¡El aparato original carece de los datos necesarios para ello!

Pero incluso si actualiza el dispositivo con los datos requeridos para el nuevo campo, la migración de datos aún falla. Cuando se está ejecutando la migración de datos, Siguiente la 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, el loaddata El comando introduce una posible inconsistencia entre el modelo y la base de datos. Definitivamente deberías NO utilícelo directamente en una migración de datos.

La solución

loaddata el comando se basa en 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),
    ]

Inspirado por algunos de los comentarios (es decir, n__o) y el hecho de que tengo muchos initial_data.* archivos distribuidos en varias aplicaciones, decidí crear una aplicación Django que facilitaría la creación de estas migraciones de datos.

Usando django-migration-fixture, simplemente puede ejecutar el siguiente comando de administración y buscará en todos sus INSTALLED_APPS por initial_data.* archivos y convertirlos en migraciones de datos.

./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.

Consulte django-migration-fixture para obtener instrucciones de instalación / uso.

valoraciones y reseñas

Si te ha sido de utilidad este post, nos gustaría que lo compartas con otros programadores de este modo nos ayudas a difundir nuestra información.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *