web-dev-qa-db-ja.com

Django 1.8移行で列IDを整数にキャストできません

サイトをSQLiteバックエンドからPostgresバックエンドに移行しています。プロジェクトの最初から、ネイティブDjangoスタイルの移行(つまり、南ではない)を実行してきました。ほとんどの移行は正常に実行されますが、アプリケーションには問題があります。

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

これらの2つの移行は正しく実行されました。

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.

関連するモデルテーブルの現在の完全に移行された状態は次のとおりです。

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)

移行を潰すことは役に立ちませんでした。 Postgresデータベースは、テーブル定義を除いてクリーンです。スタック形式の関連するPostgresテーブル定義は次のとおりです。

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で指定された主キーが他に何であると期待されますか?

15
Alex H.

問題は、ブールフィールドとしてのProcess.milestoneから外部キーとしてのProcess.milestoneへの移行です。 Postgresは、キャストできないデータで移行が失敗するのを待ちません。事前にテーブルを変更するルールが必要です。

2つのフィールド間でデータを移行する予定がない場合、最も簡単なオプションは、フィールドを削除して追加することです。この特定のケースでは、次のように操作を変更することを意味します。

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'},
    )
]
24
Alex H.

Postgresは、テーブルが空であっても、ブールフィールドを整数に変換する方法を知りません。 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'),
        ),
    ]

テーブルが空の場合は、空のusing句または空のケースで解決できる可能性があります。

4
noamk