首页 文章

Django:迁移错误中的loaddata

提问于
浏览
3

因为使用Django迁移(不是南方)并使用loaddata来修复它们内部的装置,所以我真的很烦恼 .

这是一个重现我的问题的简单方法:

  • 创建一个新模型 Testmodel ,带有1个字段 field1 (CharField或其他)

  • 使用 makemigrations 创建关联的迁移(假设为 0001

  • 运行迁移

  • 并在新表中添加一些数据

  • 将数据转储到夹具中 testmodel.json

  • 使用 call_command('loaddata', 'testmodel.json') 创建迁移:migration 0002

  • 为模型添加一些新字段: field2

  • 创建关联的迁移( 0003

现在,提交它,并将您的数据库置于更改之前的状态: ./manage.py migrate myapp zero . 所以你和你的队友处于同样的状态,但还没有得到你的改变 .

If you try to run ./manage.py migrate again you will get a ProgrammingError at migration 0002 saying that "column field2 does not exist".

这似乎是因为loaddata正在查看你的模型(已经有 field2 ),而不仅仅是将数据包应用于数据库 .

在团队中工作时,可能会发生这种情况,并且还会使测试运行器失败 .

我弄错了吗?这是一个错误吗?应该做的是那些案件?

我正在使用django 1.7

3 回答

  • 2

    当您运行 python manage.py migrate 时,它正在尝试在 fixtures 文件夹中加载 testmodel.json ,但您的模型(更新后)与 testmodel.json 中的数据不匹配 . 你可以试试这个:

    • 将目录从 fixture 更改为 _fixture .

    • 运行 python manage.py migrate

    • 可选,您现在可以通过 fixture 更改 _fixture 并使用 migrate 命令加载数据或使用 python manage.py loaddata app/_fixtures/testmodel.json 加载数据

  • 7

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

    我想我现在有一个加载夹具数据的故障安全迁移 .

    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
    

    现在我的数据迁移看起来像:

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

    loaddata 命令只会调用序列化程序 . 序列化程序将处理来自 models.py 文件的模型状态,而不是当前迁移,但是没有什么技巧可以欺骗默认的序列化程序 .

    首先,您不希望 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()
    

    其次,序列化器使用的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
    

    现在,序列化程序将使用 apps 中的模型状态而不是默认状态,整个迁移过程将成功 .

相关问题