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
from django.db import models class modelToChange(models.Model): manualAz = models.TextField(default="0") manualEl = models.TextField(default="0") manualPol = models.TextField(default="0") newField = models.ForeignKey(FkModel, null=True)
$python manage.py makemigrations
# -*- 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