skip to content

Migration Workflows

Django migrations record model changes as files that migrate applies; data scripts, branch conflicts, squashing and lock-free changes are daily work. Interviewers ask how you merge two branches.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

24

In Django, what is the difference between makemigrations and migrate, and how does Django know which migrations a database has already applied?

level: juniorimportance: must knowfreq 82%

answer

  1. one writes files, one runs them
  2. files are committed with the code
  3. a table inside each database
  4. app, name, applied
  5. a target name can go backwards

basics

~10 s

makemigrations writes migration files describing model changes; migrate runs unapplied migration files against a database. Each applied migration is recorded as an (app, name, applied) row in that database's django_migrations table.

solid answer

~40 s

`makemigrations` compares your models with the state described by the existing migration files and writes new files, each a `Migration` class with `dependencies` and `operations` such as `AddField`; it changes no tables. `migrate` loads the migration graph, reads the `django_migrations` table of the target database, and applies every migration not recorded there, in dependency order, adding a row with `app`, `name` and `applied` for each. The files are source code that you review and commit; the table is per database, so every environment tracks its own progress. `migrate workouts 0003` moves an app to a specific migration, unapplying later ones if needed, and `migrate workouts zero` unapplies all of them.

code

bash · 16 lines
bash
# after editing workouts/models.py
python manage.py makemigrations workouts
# Migrations for 'workouts':
#   workouts/migrations/0004_workout_avg_heart_rate.py
#     + Add field avg_heart_rate to workout

python manage.py showmigrations workouts
#  [X] 0001_initial
#  [X] 0002_workout_sport
#  [X] 0003_workout_duration
#  [ ] 0004_workout_avg_heart_rate

python manage.py migrate workouts

# go back to 0003 (drops the column again)
python manage.py migrate workouts 0003

go deeper

for a junior

Recall that makemigrations writes files and migrate applies them, and that django_migrations records what ran in each database.

for a middle

Explain dependencies and operations, the migration graph, and how migrate plans forwards and backwards moves to a named target.

for a senior

Treat migration files as reviewed code, never edit applied ones, and use showmigrations, --plan and sqlmigrate before touching production.

for a principal

Set team rules for who writes migrations, how they are reviewed and when they are applied, so every environment's history stays consistent.

