Миграции Django выдают 1072 — ключевой столбец car_make_id не существует в таблице

Вот упрощенная задача и настройка (Django 1.8, MySQL, Python 2.7), у меня получилось:

class Car(models.Model):
    make = models.ForeignKey(CarMake)

class Bike(models.Model):
    make = models.ForeignKey(BikeMake)

class CarMake(models.Model):
    name = models.CharField(max_length=32)

class BikeMake(models.Model):
    name = models.CharField(max_length=32)

Теперь мне нужно полностью отказаться от модели BikeMake, поэтому я обновлю модель CarMake со значениями из BikeMake, а также обновлю отношение внешнего ключа в < strong>Велосипед.

Я создал следующую миграцию, которая обновляет CarMake именами из BikeMake, добавляет временное поле Bike.car_make, переносит данные из >Bike.make в Bike.car_make, удаляет поле Bike.make и переименовывает Bike.car_make в Bike .сделать.

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations


def update_car_makes(apps, schema_editor):
    """Update CarMakes with BikeMakes"""
    BikeMake = apps.get_model('my_app', 'BikeMake')
    CarMake = apps.get_model('my_app', 'CarMake')

    for item in BikeMake.objects.all():
        if not CarMake.objects.filter(name=item.name).exists():
            CarMake.objects.create(name=item.name)


def remove_car_makers(apps, schema_editor):
    """Restore original CarMake (exclude BikeMake)"""
    pass


def migrate_to_car_make(apps, schema_editor):
    """Set Bike.car_make according to Bike.make"""
    CarMake = apps.get_model('my_app', 'CarMake')
    Bike = apps.get_model('my_app', 'Bike')
    for item in Bike.objects.all():
        old_make = item.make
        new_make = CarMake.objects.get(name=old_make.name)
        item.car_make = new_make
        item.save()


def reverse_migrate_to_car_make(apps, schema_editor):
    pass


def dummy_forwards(apps, schema_editor):
    # Empty forward migration needed for having custom backwards migration
    pass


def restore_make_column_data(apps, schema_editor):
    BikeMake = apps.get_model('products', 'BikeMake')
    Bike = apps.get_model('products', 'Bike')

    for item in Bike.objects.all():
        old_make = item.bike_make
        new_make = BikeMake.objects.get(name=old_make.name)
        item.make = new_make
        item.save()


class Migration(migrations.Migration):

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

    operations = [
        migrations.RunPython(
            update_car_makers,
            reverse_code=remove_car_makers
        ),
        migrations.AddField(
            model_name='bike',
            name='car_make',
            field=models.ForeignKey(default=1, to='my_app.CarMake'),
            preserve_default=False
        ),
        migrations.RunPython(
            migrate_to_car_make,
            reverse_code=reverse_migrate_to_car_make
        ),
        migrations.RunPython(
            dummy_forwards,
            reverse_code=restore_make_column_data
        ),
        migrations.RemoveField(
            model_name='bike',
            name='make',
        ),
        migrations.RenameField(
            model_name='bike',
            old_name='car_make',
            new_name='make'
        )
    ]

И когда я пытаюсь запустить его, я получаю ошибку # 1072 при выполнении последней операции: migrations.RenameField. Теперь интересно то, что из DB POV все завершено, данные перенесены, столбец переименован, только миграция не помечена как выполненная и выдается ошибка.

Также, если я просто перемещу migrations.RenameField в отдельный файл миграции и запущу две миграции подряд — все будет работать нормально, и ошибка #1072 не возникнет.

Кроме того, я попытался вставить точку останова непосредственно перед migrations.RenameField и убедился, что столбец Bike.car_make существует, и я могу нормально получить все объекты модели Bike в этой точке. .

Запрос MySQL, который приводит к ошибке, выглядит следующим образом:

CREATE INDEX `my_app_bike_c2036163` ON `my_app_bike` (`car_make_id`)

Любые идеи, как это исправить и иметь в одном файле миграции? Заранее спасибо!

ОБНОВЛЕНИЕ 04.02.16

Как указал @kvikshaug, это происходит потому, что Django создает индексы и ограничения после выполнения всех операций, т. е. в то время генерируется необработанный SQL для создания индекса и/или ограничений, выполняется соответствующая операция (в моем случае AddField), но этот запрос на самом деле запускается в самом конце, отсюда и ошибка.

Одним из возможных решений для относительно небольших схем может быть использование RunSQL и сами вводите необработанные запросы, но это довольно громоздко + вам нужно создавать ограничения самостоятельно.

Поэтому я пошел с разделением миграции переименования.


person 3Gee    schedule 02.02.2016    source источник
comment
Я думаю, вы пытаетесь сделать слишком много работы за одну миграцию. Я думаю, вам может быть лучше с несколькими миграциями, например. 1. Создайте новое поле car_make. 2. Заполните поле car_make. 3. Удалите старое поле make. 4. Переименуйте поле car_make в make.   -  person Alasdair    schedule 02.02.2016
comment
Может быть и так, но даже если я разделю эти действия на несколько миграций, мне все равно хотелось бы знать, почему я получаю ошибку.   -  person 3Gee    schedule 02.02.2016


Ответы (1)


Миграции Django создают индексы после выполнения всех операций. Ваша вторая операция, добавляющая поле car_make, заставляет Django добавить команду CREATE INDEX, которая, как вы заметили, вызывает ошибку:

CREATE INDEX `my_app_bike_c2036163` ON `my_app_bike` (`car_make_id`)

Несмотря на то, что позже вы переименовали поле, Django все еще пытается создать индекс для отсутствующего поля car_make, поэтому вы получаете сообщение об ошибке. Вы можете ясно это увидеть, запустив sqlmigrate:

$ ./manage.py sqlmigrate my_app 0002_blah_blah
BEGIN;
--
-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:
-- Raw Python operation
--
--
-- Add field car_make to bike
--
ALTER TABLE "my_app_bike" ADD COLUMN "car_make_id" integer DEFAULT 1 NOT NULL;
ALTER TABLE "my_app_bike" ALTER COLUMN "car_make_id" DROP DEFAULT;
--
-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:
-- Raw Python operation
--
--
-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:
-- Raw Python operation
--
--
-- Remove field make from bike
--
ALTER TABLE "my_app_bike" DROP CONSTRAINT "my_app_bike_make_id_5615ed11_fk_my_app_bikemake_id";
ALTER TABLE "my_app_bike" DROP COLUMN "make_id" CASCADE;
--
-- Rename field car_make on bike to make
--
ALTER TABLE "my_app_bike" RENAME COLUMN "car_make_id" TO "make_id";
CREATE INDEX "my_app_bike_78e8ca60" ON "my_app_bike" ("car_make_id");
ALTER TABLE "my_app_bike" ADD CONSTRAINT "my_app_bike_car_make_id_6c42be09_fk_my_app_carmake_id" FOREIGN KEY ("car_make_id") REFERENCES "my_app_carmake" ("id") DEFERRABLE INITIALLY DEFERRED;

COMMIT;

Вы можете попытаться сообщить об этом как об ошибке (или выполнить поиск; возможно, об этом уже сообщалось), но вам, вероятно, лучше всего последовать предложению Alasdairs и просто разделить миграции.

person kvikshaug    schedule 02.02.2016