In Django, what is the difference between makemigrations and migrate, and how does Django know which migrations a database has already applied?
answer
- one writes files, one runs them
- files are committed with the code
- a table inside each database
- app, name, applied
- a target name can go backwards
basics
~10 smakemigrations 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# 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 0003go deeper
Recall that makemigrations writes files and migrate applies them, and that django_migrations records what ran in each database.
Explain dependencies and operations, the migration graph, and how migrate plans forwards and backwards moves to a named target.
Treat migration files as reviewed code, never edit applied ones, and use showmigrations, --plan and sqlmigrate before touching production.
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.