## Two commands, two jobs Django splits schema changes into a **design-time** step and a **run-time** step: | | `makemigrations` | `migrate` | |---|---|---| | Input | your model classes and existing migration files | migration files and the database | | Output | new Python files in `<app>/migrations/` | schema changes plus rows in `django_migrations` | | Touches tables? | no | yes | | Run where? | on a developer machine, then committed | in every environment: local, CI, staging, production | | Typical flags | `--check`, `--dry-run`, `--name`, `--empty` | `--plan`, `--check`, `--fake`, `--database` | A common junior mistake is to treat them as one step, or to run `makemigrations` on the server. Migration files are **source code**: they are generated once, reviewed, committed, and then applied everywhere by `migrate`. ## What a migration file holds In a fitness tracker, adding an average heart rate to workouts produces a file like `workouts/migrations/0004_workout_avg_heart_rate.py`: ```python from django.db import migrations, models class Migration(migrations.Migration): dependencies = [('workouts', '0003_workout_duration')] operations = [ migrations.AddField( model_name='workout', name='avg_heart_rate', field=models.PositiveSmallIntegerField(blank=True, null=True), ), ] ``` - **`dependencies`** lists migrations that must be applied first, in this app or others; together the files form a graph. - **`operations`** are declarative steps: `CreateModel`, `AddField`, `AlterField`, `RemoveField`, `RenameField`, `AddIndex`, `AddConstraint` and so on. - Optional attributes include `initial = True` on an app's first migration, `replaces` for squashed migrations, `run_before` and `atomic`. - The name comes from the operations (`workout_avg_heart_rate`), `initial` for the first one, or `auto_<timestamp>` as a fallback; `--name` overrides it. ## How migrate knows what has run Every database has a table called **`django_migrations`** with three columns: `app`, `name` and `applied` (a timestamp). `migrate` creates it on first use. On each run it: 1. Loads all migration files and builds the dependency graph. 2. Reads which `(app, name)` pairs are already recorded in this database. 3. Plans the unapplied migrations in dependency order. 4. Runs each migration's operations and inserts its row. Because the record lives in the database, a laptop, the CI database and production each keep their own history. Nothing is stored in the files about whether they ran. ## Moving to a specific migration `migrate` accepts an app label and a migration name: - `python manage.py migrate` applies everything unapplied in all apps. - `python manage.py migrate workouts` applies the `workouts` app up to its latest migration, plus any other apps it depends on. - `python manage.py migrate workouts 0003` brings the app to exactly `0003`: if `0004` and `0005` are applied, they are **unapplied** in reverse order, and so is any migration in another app that depends on them. A unique prefix like `0003` is enough. - `python manage.py migrate workouts zero` unapplies every migration in the app. Unapplying runs each operation's reverse, so `AddField` becomes a column drop and data in that column is lost. Run `migrate --plan` first to see exactly what will happen. ## Checking the state - `showmigrations` lists each app's migrations with `[X]` for applied and `[ ]` for unapplied; `--plan` shows them in the order `migrate` would use. - `sqlmigrate workouts 0004` prints the SQL that migration would run on the current database backend, without running it. - `migrate` with nothing to do prints *No migrations to apply.* and, if models have changed without a migration, a notice telling you to run `makemigrations`. ## Mistakes interviewers listen for - **Running `makemigrations` on a server.** Each environment would then invent its own files, and nobody reviews what they do to the data. - **Editing a migration that has already been applied somewhere.** The record in `django_migrations` only stores the name, so the edit never reaches databases that ran the old version. - **Deleting migration files to 'start fresh'** once other databases have applied them. Their history tables then name migrations that no longer exist; `migrate --prune` only removes such rows, it does not rebuild the schema. - **Forgetting to commit the files**, or the `migrations/__init__.py` that makes the folder a package. For an app without a migrations folder yet, `makemigrations <app_label>` creates it. - **Assuming `migrate` is instant.** It runs real DDL, and on a large table an operation can lock writes for a long time, which is why `--plan` and `sqlmigrate` come before it in production.

  • Two developers' laptops both show 0004 as applied but have different columns; how can that be?
    Each database keeps its own `django_migrations` rows, and the rows only say a migration with that name ran. If one developer edited `0004` after applying it, or two branches each created a different `0004`, the names match while the operations differed. Never edit an applied migration; create a new one, and resolve same-numbered files from parallel branches before merging.
  • Why should migration files be committed rather than generated on the server during deploy?
    They are code that decides what happens to production data, so they need review. Generating them on the server means each environment could produce different operations, renames could be misread as a drop and an add without anyone answering the prompt, and the next `makemigrations` on a laptop would conflict with files nobody has seen.

saying these in an interview costs you the question

  • makemigrations changes the database tables directly.
  • migrate regenerates migration files from the current models.
  • Django remembers applied migrations in a flag inside each migration file.
  • Migration files are build output and should be gitignored.
  • migrate with an older migration name only applies that one migration again.
open as a page

After merging two branches that each added an orders migration 0042, why does Django's migrate report multiple leaf nodes, and how do you fix it?

level: juniorimportance: must knowfreq 48%

basics

~20 s

Both 0042 files depend on 0041, so the orders app's migration graph ends in two leaf nodes and Django refuses to guess their order. Run makemigrations --merge to add a migration depending on both, after checking they touch different fields.

open as a page

In Django, what do the dumpdata and loaddata management commands do, and where does loaddata look for fixture files?

level: juniorimportance: must knowfreq 55%

