Jake Trent

Add a Migration for a Non-null ForeignKey Field in Django

Here’s a method for getting that new foreign key column to be non-nullable and without a default.

Django migrations are awesome

Django has a solid ORM and some nice database migrations to go with it. First, make a change to your model in python code, then run:

python manage.py makemigrations

The the migrations will be generated based on your model changes since the last migration. To apply the migrations in the database, run:

python manage.py migrate

But what if you want to make a migration for a new non-nullable field?

Protective parent

Well, Django wants to help you do the right thing. And if you add a new non-nullable field to the model and run makemigrations, it’ll balk and warn you that you can’t make a new non-null field because you might already have rows without that new column, and those rows can’t violate the non-null constraint on the column.

It’ll report something like:

~/myapp ❯ python manage.py makemigrations
You are trying to add a non-nullable field 'created_by' to mymodel without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py

Give in and add the default

To get the migrations to run, add the default one way or the other.

That might look like:

from django.contrib.auth.models import User
from django.db import models

class MyModel(models.Model):
    # ...
    created_by = models.ForeignKey(User, default="")

Now you’ll be able to makemigrations.

Then turn around and remove it

But now you’re sneaking out the bedroom window…

Adjust your model again, removing the default:

from django.contrib.auth.models import User
from django.db import models

class MyModel(models.Model):
    # ...
    created_by = models.ForeignKey(User)

You run makemigrations again, and you’re set: new column, no default, non-nullable.

A bit of a run-around? Yes. Do you have a better way to do this?