Migrating Django Migrations to Django 2.x

Django is a Python framework for making web applications, and its impressive in its completeness, flexibility and power for speedy prototyping.

It's also an impressive project for forward planning, it has a kind of built in "lint" functionality that warns about deprecated code that will be disallowed in future versions.

As a result when Django 2.0 was released I didn't have to make many changes to my app code base to get it to work successfully. However, today when I tried to update my oldest Django App (started in Django 1.8x) I hit an unexpected snag. The old migrations were sometimes invalid. Curiously I don't think this problem emerged the last time I tried.

Django uses migrations to move the database schema from one version to the next. Most of the time it's a wonderful system. In the rare case it goes wrong it can be ... tricky. Today's problem is quite specific, and easier to fix.

Django 2.0 enforces that ForeignKey fields explicitly specify a behaviour to follow on deletion of the object pointed to by the key. In general whether we Cascade the deletion, or set the field to Null, getting the behaviour write can be important, particular on fields where a Null value has a legitimate meaning.

But a bit of a sting in the tail is that an older Django project may have migrations created automatically by Django which don't obey this. I discovered this today and found I couldn't proceed with my project unless I went back and modified the old migrations to be 2.0 compliant.

So if this happens to you, here are some suggestions on fixing the problem.

You will know if you have a problem if when you try to run your test server, or indeed replace runserver by check

python3 manage.py runserver

you get an error and output like this

  File "/Users/colin/Development/WAM/WAM/loads/migrations/0024_auto_20160627_1049.py", line 7, in <module>
    class Migration(migrations.Migration):
  File "/Users/colin/Development/WAM/WAM/loads/migrations/0024_auto_20160627_1049.py", line 100, in Migration
    field=models.ForeignKey(null=True, to='loads.ActivitySet', blank=True),
TypeError: __init__() missing 1 required positional argument: 'on_delete'

I would suggest you try runserver whatever you did before as it will continue to try each time you save a file.

Open your code with your favourite editor, and open your models.py file (you may have several depending on your project), and the migration file that's broken as above.

Looking in your migration file you'll find the offending line. In this case it's the last (non trivial) line below.

      migrations.AddField(
            model_name='activity',
            name='activity_set',
            field=models.ForeignKey(null=True, to='loads.ActivitySet', blank=True),
),

To ensure that your migrations will be applied consistently with your final model (well, as long as nobody tries to migrate to an intermediate state) look carefully in the correct model (Activity) in this case, and see what decision you make for deletion there. In my case I want deletion of the ActivitySet to kill all linked Activitiy(s). So replicate the "on_delete" choice from there.

      migrations.AddField(
            model_name='activity',
            name='activity_set',
            field=models.ForeignKey(null=True, to='loads.ActivitySet', on_delete=models.CASCADE, blank=True),
),

Each time you save your new migration file the runserver terminal window will re-run the check, hopefully moving on to the next migration that needs to be fixed. Work your way through methodically until your code checks clean. Check into source control, and you're done.

 

Follow me!

Leave a Reply

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