basics

~20 s

dumpdata writes database rows out as a fixture (JSON by default; XML, JSONL or YAML on request) and loaddata reads fixtures back in one transaction, searching each installed app's fixtures directory, then FIXTURE_DIRS, then the current directory.

open as a page

In Django, how do you write a data migration that fills new first_name and last_name columns from an existing full_name column?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Create an empty migration with makemigrations --empty, write a function taking (apps, schema_editor) that loads the model via apps.get_model and fills the new columns, and add it to operations as migrations.RunPython, ideally with a reverse function.

open as a page

In a Django data migration, why must RunPython code load models with apps.get_model() instead of importing them from models.py?

level: middleimportance: must knowfreq 58%

basics

~20 s

apps.get_model() returns the historical model rebuilt from the migrations up to that point; an imported model is today's class, so replaying old migrations on a fresh database breaks once its fields no longer match that step's schema.

open as a page

In Django, how do you rename a model field live when the previous release keeps querying the old column during the deploy?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Avoid RenameField's RENAME COLUMN while old code runs. Either rename only the Python attribute and keep db_column pointing at the old column, or change the column across releases: add, write both, backfill, switch reads, then drop.

open as a page

In Django, how would you add a NOT NULL column to a 50-million-row events table without downtime, and why does db_default matter?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Declare the field with db_default so AddField leaves a DEFAULT on the column. With only a Python default, Django drops the column default after filling existing rows, and the old release's INSERTs, which omit the column, then fail.

open as a page

In Django, what do migrate --fake and migrate --fake-initial do, and when is each safe to use?

level: middleimportance: should knowfreq 45%

basics

~20 s

migrate --fake records migrations as applied in django_migrations without running their SQL. --fake-initial fakes an app's initial migration only when its CreateModel tables and AddField columns already exist. Both are safe only when the schema really matches the migrations.

open as a page

How does Django's makemigrations work out what changed in your models, and why doesn't it compare against the database schema?

level: middleimportance: should knowfreq 50%

basics

~20 s

makemigrations rebuilds a project state by replaying the operations in existing migration files, builds a second state from the current models, and lets the autodetector diff the two. The database schema is never read, so manual schema changes go unnoticed.

open as a page

When is committing Django's makemigrations --merge output unsafe, and what does the merge migration not do for you?

level: middleimportance: should knowfreq 32%

basics

~20 s

A merge migration only joins the graph; it reconciles nothing. If both branches change the same field or model, databases can apply them in different orders and end with different schemas, so edit the migrations or add an explicit fix-up instead.

open as a page

In Django serialization, what are natural_key() and get_by_natural_key(), and why would a fixture use them instead of primary keys?

level: middleimportance: should knowfreq 42%

basics

~20 s

natural_key() on a model returns a tuple of unique business values; get_by_natural_key() on its default manager finds the row from that tuple. Fixtures then reference rows by stable values like an ISO code instead of database-specific ids.

open as a page

When Django's loaddata saves fixture objects, why should post_save receivers check raw=True, and what else does a raw save skip?

level: middleimportance: should knowfreq 38%

basics

~20 s

loaddata saves each object with raw=True: the model's save() override is bypassed and pre_save/post_save receivers get raw=True, because related rows may not be loaded yet and derived rows are usually already in the fixture. Receivers should return early.

open as a page

In Django, what happens when you unapply a migration whose RunPython has no reverse_code, and when is RunPython.noop the right reverse?

level: middleimportance: should knowfreq 42%

basics

~20 s

Django raises IrreversibleError before undoing any of that migration's operations, so the rollback stops there. RunPython.noop is right when leaving the forward change in place is harmless on the way back; otherwise write a real reverse function.

open as a page

In a Django migration, when would you use RunSQL instead of RunPython, and what do its reverse_sql and state_operations arguments do?

level: middleimportance: should knowfreq 35%

basics

~10 s

