Django Migrations Recipe #1

This is the first migration recipe I publish for Django’s migration framework. I published it as part of my talk at DjangoCon Europe 2016 in Budapest. This recipe will show that migration code is just Python and Django’s built-in “makemigrations” command might not output the most efficient migration.

This recipe show how one can optimize a migration Django creates.

Take the following 2 models, a “Book” and a “Library”. Each book is associated with exactly one library:

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    library = models.ForeignKey('Library')

class Library(models.Model):
    name = models.CharField(max_length=200)

When you run makemigrations for an app with these models you get 3 operations.

  • The first one is a CreateModel operation and ensures the Book model exists with a primary key and a name. But the reference to the library is nowhere to be found.
  • The CreateModel operation for the Library looks as expected. There’s a primary key and a name.
  • The next operation is an AddField. And there it is, the library field for the Book model.
$ python manage.py makemigrations
Migrations for 'optimize_makemigrations':
  0001_initial.py:
    - Create model Book
    - Create model Library
    - Add field library to book

You may ask now why the ForeignKey is added as a separate operation. There are 2, maybe 3 reasons behind that:

  • Firstly, having references at the end of a migration when automatically creating one is always safe.
  • Secondly, Django’s migration framework processes all models within the same app in alphabetical order to provide a deterministic behavior. “Book” comes before “Library”, last time I checked.
  • Thirdly, nobody has contributed a patch yet. If you’re staying for the sprints, that might be something to look into.
from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = []
    operations = [
        migrations.CreateModel(
            name='Book',
            fields=[
                ('id', models.AutoField(...)),
                ('title', models.CharField(max_length=200)),
            ],
        ),
        migrations.CreateModel(
            name='Library',
            fields=[
                ('id', models.AutoField(...)),
                ('name', models.CharField(max_length=200)),
            ],
        ),
        migrations.AddField(
            model_name='book',
            name='library',
            field=models.ForeignKey(to='app.Library'),
        ),
    ]

Now, that we have this example, how can it be optimized? I guess some of you already have an idea:

from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = []
    operations = [
        migrations.CreateModel(
            name='Library',
            fields=[
                ('id', models.AutoField(...)),
                ('name', models.CharField(max_length=200)),
            ],
        ),
        migrations.CreateModel(
            name='Book',
            fields=[
                ('id', models.AutoField(...)),
                ('title', models.CharField(max_length=200)),
                ('library', models.ForeignKey(to='app.Library')),
            ],
        ),
    ]

The answer to that is, to re-order the CreateModel operations and merge the AddField into the CreateModel for the Book.

Resources