skip to content

Why must a Django project set AUTH_USER_MODEL before its first migrate, and what does switching to a custom user model on a live database involve?

level: seniorimportance: should knowfreq 46%

answer

  1. migrations already applied elsewhere
  2. the admin log points at users
  3. dependency on the first migration
  4. reuse the existing table name
  5. record the migration by hand

basics

~20 s

Every foreign key to the user and every migration that depends on it is recorded against the model active at the first migrate. Switching later needs a hand-built transition: new model on the old table, a manually recorded migration, and data and relation fixes.

solid answer

~40 s

At the first `migrate`, `auth.0001_initial` creates `auth_user`, and migrations such as `admin.0001_initial` (the `LogEntry.user` foreign key) are applied with a `swappable_dependency` resolved to the first migration of the `auth` app. If you later point `AUTH_USER_MODEL` at `accounts.User`, that dependency resolves to `accounts.0001_initial`, and `migrate` refuses to run with `InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial`. The schema also still has foreign keys to `auth_user`. The documented position is that it is possible but complex and cannot be automated (ticket 25313). The usual route: create `accounts.User(AbstractUser)` with `db_table = 'auth_user'` in `accounts.0001_initial`, record that migration as applied in `django_migrations` directly, switch the setting, then evolve the model with ordinary migrations and fix permissions and content types.

code

python · 13 lines
python
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    class Meta:
        db_table = 'auth_user'

# accounts/migrations/0001_initial.py creates only this model.
# Record it once per environment, before switching the setting:
#   INSERT INTO django_migrations (app, name, applied)
#   VALUES ('accounts', '0001_initial', CURRENT_TIMESTAMP);
# then in settings.py:
# AUTH_USER_MODEL = 'accounts.User'

go deeper

for a junior

Remember the rule: point AUTH_USER_MODEL at a custom model before the first migrate, even if the model adds nothing yet.

for a middle

Explain why: migrations record foreign keys and swappable dependencies against the user model's app, including admin's LogEntry, so the first migrate fixes the target.

for a senior

Walk through the live switch: adopt auth_user via db_table, record accounts 0001 by hand, switch the setting, then repair content types and permissions in a rehearsed release.

for a principal

Decide whether the switch is worth it at all, weighing a one-to-one profile model against a risky history edit, and set the day-one default for future projects.

## What the first migrate locks in `AUTH_USER_MODEL` is read by migrations, not only by runtime code. When `makemigrations` writes a foreign key to the user it emits `to=settings.AUTH_USER_MODEL` and a dependency `migrations.swappable_dependency(settings.AUTH_USER_MODEL)`, which becomes `(<app label>, '__first__')`: a dependency on the **first migration of whichever app holds the user model**. On a fresh project that is `auth`, so the first `migrate` creates `auth_user`, `auth_user_groups` and `auth_user_user_permissions`, then applies every app whose models point at users, including Django's own `admin.0001_initial` for `LogEntry.user`. The Django docs therefore say to point `AUTH_USER_MODEL` at your model before creating any migrations or running `manage.py migrate` for the first time, and add that the custom model must be created in the **first migration of its app** (usually `0001_initial`), because of how the dynamic dependency is resolved. ## What breaks if you switch later Change the setting on a database that has already been migrated and several things are wrong at once: 1. **Migration history.** The swappable dependency now resolves to `accounts.0001_initial`, which is not applied, while `admin.0001_initial` is. `migrate` runs a consistency check first and stops with `InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial on database 'default'`. Faking with `migrate --fake` hits the same check before it can help. 2. **Schema.** Every existing foreign-key column references `auth_user`. Nothing in Django rewrites those constraints for you. 3. **Data.** Users, password hashes, group memberships and per-user permissions live in `auth_*` tables. 4. **Content types and permissions.** The new model gets its own `ContentType` and its own `add_user`/`change_user`/`delete_user`/`view_user` permissions; groups that held the old ones, and any generic relations keyed by the old content type, still point at `auth.user`. 5. **Code.** Any direct `User` import now raises the swapped-manager `AttributeError`. The docs sum it up as possible but complex, not automatic, and point to ticket 25313 for an outline. ## The common transition The widely used approach avoids moving data by letting the new model adopt the existing table: 1. Create an `accounts` app with `class User(AbstractUser)` and `Meta.db_table = 'auth_user'`. The many-to-many tables are named from the model's `db_table` plus the field name, so they come out as `auth_user_groups` and `auth_user_user_permissions`, matching what exists. 2. Generate `accounts/migrations/0001_initial.py` for exactly that model, with no other changes. 3. Record it as applied by inserting the row (`app='accounts'`, `name='0001_initial'`) into `django_migrations` directly, on every environment, because the consistency check blocks `migrate` itself. 4. Switch `AUTH_USER_MODEL = 'accounts.User'` and replace direct `User` references with the setting or `get_user_model()`. 5. Run `migrate`; history is now consistent and nothing needs to be created. 6. From here on, evolve the model with ordinary migrations: add fields, and optionally rename the table away from `auth_user` later. 7. Repoint groups and generic relations from the old `auth.user` content type and permissions to the new ones, and delete the stale rows when nothing refers to them. Primary keys do not change, so sessions and foreign keys stay valid throughout. ## The alternative routes The table-adoption recipe is not the only option, and interviewers like to hear the trade-off: | Route | Moves data? | Risk | |---|---|---| | Adopt `auth_user` with `db_table` | no | a hand-recorded migration row per environment | | New table, copy rows, repoint foreign keys | yes | every foreign-key constraint rewritten in raw SQL, long locks on large tables | | Reset migrations and rebuild the schema | yes, via dump and reload | only acceptable before real data exists | | Keep `auth.User`, add a one-to-one profile | no | no swap at all, one extra join for profile fields | ## Operational judgement - Rehearse on a copy of production and run the full test suite against the migrated copy. - Ship the table adoption as its own release with no field changes, so a rollback is only a settings revert. - Script step 3 so each environment applies it identically. - If the reason to switch is only extra profile data, compare the cost with a `OneToOneField` profile model, which needs no swap at all. ## The lesson interviewers want | Situation | Cost of a custom user | |---|---| | Before the first migrate | one class and one setting | | After it, live data | a migration-history edit, content-type repair and a careful release | That asymmetry is the whole argument for creating `class User(AbstractUser): pass` on day one, even when you need nothing custom yet.

  • Why can't you just run migrate --fake accounts 0001 after changing the setting?
    `migrate` calls the loader's consistency check before doing anything, including faking. With the new setting, `admin.0001_initial` is applied but its swappable dependency `accounts.0001_initial` is not, so the command raises `InconsistentMigrationHistory` first. The row has to be written into `django_migrations` by hand, or with a script run outside `migrate`.
  • Why does the custom user model have to be in its app's first migration?
    `swappable_dependency()` resolves to `(app_label, '__first__')`, so other apps depend on the user app's first migration only. If the user model appeared in `0002`, a migration pointing at users could run before the table exists. The docs call out this limitation and suggest splitting circular dependencies into a second migration.
  • What happens to existing sessions during the table-adoption switch?
    Sessions store the user's primary key and backend path, not the model class, so with `db_table = 'auth_user'` and unchanged primary keys the logged-in users are loaded from the same rows by the new model. Nobody is logged out by the switch itself.

saying these in an interview costs you the question

  • Changing AUTH_USER_MODEL and running makemigrations handles the switch automatically
  • migrate --fake resolves the InconsistentMigrationHistory error
  • Only foreign keys in your own apps reference the user table
  • The custom user model can be added in any later migration of its app
  • Switching the user model logs out every user because primary keys change