The User model, and swapping it for a custom one
coreintermediatedjango.contrib.auth.models.User is the default user model — username, email, password (hashed), first_name/last_name, is_staff/is_superuser/is_active, and groups/permissions. AUTH_USER_MODEL in settings.py points at whichever user model is actually in use; swapping it to a custom model (extending AbstractUser for a mostly-default shape, or AbstractBaseUser for a from-scratch one, e.g. email-only login) must happen BEFORE the first migration, since every other app's ForeignKey(User) actually resolves through this setting.
Think of it as
Django never hardcodes "the user model is auth.User" anywhere in its own code — every internal reference (ForeignKey to a user, request.user, the authentication system) goes through django.conf.settings.AUTH_USER_MODEL and get_user_model(), specifically so a project CAN swap in a custom model. The trap is that this indirection only helps if it's used from the start: the first migration that creates auth-related tables bakes in whichever model AUTH_USER_MODEL pointed at that day, and every other app's ForeignKey(User) — if written as a direct import rather than settings.AUTH_USER_MODEL — resolves against that same baked-in model. Swapping later means either a genuinely painful data migration across every table with a user foreign key, or starting the database over — which is why Django's own docs are blunt that this is a decision to make BEFORE the first migrate, not something to defer.
What we're doing: Add a custom user model with email as the login identifier, before any migrations exist.
- 2
- unique=True on email is required once it becomes USERNAME_FIELD — Django needs one field that uniquely identifies a user for login.
- 3
- USERNAME_FIELD tells the auth system which field to treat as the login identifier — it does not have to be literally called "username".
Why this works: A project whose users log in with email, not a separate username, should say so explicitly via USERNAME_FIELD rather than keeping an unused username field around purely because AbstractUser happens to define one.
Building out a project for months on the default User, then trying to swap in a custom model later
Wrong
Better
What you see: Every existing ForeignKey(User) across every app now points at the wrong model; migration state is fundamentally split between "what auth.User's migrations already created" and "what the new custom model needs" — commonly resolved only by a full database reset, unacceptable once real data exists.
Why: Django's own documentation states this directly: changing AUTH_USER_MODEL after tables have already been created for the default (or another) user model is not a supported operation to do casually — the recommended practice is to start every new project with a custom user model from its very first migration, even one that adds nothing yet, purely to keep the option open cheaply.
- settings.py
- AUTH_USER_MODEL — "accounts.User"
- Django internals
- get_user_model()
- ForeignKey(settings.AUTH_USER_MODEL)
- accounts/models.py
- class User(AbstractUser)
AbstractUser vs AbstractBaseUser
Together
Remember: AUTH_USER_MODEL + get_user_model() is the indirection Django uses everywhere internally — never import User directly. Decide on a custom user model (AbstractUser for default-shape-plus-fields, AbstractBaseUser for a from-scratch shape) BEFORE the first migration; swapping later is not a safe, casual operation.
See also: login logout and authentication backends · passwords staff and permissions · modeladmin basics

