Carga de datos iniciales con Django 1.7 y migraciones de datos


Recientemente cambié de Django 1.6 a 1.7, y comencé a usar migraciones (nunca usé Sur).

Antes de la versión 1.7, solía cargar los datos iniciales con un archivo fixture/initial_data.json, que se cargaba con el comando python manage.py syncdb (al crear la base de datos).

Ahora, empecé a usar migraciones, y este comportamiento está en desuso :

Si una aplicación utiliza migraciones, no hay carga automática de accesorios. Dado que las migraciones serán necesarias para las aplicaciones en Django 2.0, esto el comportamiento se considera obsoleto. Si desea cargar datos iniciales para una aplicación, considere hacerlo en una migración de datos. (https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures)

La documentación oficial no tiene un ejemplo claro de cómo hacerlo, así que mi pregunta es:

¿Cuál es la mejor manera de importar dichos datos iniciales utilizando migraciones de datos:

  1. Escribir código Python con múltiples llamadas a mymodel.create(...),
  2. Use o escriba una función Django (como llamar loaddata) para cargar datos desde un archivo de accesorio JSON.

Prefiero la segunda opción.

No quiero usar South, ya que Django parece ser capaz de hacerlo de forma nativa ahora.

Author: Community, 2014-09-21

7 answers

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


Asumiendo que usted tiene un archivo fixture en <yourapp>/fixtures/initial_data.json

  1. Crea tu migración vacía:

    En Django 1.7: {[19]]}

    python manage.py makemigrations --empty <yourapp>
    

    En Django 1.8+, puede proporcionar un nombre:

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
    
  2. Edita tu archivo de migración <yourapp>/migrations/0002_auto_xxx.py

    2.1. Personalizar implementación, 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 simple para load_fixture (por sugerencia de @julioesar):

    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: llamando a loaddata con {[11] } cargará los accesorios de la <yourapp>'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 desde todos accesorios de aplicaciones directorios (que probablemente no quieras).

  3. Ejecutarlo

    python manage.py migrate <yourapp>
    
 75
Author: n__o,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-07 18:12:56

Versión corta

Debe NO usar el comando de administración loaddata 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. Es un comportamiento incorrecto.

Por ejemplo, se supone que hay una migración de datos que utiliza el comando de administración loaddata para cargar datos de un accesorio, y ya está aplicado en su desarrollo ambiente.

Más tarde, decide agregar un nuevo campo requerido al modelo correspondiente, por lo 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 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 ejecutar todas las migraciones desde cero en el entorno de producción.

Sin embargo, la migración de datos falla. Esto se debe a que el comando deserialized model from loaddata, que representa el código actual, no se puede guardar con datos vacíos para el nuevo campo required que ha agregado. El accesorio original carece de los datos necesarios para ello!

Pero incluso si actualiza el fixture 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, el 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 comando loaddata introduce una posible inconsistencia entre el modelo y la base de datos. Definitivamente debe NO usarlo directamente en una migración de datos.

La Solución

loaddata el comando se basa en la función django.core.serializers.python._get_model para obtener el modelo correspondiente de un accesorio, que devolverá la versión más actualizada de un modelo. Tenemos que parchearlo para que tenga 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),
    ]
 32
Author: Rockallite,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-18 03:21:37

Inspirado por algunos de los comentarios (es decir, n__o's) y el hecho de que tengo muchos archivos initial_data.* repartidos en varias aplicaciones, decidí crear una aplicación Django que facilitara 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 archivos initial_data.* y los convertirá 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.

Ver django-migration-fixture para instalación / uso instrucción.

 5
Author: alexhayes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-06 21:27:00

La mejor manera de cargar los datos iniciales en las aplicaciones migradas es a través de migraciones de datos (como también se recomienda en los documentos). La ventaja es que el accesorio se carga tanto durante las pruebas como durante la producción.

@n__o se sugiere reimplementar el comando loaddata en la migración. En mis pruebas, sin embargo, llamar al comando loaddata directamente también funciona bien. Todo el proceso es así:

  1. Crear un archivo fixture en <yourapp>/fixtures/initial_data.json

  2. Crea tu vacío migración:

    python manage.py makemigrations --empty <yourapp>
    
  3. Edite su archivo de migración /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),
        ]
    
 5
Author: Pratyush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-21 22:35:13

Para dar a su base de datos algunos datos iniciales, escriba una migración de datos . En la migración de datos, utilice la función RunPython para cargar sus datos.

No escriba ningún comando loaddata ya que esta forma está obsoleta.

Sus migraciones de datos se ejecutarán solo una vez. Las migraciones son una secuencia ordenada de migraciones. Cuando el 003_xxxx.py migrations se ejecuta, django migrations escribe en la base de datos que esta aplicación se migra hasta ésta (003), y ejecutará solo tras migraciones.

 2
Author: aRkadeFR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-22 07:17:43

Las soluciones presentadas anteriormente no funcionaron para mí desafortunadamente. Descubrí que cada vez que cambio mis modelos tengo que actualizar mis accesorios. Idealmente, escribiría migraciones de datos para modificar los datos creados y los datos cargados de manera similar.

Para facilitar esto escribí una función rápida que buscará en el directorio fixtures de la aplicación actual y cargará un accesorio. Coloque esta función en una migración en el punto del historial del modelo que coincida con los campos migración.

 1
Author: leifdenby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-10 21:07:05

En mi opinión los accesorios son un poco malos. Si su base de datos cambia con frecuencia, mantenerlos actualizados pronto será una pesadilla. En realidad, no es solo mi opinión, en el libro "Two Scoops of Django" se explica mucho mejor.

En su lugar, escribiré un archivo Python para proporcionar la configuración inicial. Si necesitas algo más te sugiero que mires Factory boy .

Si necesita migrar algunos datos, debe usar migraciones de datos.

También hay "Queme Sus Accesorios, Use Fábricas de Modelos" acerca del uso de accesorios.

 0
Author: Griffosx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-21 22:33:08