Opened 9 years ago

Last modified 9 years ago

#23956 closed Bug

makemigrations creates broken initial migration for models with multiple inheritance — at Version 1

Reported by: Kirill Gagarski Owned by: nobody
Component: Migrations Version: 1.7
Severity: Normal Keywords: migrations inheritance
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Kirill Gagarski)

Hello.
I am trying to create models that use multiple inheritance and then use django migrations mechanism to migrate. But I have some problems with it.

I have created simplest example to reproduce.

Steps to reproduce

  1. Create a 'migrationstest' django app with following models.py:
from django.db import models


class A(models.Model):
    a_id = models.IntegerField(primary_key=True)


class B(models.Model):
    b_id = models.IntegerField(primary_key=True)


class C(A, B):
    pass


class D(A, B):
    pass


class E(A, B):
    pass

  1. run './manage.py makemigrations migrationstest'
  2. run './manage.py migrate migrationstest'

Expected results

Fine migration

Actual results

Exception is thrown while running 'migrate':

Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/core/management/commands/migrate.py", line 160, in handle
    executor.migrate(targets, plan, fake=options.get("fake", False))
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/migrations/executor.py", line 63, in migrate
    self.apply_migration(migration, fake=fake)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/migrations/executor.py", line 97, in apply_migration
    migration.apply(project_state, schema_editor)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/migrations/migration.py", line 107, in apply
    operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/migrations/operations/fields.py", line 37, in database_forwards
    field,
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/backends/schema.py", line 390, in add_field
    self.execute(sql, params)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/backends/schema.py", line 99, in execute
    cursor.execute(sql, params)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/backends/utils.py", line 81, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/utils/six.py", line 549, in reraise
    raise value.with_traceback(tb)
  File "/home/gagarski/polyana-web/environment/lib/python3.4/site-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: column "b_ptr_id" of relation "migrationstest_d" already exists

More details

The generated initial migration:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='A',
            fields=[
                ('a_id', models.IntegerField(serialize=False, primary_key=True)),
            ],
            options={
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='B',
            fields=[
                ('b_id', models.IntegerField(serialize=False, primary_key=True)),
            ],
            options={
            },
            bases=(models.Model,),
        ),
        migrations.CreateModel(
            name='C',
            fields=[
                ('a_ptr', models.OneToOneField(parent_link=True, serialize=False, primary_key=True, to='migrationstest.A', auto_created=True)),
            ],
            options={
            },
            bases=('migrationstest.a', 'migrationstest.b'),
        ),
        migrations.CreateModel(
            name='D',
            fields=[
                ('a_ptr', models.OneToOneField(parent_link=True, serialize=False, primary_key=True, to='migrationstest.A', auto_created=True)),
            ],
            options={
            },
            bases=('migrationstest.a', 'migrationstest.b'),
        ),
        migrations.CreateModel(
            name='E',
            fields=[
                ('a_ptr', models.OneToOneField(parent_link=True, serialize=False, primary_key=True, to='migrationstest.A', auto_created=True)),
                ('b_ptr', models.OneToOneField(parent_link=True, to='migrationstest.B', auto_created=True)),
            ],
            options={
            },
            bases=('migrationstest.a', 'migrationstest.b'),
        ),
        migrations.AddField(
            model_name='d',
            name='b_ptr',
            field=models.OneToOneField(parent_link=True, to='migrationstest.B', auto_created=True),
            preserve_default=True,
        ),
        migrations.AddField(
            model_name='c',
            name='b_ptr',
            field=models.OneToOneField(parent_link=True, to='migrationstest.B', auto_created=True),
            preserve_default=True,
        ),
    ]

As you can see b_ptr field is added in CreateModel only for the last model (E).
For D and C it is added via AddField. But it seems that b_ptr is already added to them even without a field specified in CreateModel's fields argument (with the help of bases argument, I think).

Output of sqlmigrate

BEGIN;
CREATE TABLE "migrationstest_a" ("a_id" integer NOT NULL PRIMARY KEY);
CREATE TABLE "migrationstest_b" ("b_id" integer NOT NULL PRIMARY KEY);
CREATE TABLE "migrationstest_c" ("b_ptr_id" integer NOT NULL UNIQUE, "a_ptr_id" integer NOT NULL PRIMARY KEY);
CREATE TABLE "migrationstest_d" ("b_ptr_id" integer NOT NULL UNIQUE, "a_ptr_id" integer NOT NULL PRIMARY KEY);
CREATE TABLE "migrationstest_e" ("b_ptr_id" integer NOT NULL UNIQUE, "a_ptr_id" integer NOT NULL PRIMARY KEY);
ALTER TABLE "migrationstest_d" ADD COLUMN "b_ptr_id" integer NOT NULL UNIQUE;
ALTER TABLE "migrationstest_d" ALTER COLUMN "b_ptr_id" DROP DEFAULT;
ALTER TABLE "migrationstest_c" ADD COLUMN "b_ptr_id" integer NOT NULL UNIQUE;
ALTER TABLE "migrationstest_c" ALTER COLUMN "b_ptr_id" DROP DEFAULT;
ALTER TABLE "migrationstest_c" ADD CONSTRAINT "migrationste_b_ptr_id_6684147e60c05d18_fk_migrationstest_b_b_id" FOREIGN KEY ("b_ptr_id") REFERENCES "migrationstest_b" ("b_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "migrationstest_c" ADD CONSTRAINT "migrationste_a_ptr_id_2670758b3e821055_fk_migrationstest_a_a_id" FOREIGN KEY ("a_ptr_id") REFERENCES "migrationstest_a" ("a_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "migrationstest_d" ADD CONSTRAINT "migrationste_b_ptr_id_2ed6e3025678dfb9_fk_migrationstest_b_b_id" FOREIGN KEY ("b_ptr_id") REFERENCES "migrationstest_b" ("b_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "migrationstest_d" ADD CONSTRAINT "migrationste_a_ptr_id_4d0009d1fad71dec_fk_migrationstest_a_a_id" FOREIGN KEY ("a_ptr_id") REFERENCES "migrationstest_a" ("a_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "migrationstest_e" ADD CONSTRAINT "migrationste_b_ptr_id_29d2159648c59c3e_fk_migrationstest_b_b_id" FOREIGN KEY ("b_ptr_id") REFERENCES "migrationstest_b" ("b_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "migrationstest_e" ADD CONSTRAINT "migrationste_a_ptr_id_405a8eb9707708c9_fk_migrationstest_a_a_id" FOREIGN KEY ("a_ptr_id") REFERENCES "migrationstest_a" ("a_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "migrationstest_d" ADD CONSTRAINT "migrationste_b_ptr_id_2ed6e3025678dfb9_fk_migrationstest_b_b_id" FOREIGN KEY ("b_ptr_id") REFERENCES "migrationstest_b" ("b_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "migrationstest_c" ADD CONSTRAINT "migrationste_b_ptr_id_6684147e60c05d18_fk_migrationstest_b_b_id" FOREIGN KEY ("b_ptr_id") REFERENCES "migrationstest_b" ("b_id") DEFERRABLE INITIALLY DEFERRED;

COMMIT;

Questions

Is there a good reasons to do like that? Is there a workaround that can help me to use automatic migration generation without manually editing them (at least initial migration)?

Change History (1)

comment:1 by Kirill Gagarski, 9 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top