Manually completing a botched django migration

I wrote a lot of code for my Workload Allocation system on Friday, and had been developing it on the machine with django's built in lightweight web server, and a (default) sqlite database backend. In production I decided to use a MySQL backend in case sqlite was, well, too lite.

One of the things that is really neat about django, but which also profoundly scares me, is that it handles changes to the database schema automatically. I am used to doing all of this by hand. It has been a pleasant change, but I wondered what would happen if it went wrong.

Which it did on Friday. The migrations had worked perfectly well on the development server and after some testing I decided to roll the code into production, whereupon the migration failed. I'm still not sure why, but something in the django deep magic failed. To make things worse the process is, I have discovered, not idempotent, and trying to run the migration again caused it to fail in new places because some of the database schema changes had been successful; so it was now bailing out with "already exists" kind of errors.

Removing some tables and trying again didn't quite do the trick. I thought about trying to fix the schema manually, since with the mysql command line tool I could see what fields needed to be added, but upon inspection the restraints added by django were complex and I was unsure how important they were.

So this is my clumsy workaround, that will no doubt come back to haunt me.

I used the following commands from the top of the django app directory to find the name of the migration that was failing, and than used --fake to force django to forget about having to apply it.

python manage.py migrate --list
python manage.py migrate --fake [failed migration]

I then created a "manual" django migration that added the new fields.

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

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('loads', '0013_auto_20151130_1540'),
    ]

    operations = [
        migrations.AddField(
            model_name='modulestaff',
            name='module',
            field=models.ForeignKey(to='loads.Module'),
        ),
        migrations.AddField(
            model_name='modulestaff',
            name='staff',
            field=models.ForeignKey(to='loads.Staff'),
        ),
   

  ]

It turns out that getting the dependency right at the top is very important, it needs to be previous migration.

The name of this script is important, follow the naming convention of your most recent failed migration, changing auto to custom and the timestamp appropriately. I discovered that django, would not run this migration. It detected a conflict with the previous migration that should have created the fields and wanted me to try and merge them. That would be pointless since the previous migration failed. I also discovered to my surprise there was no --force command line switch to override this logic, though Google perhaps suggests that previous versions of django allowed this.

So, I used the sqlmigration django command to output the correct SQL that it would produce if this migration did run. Once I got it showing in the shell, I forwarded this to a file.

python manage.py sqlmigration [migrationname] > fixme.sql

Finally I used the mysql command line tool

mysql -u root -p [appname]

to get access to the database, and then used the following command to import and run the SQL produced above.

source fixme.sql

And so far so good. I had been getting Server Errors on pages relating to the botched model before and at the moment they seem to be behaving correctly. Hopefully this may help you and not come back to haunt me.

Follow me!

2 thoughts on “Manually completing a botched django migration

  1. Pingback: Workload Allocation Modelling Update – Scalability | Proving the Obviously Untrue
  2. Pingback: Migrating Django Migrations to Django 2.x | Proving the Obviously Untrue

Leave a Reply

Your email address will not be published. Required fields are marked *