Django 1.8 迁移无法将列 ID 转换为整数

Django 1.8 migration unable to cast column id to integer

我正在将我的站点从 SQLite 后端迁移到 Postgres 后端。从项目一开始,我们就一直在 运行ning 本地 Django 风格的迁移(即,不是 South)。大多数迁移 运行 都很好,但我们的应用程序出现问题。

我们在 Postgres 迁移中走到了这一步。 (所有其他应用程序已完全迁移。)所有迁移 运行 在 SQLite3 上没有发生任何事故。

processes
 [X] 0001_initial
 [X] 0002_auto_20150508_2149
 [ ] 0003_auto_20150511_1543
 [ ] 0004_auto_20150528_1739
 [ ] 0005_process_upstream
 [ ] 0006_auto_20150605_1436
 [ ] 0007_auto_20150605_1706
 [ ] 0008_milestone_prevailing_process

这两个迁移 运行 正确:

0001_initial.py:

class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='DateReason',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(unique=True, max_length=50)),
                ('active', models.BooleanField(default=True)),
                ('final', models.BooleanField(default=False)),
            ],
        ),
        migrations.CreateModel(
            name='EventType',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(unique=True, max_length=50)),
                ('active', models.BooleanField(default=True)),
            ],
        ),
        migrations.CreateModel(
            name='Metric',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(unique=True, max_length=50)),
                ('active', models.BooleanField(default=True)),
            ],
        ),
        migrations.CreateModel(
            name='Process',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(max_length=50)),
                ('sequence', models.PositiveIntegerField()),
                ('sla', models.PositiveSmallIntegerField(null=True, blank=True)),
                ('milestone', models.BooleanField(default=False)),
            ],
            options={
                'ordering': ['workflow', 'sequence'],
            },
        ),
        migrations.CreateModel(
            name='Workflow',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(unique=True, max_length=20)),
                ('active', models.BooleanField(default=True)),
            ],
        ),
        migrations.AddField(
            model_name='process',
            name='workflow',
            field=models.ForeignKey(to='processes.Workflow'),
        ),
        migrations.AlterUniqueTogether(
            name='process',
            unique_together=set([('workflow', 'name'), ('workflow', 'sequence')]),
        ),
    ]

0002_auto_20150508_2149.py:

class Migration(migrations.Migration):

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

    operations = [
        migrations.CreateModel(
            name='Milestone',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('name', models.CharField(max_length=50)),
                ('sequence', models.PositiveIntegerField()),
                ('workflow', models.ForeignKey(to='processes.Workflow')),
            ],
            options={
                'ordering': ['workflow', 'sequence'],
            },
        ),
        migrations.AlterModelOptions(
            name='process',
            options={'ordering': ['milestone', 'sequence']},
        ),
        migrations.AlterUniqueTogether(
            name='process',
            unique_together=set([('milestone', 'name'), ('milestone', 'sequence')]),
        ),
        migrations.RemoveField(
            model_name='process',
            name='workflow',
        ),
        migrations.AlterUniqueTogether(
            name='milestone',
            unique_together=set([('workflow', 'name'), ('workflow', 'sequence')]),
        ),
    ]

此迁移不会 运行: 0003_auto_20150511_1543.py

class Migration(migrations.Migration):

    dependencies = [
        ('processes', '0002_auto_20150508_2149'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='process',
            options={'ordering': ['milestone', 'sequence'], 'verbose_name_plural': 'processes'},
        ),
        migrations.AlterField(
            model_name='process',
            name='milestone',
            field=models.ForeignKey(to='processes.Milestone'),
        ),
    ]

尝试 运行 此迁移导致:

django.db.utils.ProgrammingError: column "milestone_id" cannot be cast automatically to type integer
HINT:  Specify a USING expression to perform the conversion.

相关模型 tables 当前完全迁移的状态是:

class Milestone(models.Model):
    """A collection of steps in a workflow"""
    workflow = models.ForeignKey(Workflow, blank=False, null=False)
    name = models.CharField(max_length=50, blank=False, null=False)
    sequence = models.PositiveIntegerField(blank=False, null=False)
    prevailing_process = models.ForeignKey('Process', blank=False, null=True, related_name='controls_milestone')

    class Meta:
        ordering = ['workflow', 'sequence']
        unique_together = (("workflow", "sequence"), ("workflow", "name"))

    def __unicode__(self):
        return u"{0}: {1}".format(self.workflow.name, self.name)


class Process(models.Model):
    """A step in a workflow"""
    milestone = models.ForeignKey(Milestone, blank=False, null=False)
    name = models.CharField(max_length=50, blank=False, null=False)
    sequence = models.PositiveIntegerField(blank=False, null=False)
    sla = models.PositiveSmallIntegerField(blank=True, null=True)
    upstream = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, related_name='downstream_set')

    class Meta:
        ordering = ['milestone', 'sequence']
        unique_together = (("milestone", "sequence"), ("milestone", "name"))
        verbose_name_plural = "processes"

    def __unicode__(self):
        return u"{0} {1}: {2}".format(self.milestone.workflow.name, self.milestone.name, self.name)

