~/TechPurAI
~/tutorials/django-from-scratch/migrations
beginner·part 6 of 22·2 min read

Migrations: turning models into database tables

Updated Aug 15, 2026Python · Django

The Post model from part 5 is just a Python class until a migration turns it into an actual database table. Migrations are Django's version-controlled history of every schema change a project has ever made — generated from your models, applied to the database, and committed to git like any other code.

Generating a migration

bash
python manage.py makemigrations blog
text
Migrations for 'blog':
  blog/migrations/0001_initial.py
    - Create model Post

makemigrations compares the current state of models.py against the last migration and writes a new file describing the difference — it doesn't touch the database at all yet. Run it every time a model changes: add a field, remove one, rename one, each gets its own migration.

Reading the migration file

python
# blog/migrations/0001_initial.py
from django.db import migrations, models
from django.conf import settings

class Migration(migrations.Migration):
    initial = True
    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]
    operations = [
        migrations.CreateModel(
            name="Post",
            fields=[
                ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
                ("title", models.CharField(max_length=200)),
                ("slug", models.SlugField(max_length=200, unique=True)),
                ("content", models.TextField()),
                ("published", models.BooleanField(default=False)),
                ("created_at", models.DateTimeField(auto_now_add=True)),
                ("updated_at", models.DateTimeField(auto_now=True)),
                ("author", models.ForeignKey(
                    on_delete=models.deletion.CASCADE, to=settings.AUTH_USER_MODEL,
                )),
            ],
            options={"ordering": ["-created_at"]},
        ),
    ]

Every field from Post in part 5 has a corresponding entry here, and dependencies records that this migration needs the user model's migration applied first — since author is a ForeignKey to it. This file is meant to be read, not just generated and forgotten: it's the actual record of what the database looked like at this point in the project's history.

Applying the migration

bash
python manage.py migrate
text
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0001_initial... OK

migrate is the step that actually touches the database — it runs every unapplied migration, in dependency order, including the built-in ones from admin/auth/sessions that were waiting since part 1. By default Django projects use SQLite (a single db.sqlite3 file, zero setup), which is exactly right for development and this series; a real deployment (part 22) typically switches to PostgreSQL.

Common mistake

Editing a migration file by hand instead of changing the model and running makemigrations again. The migration is generated output — if it's wrong, the fix is almost always in models.py, then regenerate.

The two-step habit

makemigrations then migrate — always both, always in that order, every time a model changes. Skipping migrate after makemigrations is the single most common way to see "no such column" errors on a model that looks completely correct in models.py.

Next: the Django admin — a full interface for managing this data, with zero HTML written by hand.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous5. Models and the ORM: defining your datanext →7. The Django admin: managing data without building a UI for it