Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 20

Managing Schema Changes with Database Migrations

Recommended resources

Hello! Welcome back to our journey with Django.

In our last lesson, you successfully defined the data structure for our task manager by creating a Task model in tasks/models.py. That was a crucial step, but right now, that model only exists as Python code. Your database is completely unaware of it.

This lesson bridges that gap. We'll dive into Django Migrations, the powerful system Django uses to translate your Python models into a database schema and keep them synchronized. You'll learn the essential workflow for making your models a reality in the database.

By the end of this lesson, you will be able to: Create and apply database migrations to manage schema changes. This is a fundamental skill you'll use constantly in Django development.

1. What Are Migrations and Why Do We Need Them?

Before we start running commands, let's clarify what migrations are. Think of them as version control for your database schema.

In the previous lesson, we discussed how Django's ORM maps your model classes to database tables. The image below, which we saw before, shows this relationship.

Django Model to Database Schema Mapping
This diagram shows the end goal: a database table that matches our Python model. Migrations are the process that makes this happen automatically.

Migrations are Python files that Django generates. Each file contains the instructions needed to apply changes to the database, like creating a new table or adding a new column.

To understand why this is so important, let's turn to a clear explanation from Real Python.

Django Migrations: A Primer

The article "Django Migrations: A Primer" explains the core problems that migrations solve. Reading this will give you a solid understanding of why this feature is a cornerstone of the framework.

Read the section titled "The Problems That Migrations Solve". Focus on these key benefits: Making Database Changes Without SQL: You can manage your database schema using Python, not raw SQL. Ensuring Synchronization: All developers on a team (and your production server) can have a database schema that perfectly matches the models. Tracking Changes: Since migration files are just code, you can track them in version control (like Git) just like any other part of your project.

In short, migrations automate the tedious and error-prone process of keeping your database schema in sync with your application's code.


2. The Two-Step Migration Workflow

The migration process in Django is a simple, two-step workflow. You will use these two commands constantly.

  1. makemigrations: Scans your models for changes and creates new migration files.
  2. migrate: Applies any unapplied migrations to the database.

Let's apply this to the Task model you created in the last lesson.

Step 1: makemigrations – Creating the Blueprint

First, we need to tell Django to inspect our tasks app, see the new Task model, and create a migration file for it.

Open your terminal, make sure you are in the root directory of your project (where manage.py is), and run the following command:

python manage.py makemigrations tasks

You should see output similar to this:

Migrations for 'tasks':
  tasks/migrations/0001_initial.py
    - Create model Task

What just happened?

  • Django looked at your tasks app.
  • It noticed the Task model exists but there's no migration for it yet.
  • It created a new directory, tasks/migrations/, if it didn't exist.
  • Inside, it generated a new file, 0001_initial.py. This file contains the Python code that describes how to create the Task table in the database.

At this point, your database has not been changed yet. We have only created the blueprint for the change.

Step 2: migrate – Applying the Changes

Now that we have our migration file, we can apply it to the database. Run the following command:

python manage.py migrate