压缩迁移没有帮助。除了 table 定义外,Postgres 数据库是干净的。卡住形式的相关 Postgres table 定义是:

scorecard=# \d processes_milestone
                                   Table "public.processes_milestone"
   Column    |         Type          |                            Modifiers
-------------+-----------------------+------------------------------------------------------------------
 id          | integer               | not null default nextval('processes_milestone_id_seq'::regclass)
 name        | character varying(50) | not null
 sequence    | integer               | not null
 workflow_id | integer               | not null
Indexes:
    "processes_milestone_pkey" PRIMARY KEY, btree (id)
    "processes_milestone_workflow_id_21e7e70ae59594a8_uniq" UNIQUE CONSTRAINT, btree (workflow_id, sequence)
    "processes_milestone_workflow_id_363216929a08f11e_uniq" UNIQUE CONSTRAINT, btree (workflow_id, name)
    "processes_milestone_846c77cf" btree (workflow_id)
Check constraints:
    "processes_milestone_sequence_check" CHECK (sequence >= 0)
Foreign-key constraints:
    "processes_workflow_id_53b7557aa3f3378e_fk_processes_workflow_id" FOREIGN KEY (workflow_id) REFERENCES processes_workflow(id) DEFERRABLE INITIALLY DEFERRED

scorecard=# \d processes_process
                                  Table "public.processes_process"
  Column   |         Type          |                           Modifiers
-----------+-----------------------+----------------------------------------------------------------
 id        | integer               | not null default nextval('processes_process_id_seq'::regclass)
 name      | character varying(50) | not null
 sequence  | integer               | not null
 sla       | smallint              |
 milestone | boolean               | not null
Indexes:
    "processes_process_pkey" PRIMARY KEY, btree (id)
    "processes_process_milestone_20dc77c2825fcc38_uniq" UNIQUE CONSTRAINT, btree (milestone, name)
    "processes_process_milestone_5bb869985140bf86_uniq" UNIQUE CONSTRAINT, btree (milestone, sequence)
Check constraints:
    "processes_process_sequence_check" CHECK (sequence >= 0)
    "processes_process_sla_check" CHECK (sla >= 0)
Referenced by:
    TABLE "collection_implementation" CONSTRAINT "collection__process_id_6461d2ef37b3f126_fk_processes_process_id" FOREIGN KEY (process_id) REFERENCES processes_process(id) DEFERRABLE INITIALLY DEFERRED

我基本上没思路了。看起来它已经是一个整数,实际上,它还期望 Django 指定的主键是什么?

Postgres 不知道如何将布尔字段转换为整数,即使 table 为空。你需要用一个 using 子句来告诉它。在转换为外键之前,您可以使用 SQL 迁移进行整数转换。我不认为没有任何方法可以做到这一点 SQL,django 不知道该怎么做。

ALTER_SQL = '''
    ALTER TABLE processes_process ALTER COLUMN milestone TYPE integer USING (
        CASE milestone
            when TRUE then ...
            when FALSE then ...
        END
        );
    '''

class Migration(migrations.Migration):

    dependencies = [
        ('processes', '0002_auto_20150508_2149'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='process',
            options={'ordering': ['milestone', 'sequence'], 'verbose_name_plural': 'processes'},
        ),
        migrations.RunSQL(ALTER_SQL, None, [
            migrations.AlterField(
                model_name='process',
                name='milestone',
                field=models.IntegerField(),
                preserve_default=True,
            ),
        ]),
        migrations.AlterField(
            model_name='process',
            name='milestone',
            field=models.ForeignKey(to='processes.Milestone'),
        ),
    ]

如果 table 为空,您可以使用空的 using 子句或空的 case 来解决问题。

问题是从 Process.milestone 作为布尔字段迁移到 Process.milestone 作为外键。 Postgres 不会等待迁移在 uncastable 数据上失败。它需要一个规则来提前改变 table。

如果您不打算在两个字段之间进行任何类型的数据迁移,最简单的选择是简单地删除和添加字段。在这种特定情况下,这意味着更改操作如下:

operations = [
    migrations.RemoveField(
        model_name='process',
        name='milestone'
    ),
    migrations.AddField(
        model_name='process',
        name='milestone',
        field=models.ForeignKey(to='processes.Milestone'),
    ),
    migrations.AlterModelOptions(
        name='process',
        options={'ordering': ['milestone', 'sequence'], 'verbose_name_plural': 'processes'},
    )
]