Filter concepts by levelShowing all levels.

Django · Settings and Configuration

Core settings

Concepts
3

The settings that wire together what exists (apps, middleware, routing, templates, the database), plus locale, static/media, cache, email, logging, and server-entry settings.

This section

Wiring the project together

Which apps and middleware exist, where routing and rendering start, and how a login is checked.

Routing, rendering, and auth settings

standardintermediate

INSTALLED_APPS, MIDDLEWARE, ROOT_URLCONF, TEMPLATES, and DATABASES wire together which code runs and where data lives; AUTH_PASSWORD_VALIDATORS and AUTHENTICATION_BACKENDS control how a password is judged and how a login is checked.

Think of it as

These seven settings are the project's wiring diagram — INSTALLED_APPS lists which apps exist, MIDDLEWARE lists what wraps every request, ROOT_URLCONF says where routing starts, TEMPLATES and DATABASES say where rendering and storage happen, and the two AUTH_* settings say how a login is judged and checked. Every other setting configures one of these; these seven decide which pieces are even in the room.

python
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
]

What we're doing: Add a custom password validator alongside Django's built-in ones, understanding that order determines which error a user sees first.

settings.pypython
AUTH_PASSWORD_VALIDATORS = [
    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
     "OPTIONS": {"min_length": 10}},
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
    {"NAME": "myapp.validators.NoCompanyNameValidator"},
]
7
A custom validator is just another class implementing validate() — it runs in the position it's listed, after all four built-in ones.

Why this works: Each validator in the list runs independently and every failure is collected — a project can layer a custom rule (like blocking the company name in a password) onto Django's built-in checks without replacing any of them, just by appending to the list.

Listing an app in INSTALLED_APPS after another app that depends on it

Wrong

python
INSTALLED_APPS = [
    "billing",          # references orders.Order via ForeignKey
    "orders",
]
# works today only because billing uses a string reference —
# a direct model import here would risk breaking

Better

python
INSTALLED_APPS = [
    "orders",
    "billing",           # depends on orders — listed after it
]

What you see: No error most of the time (string FK references tolerate any order), but a direct cross-app model import, or a migration dependency, can fail or behave inconsistently depending on list order.

Why: INSTALLED_APPS order is also app-loading order (see App loading order) — while string references are order-independent, other cross-app dependencies (direct imports, some migration dependency graphs) are not, so keeping dependent apps listed after what they depend on avoids relying on luck.

Core routing, rendering, and auth settings

Core routing, rendering, and auth settings
SettingDefaultConfigures
INSTALLED_APPS[] (empty list)which built-in and project apps the registry loads
MIDDLEWARENonethe ordered chain every request/response passes through
ROOT_URLCONFnot set — requireddotted path to the module with urlpatterns
TEMPLATES[] (empty list)template engines, their dirs, and context processors
DATABASES{} (empty dict)database engine, name, host, and credentials — needs a "default" key
AUTH_PASSWORD_VALIDATORS[] (empty list)validator classes run in order when a password is set
AUTHENTICATION_BACKENDS["...ModelBackend"]the ordered list of backends authenticate() tries

Together

python
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "mysite",
        "HOST": "localhost",
    }
}

Remember: INSTALLED_APPS/MIDDLEWARE/ROOT_URLCONF/TEMPLATES/DATABASES wire together what exists and where data lives; AUTH_PASSWORD_VALIDATORS and AUTHENTICATION_BACKENDS are both ordered lists tried in sequence.

See also: settings · middleware · app loading order

Advertisement

Locale and files

Datetime and language defaults, and the URL/ROOT pairs for static and uploaded files.

Locale, static, and media settings

standardbeginner

LANGUAGE_CODE/TIME_ZONE/USE_TZ control locale and datetime handling; STATIC_URL/STATIC_ROOT and MEDIA_URL/MEDIA_ROOT are two matching pairs — the URL prefix the browser sees, and the filesystem directory the files actually live in.

Think of it as

STATIC_ROOT and MEDIA_ROOT are the actual folders on disk; STATIC_URL and MEDIA_URL are the public-facing prefixes that map to them, the same way a company's street address and its public mailing address can differ. Django never confuses the two: URL settings are strings a template writes into HTML, ROOT settings are filesystem paths collectstatic and file uploads actually write to.

python
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_TZ = True

What we're doing: Configure static and media settings so collectstatic and uploaded files land in predictable, separate directories.

settings.pypython
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"   # collectstatic's destination

MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"          # FileField/ImageField's destination
2
STATIC_ROOT only matters in production — collectstatic gathers every app's static/ files here for a web server or CDN to serve directly.
5
MEDIA_ROOT is where an actual uploaded file (e.g. a user's avatar) is written to disk — never served by collectstatic, since it's user content, not project assets.

Why this works: Static and media are kept as two entirely separate URL/ROOT pairs because they have different lifecycles: static files are part of the deployed codebase (versioned, collected at deploy time), while media files are user-generated at runtime — mixing them into one directory would make a deploy's collectstatic step risk overwriting or scanning through user uploads.

Serving MEDIA_ROOT from Django in production the same way static files are served

Wrong

python
# urls.py — fine in development, wrong in production
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# ...but this block was accidentally left unconditional

Better

python
# urls.py
urlpatterns = [...]
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# in production: a real web server or object storage serves MEDIA_ROOT instead

What you see: Django's own process serves every media file request in production — slow, ties up application workers, and skips any CDN or cache the real web server would provide.

Why: django.views.static.serve (which the static() helper wraps) is explicitly documented as insecure and inefficient for anything but local development — leaving it unconditional means every image or upload download blocks a Django worker process instead of a purpose-built static file server or object storage handling it.

Locale, static, and media settings

Locale, static, and media settings
SettingDefaultConfigures
LANGUAGE_CODE"en-us"the default locale for translation and formatting
TIME_ZONE"America/Chicago"the zone datetimes are converted to for display
USE_TZTruewhether datetimes are timezone-aware, stored in UTC
STATIC_URL"static/"URL prefix templates use to reference static files
STATIC_ROOTNonefilesystem directory collectstatic copies files into
MEDIA_URL"" (empty)URL prefix for user-uploaded files
MEDIA_ROOT"" (empty)filesystem directory FileField/ImageField writes to

Together

python
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"

MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"

Remember: STATIC_ROOT/MEDIA_ROOT are real filesystem directories; STATIC_URL/MEDIA_URL are just the public URL prefixes that map to them — never serve MEDIA_ROOT from Django itself in production.

See also: static files · media files · time zone support · internationalization

Advertisement

Cache, mail, logging, and servers

Where hot data lives, how mail is sent, what gets logged, and which callable a server runs.

Cache, email, logging, and server settings

standardintermediate

CACHES configures one or more named cache backends (default: in-process memory); EMAIL_BACKEND picks how mail is actually sent; LOGGING is a full dictConfig; ASGI_APPLICATION/WSGI_APPLICATION point servers at the project's entry-point callable.

Think of it as

These five settings are the project's outward-facing connections — where it stores hot data (CACHES), how it sends mail (EMAIL_BACKEND), what it writes about itself (LOGGING), and which callable a production server actually calls to run it (ASGI_APPLICATION/WSGI_APPLICATION). Change any one and nothing about routing or models has to change — they're orthogonal to the app's own code.

python
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {"console": {"class": "logging.StreamHandler"}},
    "root": {"handlers": ["console"], "level": "INFO"},
}

What we're doing: Switch EMAIL_BACKEND to the console backend for local development, so mail is printed instead of actually sent.

settings/development.pypython
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "dev@example.com"
1
The console backend writes the full email (headers, body) to stdout instead of connecting to any SMTP server — nothing leaves the machine.

Why this works: Django's email API (send_mail(), EmailMessage) is backend-agnostic — application code that sends mail never changes between environments, only EMAIL_BACKEND does, the same separation-of-concerns pattern DATABASES and CACHES already follow.

Leaving CACHES on the default LocMemCache in a multi-worker production deployment

Wrong

python
# settings.py — no CACHES override, so it's LocMemCache by default
# 4 gunicorn workers, each with its own separate in-memory cache

Better

python
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
    }
}
# one shared cache all workers see the same view of

What you see: A value set by cache.set() in one request is invisible to a request served by a different worker process — cache hits appear inconsistent or the cache seems to "not work" under load.

Why: LocMemCache lives entirely inside one Python process's memory — it is not shared across the multiple worker processes a production deployment runs. A shared backend like Redis or Memcached is required the moment more than one process needs to see the same cached values.

Cache, email, logging, and server settings

Cache, email, logging, and server settings
SettingDefaultConfigures
CACHESLocMemCache, in-processone or more named cache backends (Redis, Memcached, ...)
EMAIL_BACKENDSMTP backendhow send_mail() actually delivers — SMTP, console, locmem, file
LOGGINGDjango's built-in configloggers/handlers/formatters, a stdlib dictConfig-shaped dict
ASGI_APPLICATION"<project>.asgi.application"the ASGI callable an async-capable server calls
WSGI_APPLICATION"<project>.wsgi.application"the WSGI callable a synchronous server calls

Together

python
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
    }
}

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"  # dev only

Remember: CACHES defaults to a per-process LocMemCache — switch to Redis/Memcached before running multiple workers; EMAIL_BACKEND and LOGGING are the other two settings worth overriding per environment.

See also: asgi py · wsgi py · settings

Advertisement