Проблемы с доступом к полям модели Django из модели перед миграцией

Я перемещаю поле some_field из Model_A в новое Model_B с отношением OneToOne. Перед удалением этого поля в Model_A я хочу скопировать значение из (исторического) Model_A во вновь созданное Model_B. Проблема в том, что я не могу получить поле во время миграции, так как Model_A больше не содержит some_field.

Это сообщение об ошибке, которое я получаю, когда пытаюсь запустить свою пользовательскую миграцию:

AttributeError: 'Model_A' object has no attribute 'some_field'

Модели до изменений:

class Model_A:
    some_field = models.BooleanField(default=False)
    some_other_field = models.BooleanField(default=False)

Модели после изменений:

class Model_A:
    some_other_field = models.BooleanField(default=False)

class Model_B:
    model_a = models.OneToOneField(Model_A, related_name='extension')
    some_field = models.BooleanField(default=False)

Миграция:

class Migration(migrations.Migration):

    dependencies = [
        ('my_app', '0001_initial'),
    ]

    def forwards_func(apps, schema_editor):
        # This is where I try to get the "historical" Model_A
        Model_A = apps2.get_model("my_app", "Model_A")

        # And this is where I intend to copy the some_field values
        for model_A_instance in Model_A.objects.all():
            b = Model_B(model_a=model_A_instance)
            # b gets created correctly, but the following step fails
            b.some_field = modelA_instance.some_field
            b.save()

    operations = [
        migrations.CreateModel(
            name='Model_B',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('some_field', models.BooleanField(default=False)),
                ('model_a', models.OneToOneField(related_name='extension', to='my_app.Model_A')),
            ],
            options={
            },
            bases=(models.Model,),
        ),

        migrations.RunPython(forwards_func),

        migrations.RemoveField(
            model_name='model_a',
            name='some_field',
        ),
    ]

Я в значительной степени осознаю, что мне каким-то образом нужно получить «историческое» представление Model_A (= то, которое в настоящее время находится в базе данных), но я думал, что это то, для чего предназначена часть apps2.get_model("my_app", "Model_A").

Любые данные о том, как это сделать? Или мне следует разделить миграцию на две части, где первая создает Model_B + копирует значения some_field, а вторая удаляет поле some_field из Model_A?


person Karlis Rode    schedule 06.10.2015    source источник


Ответы (1)


Да, вам нужно иметь историческое представление Model_A, и его довольно легко получить. Вот для чего apps передается в вашу функцию, вызываемую RunPython миграцией, так почему вы используете здесь какие-то apps2 мертвые приложения? Кроме того, вы должны получить свой Model_B из того же экземпляра apps, что и Model_A. Ваша миграция должна выглядеть так:

class Migration(migrations.Migration):

    dependencies = [
        ('my_app', '0001_initial'),
    ]

    def forwards_func(apps, schema_editor):
        # This is where I try to get the "historical" Model_A
        # here is change - using apps passed into forwards_func by RunPython instead of apps2
        Model_A = apps.get_model("my_app", "Model_A")
        Model_B = apps.get_model("my_app", "Model_B")

        # And this is where I intend to copy the some_field values
        for model_A_instance in Model_A.objects.all():
            b = Model_B(model_a=model_A_instance)
            b.some_field = modelA_instance.some_field
            b.save()

    operations = [
        migrations.CreateModel(
            name='Model_B',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('some_field', models.BooleanField(default=False)),
                ('model_a', models.OneToOneField(related_name='extension', to='my_app.Model_A')),
            ],
            options={
            },
            bases=(models.Model,),
        ),

        migrations.RunPython(forwards_func),

        migrations.RemoveField(
            model_name='model_a',
            name='some_field',
        ),
    ]

Почему вы все равно используете apps2? И что это такое?

person GwynBleidD    schedule 06.10.2015
comment
Кажется, я забыл вернуться к одной из моих предыдущих попыток с этим импортом: from django.apps import apps as apps2 К сожалению, удаление этого импорта и запуск apps.get_model() не решает проблему. Вместо этого это дает мне следующую ошибку: ValueError: Невозможно назначить ‹Model_A: объект Model_A›: Model_B.model_a должен быть экземпляром Model_A. - person Karlis Rode; 07.10.2015
comment
Ваш Model_B должен быть получен с использованием того же apps, что и Model_A, см. обновленный ответ. - person GwynBleidD; 07.10.2015
comment
GwynBleidD, ты заслужил медаль! Все прошло гладко, когда я получил новые модели так же, как Model_A. Почему-то я подумал, что будет нормально их смешать. Очевидно, я был неправ! :) - person Karlis Rode; 07.10.2015
comment
Вам не следует никогда импортировать модели из models.py или других источников при миграции. Всегда полагайтесь на apps, переданные в ваши пользовательские функции миграции. - person GwynBleidD; 07.10.2015