skip to content

In Django, why should code reference settings.AUTH_USER_MODEL or get_user_model() instead of importing the User class directly?

level: juniorimportance: should knowfreq 58%

answer

  1. the user model can be replaced
  2. one setting names the active model
  3. a string for relations, a class for queries
  4. what reusable apps must assume

basics

~10 s

Django's user model is swappable: AUTH_USER_MODEL names whichever model the project uses. Importing auth.User hard-codes the default and breaks once it is swapped, so relations use the setting string and runtime code calls get_user_model().

solid answer

~40 s

Django lets a project replace the built-in `User` by pointing `AUTH_USER_MODEL` (default `'auth.User'`) at its own model. Code that imports `django.contrib.auth.models.User` ignores that setting: in a swapped project its queries raise `AttributeError: Manager isn't available; 'auth.User' has been swapped for 'accounts.User'`, and a `ForeignKey(User, ...)` fails the system check `fields.E301`. The convention is split by timing. In model fields and in `sender=` for signals you pass the string `settings.AUTH_USER_MODEL`, which the app registry resolves lazily. In views, forms, managers, management commands and tests you call `get_user_model()`, which returns the active class. Reusable apps must follow this strictly, because they cannot know which user model the host project picked.

code

python · 17 lines
python
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models


class Invoice(models.Model):
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name='invoices',
    )
    total_cents = models.PositiveIntegerField()


def active_owner_count():
    UserModel = get_user_model()
    return UserModel.objects.filter(is_active=True, invoices__isnull=False).distinct().count()

go deeper

for a junior

Recall the two tools: the settings.AUTH_USER_MODEL string in model relations, get_user_model() when you need the class to query. Say the default value is auth.User.

for a middle

Explain what actually fails when code imports User in a swapped project: the swapped-manager AttributeError on queries and the fields.E301 check on relations.

for a senior

Show you police this in review: a direct User import is a latent bug that only fires when a project customises users, and cached get_user_model() results go stale under override_settings.

for a principal

Frame it as an ecosystem contract: reusable apps depend on every app honouring the swappable setting, which is why app authors must never define or import a concrete user model.

## The user model is swappable Django ships a ready-made user model, `django.contrib.auth.models.User`, but it does not force projects to use it. The setting **`AUTH_USER_MODEL`** names the model that plays the user role. Its default in `django/conf/global_settings.py` is the string `'auth.User'`: an **app label** plus a **model name**, not a Python import path. A project that writes `AUTH_USER_MODEL = 'accounts.User'` has *swapped* the user model; the built-in `User` class carries `swappable = 'AUTH_USER_MODEL'` in its `Meta`, and once swapped it is treated as not installed. That is why the question matters: any line of code that names `User` directly is betting that nobody swapped it. ## What breaks when you import User directly In a project that has swapped the model, direct references fail in two different places: - **Queries.** `User.objects` is guarded by a descriptor that raises `AttributeError: Manager isn't available; 'auth.User' has been swapped for 'accounts.User'`. The failure appears the first time the code path runs, which may be in production if tests never hit it. - **Relations.** A model field such as `models.ForeignKey(User, on_delete=models.CASCADE)` points at a swapped-out model. The system check framework reports `fields.E301` ("Field defines a relation with the model 'auth.User', which has been swapped out") with the hint to point at `settings.AUTH_USER_MODEL`. - **Silent wrong data.** Even where nothing crashes, type checks such as `isinstance(obj, User)` quietly return `False` for the real user objects. In a project that never swapped the model, the direct import happens to work, which is exactly why the mistake survives code review: it is a latent bug that fires only when the project, or a project that installs your app, customises users. ## Two tools, chosen by timing | Where the reference lives | Use | Why | |---|---|---| | `ForeignKey`, `OneToOneField`, `ManyToManyField` targets | `settings.AUTH_USER_MODEL` | a string is resolved lazily once the app registry is ready | | `sender=` when connecting signals to the user model | `settings.AUTH_USER_MODEL` | same lazy resolution, no import needed | | Views, forms, managers, management commands, tests | `get_user_model()` | you need the class itself to query or instantiate | | Type hints and admin registration inside your own project | the concrete class you wrote | you own that model and know it is active | `get_user_model()` lives in `django.contrib.auth`. It calls the app registry with `require_ready=False` and returns the active model class. If the setting is malformed it raises `ImproperlyConfigured` with "AUTH_USER_MODEL must be of the form 'app_label.model_name'"; if it names a model that is not installed it raises `ImproperlyConfigured` saying so. ## Import-time details and the caching trap Because of `require_ready=False`, the documentation allows calling `get_user_model()` while models are being imported, so `models.ForeignKey(get_user_model(), ...)` works. The string form is still the idiomatic choice in model fields: 1. It avoids any dependency on import order between apps. 2. It is what `makemigrations` writes into migrations anyway, as `migrations.swappable_dependency(settings.AUTH_USER_MODEL)` plus `to=settings.AUTH_USER_MODEL`, so the same migration works against whichever user model a project installs. 3. It reads the same in every file, which makes a code search for user references reliable. The trap is caching. A module that does `UserModel = get_user_model()` at import time keeps that class forever. That is fine in production, where the setting never changes, but a test suite that uses `@override_settings(AUTH_USER_MODEL=...)` will keep the stale class unless the module listens to the `setting_changed` signal and refreshes it; the Django docs show exactly this pattern. ## Reusable apps For a third-party or internal reusable app the rule is stricter. The documentation says reusable apps should not define their own custom user model: two apps that each did so could never be installed together, because only one model can be `AUTH_USER_MODEL`. Instead they store per-user data in their own model with a `ForeignKey` or `OneToOneField` to `settings.AUTH_USER_MODEL`, and they fetch the class with `get_user_model()` whenever they need to query it. An app written this way works unchanged whether the host project keeps `auth.User`, extends `AbstractUser`, or builds an email-only model on `AbstractBaseUser`. ## What to say in an interview - The user model is a **setting**, not a fixed class. - **Strings** for relations and signal senders; **`get_user_model()`** for code that runs queries. - Direct imports fail with the *swapped* `AttributeError` or `fields.E301` in any project that customised users. - Reusable apps never define a user model and never import `User`.

  • Can you call get_user_model() at module level in models.py?
    Yes. It resolves the model with `require_ready=False`, and the docs explicitly allow `models.ForeignKey(get_user_model(), ...)` during model import. The string `settings.AUTH_USER_MODEL` is still preferred in fields. The risk with a module-level `UserModel = get_user_model()` is caching: tests that override `AUTH_USER_MODEL` keep the stale class unless you refresh it on the `setting_changed` signal.
  • Why does a migration with a foreign key to the user contain swappable_dependency?
    `migrations.swappable_dependency(settings.AUTH_USER_MODEL)` turns the setting into a dependency on the first migration of whichever app holds the active user model. The migration therefore does not pin `auth`; the same file works in a project that keeps `auth.User` and in one that swapped to `accounts.User`.

saying these in an interview costs you the question

  • Importing User from django.contrib.auth.models is safe in every project
  • settings.AUTH_USER_MODEL holds the model class, so you can query it directly
  • AUTH_USER_MODEL takes a dotted Python path like accounts.models.User
  • Reusable apps should ship their own custom user model
  • get_user_model() may only be called after all apps are fully loaded