Django:迁移错误中的加载数据

Posted

技术标签:

【中文标题】Django:迁移错误中的加载数据【英文标题】:Django: loaddata in migrations errors 【发布时间】:2015-12-30 22:56:19 【问题描述】:

自从使用 Django 迁移(不是南)并使用 loaddata 作为其中的固定装置以来,我发生了一些非常烦人的事情。

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

使用 1 个字段 field1(CharField 或其他)创建一个新模型 Testmodel 使用makemigrations 创建一个关联的迁移(比如说0001) 运行迁移 并在新表中添加一些数据 将数据转储到夹具testmodel.json 使用call_command('loaddata', 'testmodel.json') 创建迁移:迁移0002 为模型添加一些新字段:field2 创建关联的迁移 (0003)

现在,提交它,并将您的数据库置于更改之前的状态:./manage.py migrate myapp zero。因此,您与尚未获得更改的队友处于相同状态。

如果您尝试再次运行 ./manage.py migrate,您将在迁移 0002 处收到 ProgrammingError,表示“列字段 2 不存在”。

似乎是因为 loaddata 正在查看您的模型(已经有 field2),而不仅仅是将夹具应用于数据库。

在团队中工作时,这可能会在多种情况下发生,并且还会导致测试运行器失败。

我是不是搞错了什么?它是一个错误吗?这些情况应该怎么办?

--

我正在使用 django 1.7

【问题讨论】:

您是否在 RunPython 的正向函数中应用了数据加载? @andilabs 是的,这就是我想要做的。 【参考方案1】:

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()

序列化程序使用的第二个猴子补丁应用程序注册表:

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 的模型状态而不是默认状态,整个迁移过程将成功。

【讨论】:

很棒的sn-p!有效! 我现在正在这样做,它似乎没有增加 postgres 中的主 ID 计数器。我的测试创建的用户比我的夹具中的用户多,并且在测试开始通过之前我得到 2(夹具用户的数量)重复 ID 错误。【参考方案2】:

扩展来自 GwynBleidD 的答案并混合在这个问题中,因为 Postgres 在以这种方式加载时不会重置主键序列 (https://***.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,
        ),
    ]

【讨论】:

【参考方案3】:

当您运行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 加载数据

【讨论】:

您的建议会产生完全相同的错误,但在这种情况下有充分的理由。注意:docs.djangoproject.com/en/1.7/howto/initial-data/… 我也遇到了同样的问题,现在我手动加载数据(测试数据在 _fixture 文件夹中),因为在我的开发阶段我的模型仍在变化。 我明白你的意思。但最后我希望能够自动加载我的灯具......

以上是关于Django:迁移错误中的加载数据的主要内容,如果未能解决你的问题,请参考以下文章

Django AWS Elastic Beanstalk 迁移数据库

Django 迁移中的操作错误

django 2 中的迁移错误; AttributeError:“str”对象没有属性“decode”

django 在向后迁移/ loaddata 后从夹具加载数据使用的是模型模式而不是数据库模式

Django 如何摆脱迁移错误

具有默认值的外键上的 Django 1.7 迁移错误