This time, you'll see a lot more output, something like this:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, tasks
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  ... (many more lines for Django's built-in apps) ...
  Applying tasks.0001_initial... OK
  Applying sessions.0001_initial... OK

What just happened?

  • Django looked for any migrations that haven't been applied yet across all of your installed apps.
  • Since this is our first time, it found migrations for Django's built-in apps (admin, auth, etc.) and our new migration for the tasks app.
  • It ran all of them, executing the necessary SQL commands to create all the tables.

Your database now contains a tasks_task table with columns corresponding to the fields you defined in your Task model (id, title, description, completed, created_at). Congratulations, your model is now reflected in the database!

For a quick visual recap of this two-step process, the following video from Net Ninja is excellent.

Django Tutorial #7 - Migrations

Watch these short clips from the "Django Tutorial #7 - Migrations" by Net Ninja. They concisely demonstrate the makemigrations and migrate commands in action.

Watch from 02:28 to 04:37. This covers: Running makemigrations to generate the migration file for a new model. Running migrate to apply that change to the database.

Test your understanding!

What is the key difference between the makemigrations and migrate commands? If you ran makemigrations but forgot to run migrate, would your application be able to save a new Task to the database?

Show answer

The makemigrations command creates the Python files that describe the database changes, but it does not alter the database itself.

The migrate command reads those files and applies the changes to the database.

If you only ran makemigrations, the application would fail if it tried to save a Task because the tasks_task table would not exist in the database yet. You would likely get an OperationalError.


3. Managing Schema Changes

Your application requirements will evolve, and so will your models. This is where the migration system truly shines. Let's say you decide that tasks should have an optional due date.

First, update your model. Open tasks/models.py and add a DateField. Let's also add an ordering Meta class to sort tasks by their creation date by default. Your file should now look like this:




# tasks/models.py

from django.db import models
from django.utils import timezone # Import timezone

class Task(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField(null=True, blank=True)
    completed = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    due_date = models.DateField(null=True, blank=True) # New field

    def __str__(self):
        return self.title

    class Meta:
        ordering = ['created_at'] # New Meta class

Note: We've made due_date optional with null=True, blank=True because our existing tasks in the database won't have a value for it.

Now your model definition is out of sync with your database schema. To fix this, we simply repeat the two-step workflow.

  1. Run makemigrations again:

    python manage.py makemigrations tasks
    

    Django will detect the new due_date field and generate a new migration file:

    Migrations for 'tasks':
      tasks/migrations/0002_task_due_date.py
        - Add field due_date to task
    
  2. Run migrate again:

    python manage.py migrate
    

    Django sees the new, unapplied 0002_... migration and applies it:

    Operations to perform:
      Apply all migrations: admin, auth, contenttypes, sessions, tasks
    Running migrations:
      Applying tasks.0002_task_due_date... OK
    

That's it! Django automatically handled the ALTER TABLE SQL command to add the new due_date column to your tasks_task table. This simple, repeatable process is how you'll manage all future changes to your database schema.


4. Peeking Under the Hood (Optional)

You don't need to know SQL to use Django, but understanding the connection between the ORM and the database is valuable. Django provides commands to inspect the migration process.

  • showmigrations: Lists all migrations in your project and shows which ones have been applied with an [X].
  • sqlmigrate: Shows the actual SQL that a specific migration will run.

Let's see the SQL for the migration we just created. Run this command (you may need to adjust the migration number if yours is different):

python manage.py sqlmigrate tasks 0002

This will output the raw SQL statement, which for SQLite will look something like this:

--
-- Add field due_date to task
--
ALTER TABLE "tasks_task" ADD COLUMN "due_date" date NULL;
COMMIT;

This confirms that Django is simply automating the SQL you would have otherwise had to write manually. The following video provides a great visual walkthrough of these useful commands.

Django - What are migrations - actually? Introduction to migrations and the Django database

This video from Very Academy, "Django - What are migrations - actually?", does an excellent job of exploring what happens behind the scenes. Watching this segment will clarify the link between migrations and SQL.

Watch the following two clips: 23:42 - 25:03: This part introduces the showmigrations command, which helps you see the status of all migrations in your project. 27:48 - 29:08: This part demonstrates the sqlmigrate command, showing you the exact SQL generated from a migration file that alters a table.


Conclusion

You've just mastered one of the most fundamental and powerful features of Django. By understanding and using migrations, you can confidently evolve your application's data structure in a safe, repeatable, and automated way.

Key Takeaways:

  • Migrations are Django's way of keeping your database schema synchronized with your models.py files.
  • The core workflow is a two-step process:
    1. python manage.py makemigrations to generate migration files from your model changes.
    2. python manage.py migrate to apply those changes to the database.
  • This workflow is used both for creating initial tables and for applying subsequent changes (like adding or removing a field).
  • Commands like showmigrations and sqlmigrate can help you inspect and understand the migration process.

Preview of the Next Lesson:
Our Task model is now set up, but most real-world applications have data that relates to other data. For example, a task in our app will eventually need to belong to a specific user. In the next lesson, we'll explore how to create these relationships between models using fields like ForeignKey and ManyToManyField.