Django Manual migrations


Example

Sometimes, migrations generated by Django are not sufficient. This is especially true when you want to make data migrations.

For instance, let's you have such model:

class Article(models.Model):
    title = models.CharField(max_length=70)

This model already have existing data and now you want to add a SlugField:

class Article(models.Model):
    title = models.CharField(max_length=70)
    slug = models.SlugField(max_length=70)

You created the migrations to add the field, but now you would like to set the slug for all existing article, according to their title.

Of course, you could just do something like this in the terminal:

$ django-admin shell
>>> from my_app.models import Article
>>> from django.utils.text import slugify
>>> for article in Article.objects.all():
...     article.slug = slugify(article.title)
...     article.save()
...
>>>

But you will have to do this in all your environments (ie. your office desktop, your laptop, ...), all your coworkers will have to do so as well, and you will have to think about it on staging and when pushing live.

To make it once and for all, we will do it in a migration. First create an empty migration:

$ django-admin makemigrations --empty app_name

This will create an empty migration file. Open it, it contains an base skeleton. Let's say your previous migration was named 0023_article_slug and this one is named 0024_auto_20160719_1734. Here is what we will write in our migration file:

# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-19 15:34
from __future__ import unicode_literals

from django.db import migrations
from django.utils.text import slugify


def gen_slug(apps, schema_editor):
    # We can't import the Article model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Article = apps.get_model('app_name', 'Article')
    for row in Article.objects.all():
        row.slug = slugify(row.name)
        row.save()


class Migration(migrations.Migration):

    dependencies = [
        ('hosting', '0023_article_slug'),
    ]

    operations = [
        migrations.RunPython(gen_slug, reverse_code=migrations.RunPython.noop),
        # We set `reverse_code` to `noop` because we cannot revert the migration
        # to get it back in the previous state.
        # If `reverse_code` is not given, the migration will not be reversible,
        # which is not the behaviour we expect here.
    ]