#28438: Initial migration creates fields not listed in the migration if mixin class changes -------------------------------------+------------------------------------- Reporter: Michal Dabski | Owner: nobody Type: Uncategorized | Status: new Component: Uncategorized | Version: 1.11 Severity: Normal | Resolution: Keywords: | Triage Stage: migration,models,mixin,sql | Unreviewed Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 -------------------------------------+------------------------------------- Description changed by Michal Dabski:
Old description: > Consider the sample model with a mixin class: > {{{ > from django.db import models > > class TestMixin(object): > pass > > class TestModel(TestMixin, models.Model): > sample_field = models.CharField(max_length=20) > }}} > > When running makemigrations, django creates a perfectly good and valid > migration: > {{{ > # -*- coding: utf-8 -*- > # Generated by Django 1.11.3 on 2017-07-26 17:03 > from __future__ import unicode_literals > > from django.db import migrations, models > import migration_test.models > > class Migration(migrations.Migration): > > initial = True > > dependencies = [ > ] > > operations = [ > migrations.CreateModel( > name='TestModel', > fields=[ > ('id', models.AutoField(auto_created=True, > primary_key=True, serialize=False, verbose_name='ID')), > ('sample_field', models.CharField(max_length=20)), > ], > bases=(migration_test.models.TestMixin, models.Model), > ), > ] > > }}} > SQL: > > {{{ > BEGIN; > -- > -- Create model TestModel > -- > CREATE TABLE "migration_test_testmodel" ("id" serial NOT NULL PRIMARY > KEY, "sample_field" varchar(20) NOT NULL); > COMMIT; > }}} > > > Next, refactor mixin class to add a field to it - need to change mixin's > base class to {{{models.Model}}}, otherwise field will not be correctly > inherited by models: > {{{ > class TestMixin(models.Model): > mixin_field = models.CharField(max_length=20, default='test') > > class Meta: > abstract = True > }}} > > This creates a new migration which adds {{{mixin_field}}} to it - nothing > special. However, when applying both migrations after the model changes, > second migration fails with the following error > {{{django.db.utils.ProgrammingError: column "mixin_field" of relation > "migration_test_testmodel" already exists}}}. as it turns out, the first > migration's SQL has now changed to include {{{mixin_field}}}: > {{{ > BEGIN; > -- > -- Create model TestModel > -- > CREATE TABLE "migration_test_testmodel" ("id" serial NOT NULL PRIMARY > KEY, "mixin_field" varchar(20) NOT NULL, "sample_field" varchar(20) NOT > NULL); > COMMIT; > }}} > The python code of the migration obviously has not changed, but the > resulting SQL did, and it includes a field not explicitly listed in the > migration's {{{fields}}}. > > Note: > - this does not happen if the mixin class extends {{{models.Model}}} to > begin with. > - if model extends a mixin that extends {{{object}}}, it ends up in > model's {{{bases}}}, however if mixin extends {{{model.Model}}} it does > not. > > Tested with Django 1.11.3, Python 2.7 New description: Consider the sample model with a mixin class: {{{ from django.db import models class TestMixin(object): pass class TestModel(TestMixin, models.Model): sample_field = models.CharField(max_length=20) }}} When running makemigrations, django creates a perfectly good and valid migration: {{{ # -*- coding: utf-8 -*- # Generated by Django 1.11.3 on 2017-07-26 17:03 from __future__ import unicode_literals from django.db import migrations, models import migration_test.models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='TestModel', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('sample_field', models.CharField(max_length=20)), ], bases=(migration_test.models.TestMixin, models.Model), ), ] }}} SQL: {{{ BEGIN; -- -- Create model TestModel -- CREATE TABLE "migration_test_testmodel" ("id" serial NOT NULL PRIMARY KEY, "sample_field" varchar(20) NOT NULL); COMMIT; }}} Next, refactor mixin class to add a field to it - need to change mixin's base class to {{{models.Model}}}, otherwise field will not be correctly inherited by models: {{{ class TestMixin(models.Model): mixin_field = models.CharField(max_length=20, default='test') class Meta: abstract = True }}} This creates a new migration which adds {{{mixin_field}}} to it - nothing special. However, when applying both migrations after the model changes, second migration fails with the following error {{{django.db.utils.ProgrammingError: column "mixin_field" of relation "migration_test_testmodel" already exists}}}. as it turns out, the first migration's SQL has now changed to include {{{mixin_field}}}: {{{ BEGIN; -- -- Create model TestModel -- CREATE TABLE "migration_test_testmodel" ("id" serial NOT NULL PRIMARY KEY, "mixin_field" varchar(20) NOT NULL, "sample_field" varchar(20) NOT NULL); COMMIT; }}} The python code of the migration obviously has not changed, but the resulting SQL did, and it includes a field not explicitly listed in the migration's {{{fields}}}. Note: - this does not happen if the mixin class extends {{{models.Model}}} to begin with. - if model extends a mixin that extends {{{object}}}, it ends up in model's {{{bases}}}, however if mixin extends {{{model.Model}}} it does not. - when mixin's base class changes, schema migration does not reflect this change in model's {{{bases}}} Tested with Django 1.11.3, Python 2.7 Proposed solution: migration should not create database fields for model fields not explicitly listed in {{{fields}}} -- -- Ticket URL: <https://code.djangoproject.com/ticket/28438#comment:3> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To post to this group, send email to django-updates@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/064.0129d722709a9b980e238475beb72763%40djangoproject.com. For more options, visit https://groups.google.com/d/optout.