python - Django 迁移抛出 1072 - 表中不存在键列 'car_make_id'

标签 python mysql django django-migrations

这是简化的任务和设置(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 模型,以便使用 BikeMake 中的值更新 CarMake 模型,并更新外键自行车中的关系。

我创建了以下迁移,它使用 BikeMake 中的名称更新 CarMake,添加临时字段 Bike.car_make,迁移数据从 Bike.makeBike.car_make,删除 Bike.make 字段并将 Bike.car_make 重命名为 <强>Bike.make。

# -*- 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 列存在,并且我可以正常获取以下所有对象此时的自行车型号。

导致错误的 MySQL 查询如下:

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

有什么想法可以修复它并将其包含在一个迁移文件中吗?提前致谢!

更新 04.02.16

正如 @kvikshaug 指出的,发生这种情况是因为 Django 在执行所有操作后创建索引和约束,即当时生成用于创建索引和/或约束的原始 SQL,执行相应的操作(在我的情况下 AddField ),但该查询实际上是在最后运行的,因此出现错误。

对于相对较小的模式,一种可能的解决方案是使用 Django 的 RunSQL并自己输入原始查询,但这非常麻烦+您必须自己创建约束。

所以我选择分离重命名迁移。

最佳答案

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 的建议并分开迁移。

关于python - Django 迁移抛出 1072 - 表中不存在键列 'car_make_id',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35158530/

相关文章:

python - skflow.TensorFlowDNNRegressor 参数

mysql - 使用触发器停止插入或更新

php - utf8_decode 未解码 ×(黑色菱形 �)

php - 尝试创建 MySQL 命令,选择一列并搜索另外两行

python - 如何在 Django 中输出 FilePathField 的完整路径?

Django:为什么在外键定义中的模型周围有引号

django - 想要使用单个脚本运行多个命令

python - Django/报告实验室 : Save generated pdf directly to FileField in AWS s3

python - 将整行设置为值

python - 函数属性 - 范围