Django — Custom Migrations

John Grant
Mar 2, 2018 · 2 min read

The Problem

I have a Django app running on Heroku. Recently I had to change one of the models to add a new ForeignKey. The app in production has data stored in the database that I want to keep and this existing data can be used to define the new ForeignKey field.

After a bit of searching I discovered I can run a custom migration to manipulate the data in the database. Before now I never really understood what migrations were really doing. As usual the Django docs are really good and for this particular case I found this How to Create Django Data Migrations blog post useful — Vitor Freitas stuff is always good.

The Solution

First I added the new field to the model:

from django.db import modelsclass modelToChange(models.Model):
manualAz = models.TextField(default="0")
manualEl = models.TextField(default="0")
manualPol = models.TextField(default="0")
newField = models.ForeignKey(FkModel, null=True)

Next I ran the makemigrations command. The makemigrations command is responsible for creating new migrations based on the changes you have made to your models (kind of obvious really).

$python manage.py makemigrations

This creates a new migration file in the app/migrations folder which looks like:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-02-08 11:57
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):dependencies = [
('myApp', '0015_auto_20180201_0936'),
]
operations = [
migrations.AddField(
model_name='modelToChange',
name='newField',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='myApp.FkModel'),
),
]

This adds a new field, ‘newField’, to the existing model, ‘modelToChange’. The field is a ForeignKey field link to another model class called FkModel.

Normally I’d run the migrate command next. Migrate — is responsible for applying and unapplying migrations. Basically, it updates the database. In this case I want to add some custom code to the migration to update the new field using existing data.

After the additions the migration file looks like this:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-02-08 11:57
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
def addCustom(apps, schema_editor): ExistingRecords = apps.get_model('myApp', 'modelToChange')
ForeignKeyRecords = apps.get_model('myApp', 'FkModel')
for message in ExistingRecords.objects.all():
fkRecord =ForeignKeyRecords.objects.filter(id=message.id)

if len(fkRecord) == 0:
fkRecord =ForeignKeyRecords(id="unknown:" + message.id)
fkRecord.save()
else:
fkRecord =fkRecord[0]
message.fkRecord =fkRecord
message.save()
class Migration(migrations.Migration): dependencies = [
('myApp', '0015_auto_20180201_0936'),
]
operations = [
migrations.AddField(
model_name='modelToChange',
name='newField',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='myApp.FkModel'),
),
migrations.RunPython(addCustom),
]

Notice the addition of migrations.RunPython(addCustom) to the Migration class and the new function, addCustom.

The last step is just to run the migrate command:

$python manage.py migrate

When the migrate command is run the addCustom function will be called. The addCustom function iterates through existing records in the database and adds the foreign key object to the existing data.

Heroku Application

It’s also possible to run custom migrations on a Heroku application. Migration files will be pushed to the app along with new code. Once this is deployed the following command can be run:

heroku run python manage.py migrate.

You can put your app in maintenance first with:

heroku maintenance:on

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store