RunSQL runs raw SQL: use it for set-based updates or database features Django does not model. reverse_sql is the SQL run on unapply; state_operations tells Django's project state what schema the SQL created.

open as a page

What does Django's SeparateDatabaseAndState migration operation do, and when would you reach for it?

level: middleimportance: should knowfreq 30%

basics

~20 s

SeparateDatabaseAndState takes two lists: state_operations change Django's model state, database_operations change the real schema. It lets you change one without the other, such as removing a field from the models a release before dropping its column.

open as a page

A Django fitness tracker adds avg_heart_rate to Workout and production then fails with a missing column; how do makemigrations --check, migrate --check, showmigrations and sqlmigrate catch or diagnose it?

level: seniorimportance: should knowfreq 48%

basics

~20 s

A missing column means the migration was never written or never applied. makemigrations --check fails CI when a model change lacks a migration; migrate --check fails when unapplied migrations exist; showmigrations and sqlmigrate show which migration is missing and what SQL it runs.

open as a page

In a Django team, when would you renumber one of two conflicting 0042 migrations onto the other instead of committing a merge migration?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Renumber, making one migration 0043 that depends on the other 0042, when it has only run on disposable local databases; history stays linear. Once both ran on shared databases, merge instead: a renamed file looks unapplied and Django would run it again.

open as a page

How does Django's squashmigrations work, and when is it safe to delete the migration files a squashed migration replaces?

level: seniorimportance: should knowfreq 40%

basics

~20 s

squashmigrations concatenates an app's migrations, optimizes the operations and writes one migration whose replaces lists the originals. Keep the old files until every database has applied them all; then delete them, repoint dependencies and remove replaces.

open as a page

Your Django project must ship country and currency reference rows to every environment, production included: should they come from a fixture or a data migration, and why?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Use a data migration: migrate applies it automatically, once, in order, against the historical model state, including test databases. A fixture only loads when someone runs loaddata, uses today's models, and overwrites rows by primary key.

open as a page

On PostgreSQL, one Django migration adds first_name and last_name and then backfills 20 million customers with RunPython; why is that risky, and how do Migration.atomic and RunPython's atomic argument help?

level: seniorimportance: should knowfreq 38%

basics

~20 s

On PostgreSQL the whole migration is one transaction, so the table stays locked by the ALTER while the backfill runs and everything rolls back together. Split schema and data; for huge tables set Migration.atomic = False and commit batches yourself.

open as a page

In Django on PostgreSQL, how do you add an index to a 50-million-row events table without blocking writes?

level: seniorimportance: should knowfreq 36%

basics

~10 s

Replace the generated AddIndex with AddIndexConcurrently from django.contrib.postgres.operations and set atomic = False on the migration, because CREATE INDEX CONCURRENTLY cannot run in a transaction. RemoveIndexConcurrently does the same for dropping.

open as a page

What migration policy would you set for a Django team deploying continuously, so every migration stays safe while the previous release still serves traffic?

level: principalimportance: should knowfreq 25%

basics

~20 s

Require every migration to work with both the new and the previous release: additive changes ship with the code, destructive steps a release later, indexes built concurrently, backfills in separate data migrations, and each migration's SQL reviewed with sqlmigrate.

open as a page

What does Django's django.core.serializers module provide, and how does it differ from a Django REST Framework serializer?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

django.core.serializers turns model instances into Django's fixture format (JSON, JSONL, XML, YAML) and back, powering dumpdata and loaddata. It has a fixed model/pk/fields shape and no validation, unlike DRF serializers, which define API representations and validate input.

open as a page

When Django's squashmigrations meets RunPython operations, why does it report manual porting, and how does elidable=True change the squashed result?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

The squashed file references RunPython functions defined in the old numbered migration modules, which you will delete, so you copy them in by hand. Non-elidable RunPython and RunSQL also block the optimizer; elidable ones may be dropped.

open as a page