Загрузка исходных данных с помощью Django 1.7 и миграции данных



недавно я переключился с Django 1.6 на 1.7, и я начал использовать миграции (я никогда не использовал Юг).



до 1.7, я использовал для загрузки исходных данных с fixture/initial_data.json файл, который был загружен с (при создании базы данных).



теперь я начал использовать миграции, и это поведение устарело:




если приложение использует миграции, нет автоматической загрузки светильников.
Поскольку миграция будет необходима для приложения в Django 2.0 такое поведение считается устаревшим. Если вы хотите загрузить исходные данные для приложения, рассмотрите возможность сделать это в миграции данных.
(https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures)




The официальная документация не имеет четкого примера о том, как это сделать, поэтому мой вопрос:



каков наилучший способ импорта таких исходных данных с использованием данных миграции:




  1. написать код Python с несколькими вызовами mymodel.create(...),

  2. используйте или напишите функцию Django (например, вызов loaddata) для загрузки данных из файла JSON fixture.


Я предпочитаю второй вариант.



Я не хочу использовать юг, так как Джанго, кажется, может сделать это изначально сейчас.

1352   7  

7 ответов:

обновление: см. комментарий @GwynBleidD ниже для проблем, которые может вызвать это решение, и см. ответ @Rockallite ниже для подхода, который более долговечен для будущих изменений модели.


предполагая, что у вас есть файл крепления в <yourapp>/fixtures/initial_data.json

  1. создайте пустую миграцию:

    В Django 1.7:

    python manage.py makemigrations --empty <yourapp>
    

    в Django 1.8+, вы можете обеспечить a имя:

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
    
  2. редактировать файл миграции <yourapp>/migrations/0002_auto_xxx.py

    2.1. Пользовательская реализация, вдохновленная Django'loaddata (первый ответ):

    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. Более простое решение для load_fixture (по предложению @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) 
    

    полезно, если вы хотите использовать пользовательский каталог.

    2.3. самый простой: вызов loaddata С app_label загрузит светильники из <yourapp> ' s fixtures реж автоматически :

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

    если вы не укажете app_label, loaddata будет пытаться загрузить fixture С именем все приложения светильники каталоги (которые вы, вероятно, не хотите).

  3. запустить

    python manage.py migrate <yourapp>
    

короткая версия

вы должны не использовать loaddata команды управления непосредственно в миграции данных.

# 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),
    ]

текст

loaddata использует django.core.serializers.python.Deserializer который использует самые современные модели для десериализации исторических данных в миграции. Это неправильное поведение.

например, предполагается, что существует миграция данных, которая использует loaddata команда управления для того чтобы нагрузить данные от приспособления, и оно уже применяется в среде разработки.

позже вы решите добавить новый требуются поле для соответствующей модели, поэтому вы делаете это и делаете новую миграцию против обновленной модели (и, возможно, предоставляете одноразовое значение новому полю, когда ./manage.py makemigrations запрос).

вы запускаете следующую миграцию, и все хорошо.

наконец, вы закончили разработку приложения Django и развернули его на рабочем сервере. Сейчас это время для вас, чтобы запустить все миграции с нуля на производственной среде.

однако, перенос данных не. Это потому, что десериализованная модель из loaddata команда, которая представляет текущий код, не может быть сохранена с пустыми данными для нового требуются поле, которое вы добавили. Оригинальный прибор не имеет необходимых для этого данных!

но даже если вы обновите прибор с необходимыми данными для нового поля, перенос данных по-прежнему не удается. Когда миграция данных выполняется,далее миграция, которая добавляет соответствующий столбец в базу данных, пока не применяется. Вы не можете сохранить данные в столбец, который не существует!

вывод: в миграции данных loaddata команда вводит потенциальное несоответствие между моделью и базой данных. Вы должны обязательно не используйте его непосредственно в a миграция данных.

Решение

использует django.core.serializers.python._get_model функция для того чтобы получить соответствуя модель от приспособления, которое возвратит самую последнюю версию модели. Нам нужно подлатать его, чтобы он получил историческую модель.

(следующий код работает для 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),
    ]

вдохновленный некоторыми комментариями (а именно n_ _ o) и тем, что у меня много initial_data.* файлы, разбросанные по нескольким приложениям я решил создать приложение Django, которое облегчило бы создание этих миграций данных.

используя django-migration-fixture вы можете просто запустить следующую команду управления, и она будет искать через все ваши INSTALLED_APPS на initial_data.* файлы и превратить их в миграции данных.

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

посмотреть django-migration-fixture для инструкций по установке/использованию.

лучший способ загрузить исходные данные в перенесенных приложениях - это миграция данных (как Также рекомендуется в документах). Преимущество заключается в том, что приспособление таким образом загружается как во время испытаний, так и в процессе производства.

@n_ _ o предложил переопределить loaddata команда в миграции. В моих тестах, однако, вызов loaddata команда напрямую также отлично работает. Таким образом, весь процесс является следующим:

  1. создать файл в <yourapp>/fixtures/initial_data.json

  2. создайте пустую миграцию:

    python manage.py makemigrations --empty <yourapp>
    
  3. изменить файл миграции /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),
        ]
    

чтобы дать вашей базе данных некоторые исходные данные, напишите миграция данных. При переносе данных используйте RunPython функция для загрузки данных.

Не пишите никаких команд loaddata, так как этот способ устарел.

перенос данных будет выполняться только один раз. Миграции представляют собой упорядоченную последовательность миграций. Когда 003_xxxx.py выполняется миграция, Django migrations записывает в базу данных, что это приложение переносится до этого (003), и будет выполняться только следующие миграции.

приведенные выше решения не работают для меня, к сожалению. Я обнаружил, что каждый раз, когда я меняю свои модели, я должен обновлять свои светильники. В идеале я бы вместо этого написал миграции данных для изменения созданных данных и данных, загруженных приспособлением, аналогично.

чтобы облегчить это Я написал быструю функцию которая в fixtures каталог текущего приложения и загрузить приспособление. Поместите эту функцию в миграцию в точке истории модели, которая соответствует поля в миграции.

на мой взгляд, светильники немного плохие. Если ваша база данных часто меняется, поддержание их в актуальном состоянии скоро станет кошмаром. На самом деле, это не только мое мнение, в книге "два совка Джанго" это объясняется гораздо лучше.

вместо этого я напишу файл Python для обеспечения начальной установки. Если вам нужно что-то еще, я предлагаю вам посмотреть на Фабрика мальчик.

Если вам нужно перенести некоторые данные, вы должны использовать сведения миграции.

там же "Сожгите Ваши Приспособления, Используйте Модельные Фабрики" об использовании приспособлений.

Comments

    Ничего не найдено.