Opened 10 years ago

Last modified 10 years ago

#23630 closed Bug

AlterModelTable doesn't rename auto-created tables — at Version 5

Reported by: Richard Eames Owned by: nobody
Component: Migrations Version: 1.7
Severity: Release blocker Keywords:
Cc: github@…, Shai Berger 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 Shai Berger)

According to https://docs.djangoproject.com/en/dev/ref/models/fields/#ref-manytomany, when a m2m field uses an automatic (implicit) through model, the through model's table is named by combining the containing model's table-name and the field name. Consequently, if a model contains such m2m fields and its table name is changed by a migration, the m2m field's tables should also be renamed.

This doesn't happen (see comment:4 and comment:5 for details), leading to broken models.

Change History (5)

comment:1 by Baptiste Mispelon, 10 years ago

Triage Stage: UnreviewedAccepted

Hi,

Indeed, I can reproduce this issue like so:

Create an app with the following models:

class Foo(models.Model):
    pass


class Bar(models.Model):
    foos = models.ManyToManyField(Foo)

Run makemigrations to create the initial migration then change the Foo model to:

class Foo(models.Model):
    class Meta:
        db_table = 'asdf'

Since the db_table change isn't auto-detected (#23629), also create a migration (makemigrations --empty appname):

class Migration(migrations.Migration):

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

    operations = [
        migrations.AlterModelTable(
            name='Foo',
            table='asdf',
        )
    ]

After that, run migrate to sync the database.

Once that's done, running Foo.objects.create() triggers the following error:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "./django/db/models/manager.py", line 92, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "./django/db/models/query.py", line 372, in create
    obj.save(force_insert=True, using=self.db)
  File "./django/db/models/base.py", line 591, in save
    force_update=force_update, update_fields=update_fields)
  File "./django/db/models/base.py", line 619, in save_base
    updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
  File "./django/db/models/base.py", line 700, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "./django/db/models/base.py", line 733, in _do_insert
    using=using, raw=raw)
  File "./django/db/models/manager.py", line 92, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "./django/db/models/query.py", line 921, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "./django/db/models/sql/compiler.py", line 920, in execute_sql
    cursor.execute(sql, params)
  File "./django/db/backends/utils.py", line 81, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "./django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "./django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "./django/utils/six.py", line 549, in reraise
    raise value.with_traceback(tb)
  File "./django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "./django/db/backends/sqlite3/base.py", line 485, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: asdf

Ideally, the AlterModelTable operation should be smart enough rename the M2M and foreignkeys pointing to the model whose table has changed.
If that's not possible, the limitation should definitely be documented.

Thanks.

comment:2 by Shai Berger, 10 years ago

Cc: Shai Berger added

comment:1 : The error seems to be about table asdf, the named table of the model. Are you sure you ran the migration?

Also, according to https://docs.djangoproject.com/en/dev/ref/models/fields/#ref-manytomany changing the table name for model Foo should not change anything; the M2M table should still be named app_bar_foos. A change should happen if the Bar table name is modified.

in reply to:  2 comment:3 by Baptiste Mispelon, 10 years ago

Resolution: wontfix
Status: newclosed

Replying to shaib:

Are you sure you ran the migration?

Ugh, not sure what I did there but I can't seem to be able to reproduce it now so probably you're right.

I also don't see any reason to rename the M2M table, so I'm marking this as wontfix.
Feel free to reopen this ticket if you have some more arguments for this feature, or you can also raise the issue on the django-developers mailing list.

Thanks.

comment:4 by Richard Eames, 10 years ago

Resolution: wontfix
Status: closednew

Steps to reproduce:

  1. Create models as such:
class Spam(models.Model):
        class Meta:
                db_table = 'hello_spam'

class Eggs(models.Model):
        class Meta:
                db_table = 'hello_eggs'
        
        m2m = models.ManyToManyField(Spam)
  1. Create, and run, a migration. Tables created:
    $ sqlite3 db.sqlite3 .tables
    django_migrations  hello_eggs         hello_eggs_m2m     hello_spam     
    
  2. Remove the Meta.db_table entries.
  3. Create an empty migration.
  4. Add the following to the migration (this step should be automated, but is blocked on #23629):
    class Migration(migrations.Migration):
    
        dependencies = [
            ('spam', '0001_initial'),
        ]
    
        operations = [
            # Rename the models to what they should default to
            migrations.AlterModelTable(
                    name = 'Spam',
                    table = 'spam_spam'
            ),
            migrations.AlterModelTable(
                    name = 'Eggs',
                    table = 'spam_eggs'
            ),
        ]
    
  5. Run migrations. Current tables:
    $ sqlite3 db.sqlite3 .tables
    django_migrations  hello_eggs_m2m     spam_eggs          spam_spam   
    

As you can see, the m2m table isn't renamed, and one would have to manually add the following to the migration:

migrations.RunPython(lambda a, e: e.alter_db_table(None, 'hello_eggs_m2m', 'spam_eggs_m2m'))

comment:5 by Shai Berger, 10 years ago

Description: modified (diff)
Severity: NormalRelease blocker

I've been able to reproduce the problem as described in comment:4. I've also verified that this is independent of whether the (main) models use the default table name -- the behavior is the same when changing from non-default to default (as in comment:4), from default to non-default, or between two different non-default names.

Marking as a release blocker as well -- this is, IMO, as important as #23629.

Note: See TracTickets for help on using tickets.
Back to Top