Django: loaddata in migrations errors

不想你离开。 提交于 2019-12-05 09:57:39
GwynBleidD

loaddata command will simply call serializers. Serializers will work on models state from your models.py file, not from current migration, but there is little trick to fool default serializer.

First, you don't want to use that serializer by call_command but rather directly:

from django.core import serializers

def load_fixture(apps, schema_editor):
    fixture_file = '/full/path/to/testmodel.json'
    fixture = open(fixture_file)
    objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
    for obj in objects:
        obj.save()
    fixture.close()

Second, monkey-patch apps registry used by serializers:

from django.core import serializers

def load_fixture(apps, schema_editor):
    original_apps = serializers.python.apps
    serializers.python.apps = apps
    fixture_file = '/full/path/to/testmodel.json'
    fixture = open(fixture_file)
    objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
    for obj in objects:
        obj.save()
    fixture.close()
    serializers.python.apps = original_apps

Now serializer will use models state from apps instead of default one and whole migration process will succeed.

When you run python manage.py migrate it's trying to load your testmodel.json in fixtures folder, but your model (after updated) does not match with data in testmodel.json. You could try this:

  • Change your directory from fixture to _fixture.

  • Run python manage.py migrate

  • Optional, you now can change _fixture by fixture and load your data as before with migrate command or load data with python manage.py loaddata app/_fixtures/testmodel.json

To expand on the answer from GwynBleidD and mix in this issue since Postgres won't reset the primary key sequences when loaded this way (https://stackoverflow.com/a/14589706/401636)

I think I now have a failsafe migration for loading fixture data.

utils.py:

import os

from io import StringIO

import django.apps

from django.conf import settings
from django.core import serializers
from django.core.management import call_command
from django.db import connection


os.environ['DJANGO_COLORS'] = 'nocolor'


def reset_sqlsequence(apps=None, schema_editor=None):
    """Suitable for use in migrations.RunPython"""

    commands = StringIO()
    cursor = connection.cursor()
    patched = False

    if apps:
        # Monkey patch django.apps
        original_apps = django.apps.apps
        django.apps.apps = apps
        patched = True
    else:
        # If not in a migration, use the normal apps registry
        apps = django.apps.apps

    for app in apps.get_app_configs():
        # Generate the sequence reset queries
        label = app.label
        if patched and app.models_module is None:
            # Defeat strange test in the mangement command
            app.models_module = True
        call_command('sqlsequencereset', label, stdout=commands)
        if patched and app.models_module is True:
            app.models_module = None

    if patched:
        # Cleanup monkey patch
        django.apps.apps = original_apps

    sql = commands.getvalue()
    print(sql)
    if sql:
        # avoid DB error if sql is empty
        cursor.execute(commands.getvalue())


class LoadFixtureData(object):
    def __init__(self, *files):
        self.files = files

    def __call__(self, apps=None, schema_editor=None):
        if apps:
            # If in a migration Monkey patch the app registry
            original_apps = serializers.python.apps
            serializers.python.apps = apps

        for fixture_file in self.files:
            with open(fixture_file) as fixture:
                objects = serializers.deserialize('json', fixture)

                for obj in objects:
                    obj.save()

        if apps:
            # Cleanup monkey patch
            serializers.python.apps = original_apps

And now my data migrations look like:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on foo
from __future__ import unicode_literals

import os

from django.conf import settings
from django.db import migrations

from .utils import LoadFixtureData, reset_sqlsequence


class Migration(migrations.Migration):

    dependencies = [
        ('app_name', '0002_auto_foo'),
    ]

    operations = [
        migrations.RunPython(
            code=LoadFixtureData(*[
                os.path.join(settings.BASE_DIR, 'app_name', 'fixtures', fixture) + ".json"
                for fixture in ('fixture_one', 'fixture_two',)
            ]),
            # Reverse will NOT remove the fixture data
            reverse_code=migrations.RunPython.noop,
        ),
        migrations.RunPython(
            code=reset_sqlsequence,
            reverse_code=migrations.RunPython.noop,
        ),
    ]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!