12

I'm having issues with reverting a Django (1.8.7) migration that contains the renaming of a table. Even though it seems to be able to rename it in Postgres, it then tries to add a constraint using the old table name.

Here's the traceback:

    cursor.execute(sql, params)
  File "/Users/myworkspace/projects/xxx/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 79, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/Users/myworkspace/projects/xxx/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/Users/myworkspace/projects/xxx/venv/lib/python3.5/site-packages/django/db/utils.py", line 97, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/Users/myworkspace/projects/xxx/venv/lib/python3.5/site-packages/django/utils/six.py", line 658, in reraise
    raise value.with_traceback(tb)
  File "/Users/myworkspace/projects/xxx/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "team_membershiprole" does not exist

If you take a look at the SQL it generates,

[...]
ALTER TABLE "team_membershiprole" RENAME TO "team_leadershiprole";
[...]
ALTER TABLE "team_leadershipteammember" 
    ADD CONSTRAINT "team_l_role_id_xxx" 
    FOREIGN KEY ("role_id") REFERENCES "team_membershiprole" ("id")     
    DEFERRABLE INITIALLY DEFERRED;
[...]

COMMIT;

You can see that there's a reference to team_membershiprole, even though that table doesn't exist anymore (it was renamed). Here's the migration code:

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

from django.db import models, migrations


class Migration(migrations.Migration):

    dependencies = [
        ('core', '0023_xxx'),
        ('team', '0009_xxx2'),
    ]

    operations = [
        migrations.CreateModel(
            name='TeamMembership',
            fields=[
                ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
                ('team', models.ForeignKey(related_name='members', to='team.Team')),
                ('member', models.ForeignKey(to='core.Member')),
            ],
        ),
        migrations.RenameModel(
            old_name='LeadershipRole',
            new_name='MembershipRole',
        ),
        migrations.RemoveField(
            model_name='leadershipteammember',
            name='team',
        ),
        migrations.RemoveField(
            model_name='leadershipteammember',
            name='member',
        ),
        migrations.RemoveField(
            model_name='leadershipteammember',
            name='role',
        ),
        migrations.DeleteModel(
            name='LeadershipTeamMember',
        ),
        migrations.AddField(
            model_name='teammembership',
            name='role',
            field=models.ForeignKey(to='team.MembershipRole'),
        ),
    ]

I understand this is might be a Django migrations bug, but is there any way to work around it?

  • 1
    Could you check if it works on 1.9, and if not, create a ticket on https://code.djangoproject.com/newticket? That should also help determine the root cause and develop a workaround. – knbk Dec 11 '15 at 22:09
  • I'll try that and comment back here with the results. – Alexandre Cordeiro Dec 16 '15 at 13:18

1 Answers1

2

You can override Migration.unapply so it uses a different set of operations.

class MyMigration(Migration):
    operations = [
        ... your operations ...
    ]
    reverse_operations = [
        ... your fixed reverse operations ...
    ]
    def unapply(self, project_state, schema_editor, collect_sql=False):
        self.operations = self.reverse_operations
        return super(MyMigration, self).unapply(..)

I have not tested this but it should give you an idea. Oh and you might need to reverse the list of reverse migrations because Django will be expecting it to be the list of forward migrations so traverse them in reverse.

Ronald
  • 1,009
  • 6
  • 13
  • That makes sense, but should I add the raw SQL queries for the reverse migration? I'd like at least to keep using Django migration API for the operations. – Alexandre Cordeiro Dec 16 '15 at 13:18
  • Ok looking at the source it looks like you can override Migration.unapply and hack a customized self.operations into place before calling super(..). I think that is best here, you don't need the RunPython stuff I suggested first. – Ronald Dec 17 '15 at 13:45