If you’ve ever edited your models.py, stared at your database, and wondered why nothing actually changed, don't worry, almost everyone trips over Django migrations when starting out. The confusion usually comes down to one thing: Django doesn't touch your database when you edit Python code. Changing a model is just drafting an idea. To actually update the database, you have to run two separate commands, in a specific order.
Here is what’s actually happening under the hood.
The Mental Model
Think of database changes in two stages: planning and executing.
- models.py: Your blueprint. It defines what you want your data to look like in Python.
makemigrations: The planner. It inspects models.py, compares it against your past migrations, and generates a new instruction file (a migration script). It does not touch your database.
migrate: The executor. It reads those instruction files and actually runs the SQL to create or update your database tables.
A Quick Code Example
To see this in action, we'll use the Task model from our Django Beginner Tutorial on YouTube
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Editing this file is totally safe. You can add, tweak, or delete fields all day long, nothing breaks because Python hasn't talked to your database yet.
Step 1: makemigrations (Write the Plan)
When you're ready to lock in your schema changes, run:
python manage.py makemigrations
Django checks your app’s migrations/ folder, notices your new Task model, and creates a file named something like 0001_initial.py.
If you open that file, you’ll see Python code that describes the database operations needed to build that table.
Why doesn't Django just update the database automatically?
Keeping design separate from execution saves you from accidental disasters:
- Sanity checks: You can look at the migration file before touching real data to ensure Django interpreted your changes correctly.
- Version control: Migration files live in Git alongside your code, meaning your teammates get the exact same database setup when they pull down your branch.
- Custom data fixes: If you need to transform or back up data before dropping a column, you can write custom logic inside the migration file first.
Step 2: migrate (Run the Plan)
Once the migration file looks good, run:
python manage.py migrate
Django checks a special hidden table in your database called django_migrations. It looks for any migration files in your project that haven't been run yet, executes the corresponding SQL (CREATE TABLE, ALTER TABLE, etc.), and checks them off the list.
Now your database schema matches your models.py.
The 2-Step Workflow in Action
Let’s add a deadline field to our task model:
# models.py
deadline = models.DateTimeField(null=True, blank=True)
- Draft it: Run python manage.py makemigrations. Django creates 0002_add_deadline.py with an AddField instruction.
- Apply it: Run python manage.py migrate. Django connects to PostgreSQL/SQLite and executes the SQL statement to add the column.
Common Beginner Confusions
- "I ran makemigrations, but my admin panel didn't change!" You wrote the plan, but didn't execute it. Run python manage.py migrate.
- "migrate says 'No migrations to apply', but I edited models.py!" You forgot to generate the plan. Run python manage.py makemigrations first.
- "Why do I have so many migration files?" Think of them like Git commits for your database schema. They record your project's database history step-by-step so you can replicate or roll back changes anytime.
- "Django asked me if I renamed a field, what do I do?" Pay attention to prompts like Did you rename task.due_date to task.deadline? [y/N]. If you hit "No", Django might delete the old column (and its data) and create a brand new empty one.
Practical Habits to Keep Your Database Safe
- Commit your migrations: Always check your migrations/ folder into Git. Never .gitignore it.
- Look before you leap: Skim auto-generated migration files before running migrate, especially in production.
- Deploy together: Pull your latest code to the server, then run python manage.py migrate as part of your deployment build script.
Conclusion
Once you get used to this two-step workflow, database migrations stop feeling like a chore and start working like a safety net. You design your models in Python, verify the plan, and apply the changes when you’re ready, no unexpected schema drops and no lost data.


Top comments (0)