简体   繁体   English

Django:迁移错误中的loaddata

[英]Django: loaddata in migrations errors

Something really annoying is happening to me since using Django migrations (not south) and using loaddata for fixtures inside of them. 自从使用Django迁移(不是南方)并在其中使用loaddata for fixture以来,我发生了一些令人讨厌的事情。

Here is a simple way to reproduce my problem: 这是一个重现我的问题的简单方法:

  • create a new model Testmodel with 1 field field1 (CharField or whatever) 使用1字段field1 (CharField或其他)创建一个新模型Testmodel
  • create an associated migration (let's say 0001 ) with makemigrations 使用makemigrations创建关联的迁移(假设为0001
  • run the migration 运行迁移
  • and add some data in the new table 并在新表中添加一些数据
  • dump the data in a fixture testmodel.json 将数据转储到fixture testmodel.json
  • create a migration with call_command('loaddata', 'testmodel.json') : migration 0002 使用call_command('loaddata', 'testmodel.json')创建迁移:迁移0002
  • add some a new field to the model: field2 在模型中添加一些新字段: field2
  • create an associated migration ( 0003 ) 创建关联的迁移( 0003

Now, commit that, and put your db in the state just before the changes: ./manage.py migrate myapp zero . 现在,提交,并将您的数据库置于更改之前的状态: ./manage.py migrate myapp zero So you are in the same state as your teammate that didn't get your changes yet. 所以你和你的队友处于同样的状态,但还没有得到你的改变。

If you try to run ./manage.py migrate again you will get a ProgrammingError at migration 0002 saying that "column field2 does not exist". 如果您再次尝试运行./manage.py migrate ,则会在迁移0002时收到ProgrammingError ,说“column field2不存在”。

It seems it's because loaddata is looking into your model (which is already having field2 ), and not just applying the fixture to the db. 这似乎是因为loaddata正在查看你的模型(已经有了field2 ),而不仅仅是将数据包应用到db。

This can happen in multiple cases when working in a team, and also making the test runner fail. 在团队中工作时,可能会发生多种情况,也会导致测试运行器失败。

Did I get something wrong? 我弄错了吗? Is it a bug? 这是一个错误吗? What should be done is those cases? 应该做些什么呢?

-- -

I am using django 1.7 我正在使用django 1.7

loaddata command will simply call serializers. loaddata命令只会调用序列化程序。 Serializers will work on models state from your models.py file, not from current migration, but there is little trick to fool default serializer. 序列化程序将处理models.py文件中的模型状态,而不是当前迁移,但是没有什么可以欺骗默认的序列化程序。

First, you don't want to use that serializer by call_command but rather directly: 首先,您不希望通过call_command使用该序列化call_command ,而是直接使用该序列化call_command

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: 其次,序列化器使用的monkey-patch apps注册表:

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. 现在,序列化程序将使用apps模型状态而不是默认状态,整个迁移过程将成功。

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 . 当你运行python manage.py migrate它试图在fixtures文件夹中加载你的testmodel.json ,但你的模型(更新后)与testmodel.json数据不匹配。 You could try this: 你可以试试这个:

  • Change your directory from fixture to _fixture . 将目录从fixture更改为_fixture

  • Run python manage.py migrate 运行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 可选,您现在可以通过fixture更改_fixture并像以前一样使用python manage.py loaddata app/_fixtures/testmodel.json加载数据来使用migrate命令或加载数据

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 ) 扩展GwynBleidD的答案并混合使用此问题,因为Postgres在以这种方式加载时不会重置主键序列( https://stackoverflow.com/a/14589706/401636

I think I now have a failsafe migration for loading fixture data. 我想我现在有一个用于加载夹具数据的故障安全迁移。

utils.py: 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,
        ),
    ]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM