The 30 Second Fix: Add a default value
If you just ran python manage.py makemigrations and got the error, the fastest way out is to give new rows a default. Django usually asks you this during makemigrations — but if you said "no" and then tried to migrate, you're stuck.
Delete the broken migration file in your app's migrations/ folder. Then re-run:
python manage.py makemigrations
This time, when Django asks "You are trying to add a non-nullable field... please provide a one-off default now", type something like 0 (for numbers), '' (for strings), or datetime.datetime.now (for dates).
Why this works: Django adds a default value to the ALTER TABLE statement for existing rows. It's a one-time default — new rows will still use whatever you set in your model, but old rows get that default. The migration applies in seconds.
This fix doesn't need code changes. Just answer the prompt correctly next time.
The 5 Minute Fix: Add null=True temporarily, then fix later
If the 30 second fix isn't possible (maybe you deleted the migration but keep getting the error), modify your model to allow null temporarily.
In your model, change the field from:
class User(models.Model):
name = models.CharField(max_length=100) # no null
age = models.IntegerField() # this is the new field causing trouble
to:
age = models.IntegerField(null=True, blank=True) # allow null temporarily
Run makemigrations and migrate. This works because the column becomes nullable — old rows get NULL, migration passes.
Now run a data migration to fill in those nulls. Create an empty migration:
python manage.py makemigrations yourapp --empty
Open the generated file and add:
from django.db import migrations
def set_default_age(apps, schema_editor):
User = apps.get_model('yourapp', 'User')
User.objects.filter(age__isnull=True).update(age=0)
class Migration(migrations.Migration):
dependencies = [
('yourapp', 'previous_migration_name'),
]
operations = [
migrations.RunPython(set_default_age),
]
Run migrate again. Now all rows have a value. Finally, change the model back to null=False. Make another migration. This will alter the column to NOT NULL, but since no nulls exist, it works.
Why this works: You're separating the column creation (nullable) from the constraint (NOT NULL). The data migration fills the gap. On Postgres, ALTER COLUMN ... SET NOT NULL only scans the table if you don't provide a CHECK constraint — but Django uses a CHECK with NOT NULL, so it's fine.
The 15+ Minute Fix: Manual SQL for complex cases
If you have millions of rows, a foreign key to a non-existent row, or a composite unique constraint, the previous fixes can timeout or fail silently. For example, adding a NOT NULL ForeignKey to a table with 5 million users on Postgres 12.
Here's what you actually do:
- First, add the column as nullable in the model. Run
makemigrationsandmigrate. No default value needed. - Connect to your database directly. For Postgres:
psql -d yourdb. For SQLite:sqlite3 db.sqlite3. - Run a batch UPDATE to fill in the values:
-- Postgres: update in batches to avoid locking
UPDATE users SET age = 0 WHERE age IS NULL AND id % 100 = 0;
-- repeat for each chunk or use a script
-- MySQL: simpler, but may lock the table
UPDATE users SET age = COALESCE(age, 0);
- Verify no nulls remain:
SELECT count(*) FROM users WHERE age IS NULL;
- Now change the model back to
null=False, blank=False. Runmakemigrations. Open the generated migration. Do not run it yet. Edit the migration to useRawSQLorRunSQLfor the ALTER:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [...]
operations = [
migrations.RunSQL(
'ALTER TABLE users ALTER COLUMN age SET NOT NULL',
reverse_sql='ALTER TABLE users ALTER COLUMN age DROP NOT NULL',
),
]
Run migrate. This avoids Django's default ALTER TABLE that does a full table scan and constraint check. The SQL runs directly, and on Postgres 11+, SET NOT NULL is a metadata-only operation if the column has no nulls — it's instant even on large tables.
Why step 3 works: Postgres's SET NOT NULL doesn't actually scan the table. It only checks the constraint when you add it. But if you leave any nulls, the ALTER will fail with the same error. So you must guarantee no nulls exist before running the ALTER. The batch update minimizes lock time.
One more thing: SQLite
SQLite is special. It doesn't support ALTER COLUMN SET NOT NULL at all. Django's migration on SQLite actually recreates the whole table. The 2-minute fix (null=True then data migration) is your only option on SQLite. Don't try the SQL approach — it won't work.
Real scenario that triggered this: Added a
newsletter_opt_inboolean field to a User model on a production Django 4.2 app with Postgres 14. The model had 80k users. Makemigrations asked for a default, I typedFalse, and it still failed because one user record had a null in a related table that was causing a cascade issue. The 5-min fix (null=True + data migration) solved it.