Django core — request cycle, middleware, settings
Questions in this set 11
- 01Walk me through the Django request/response cycle.
- 02Explain middleware ordering with a real consequence.
- 03Function-based views vs class-based views vs generic views.
- 04What is CSRF, and how does Django protect against it?
- 05When should you use signals, and when should you not?
- 06How do you structure a Django project for a real team?
- 07What are the production settings you must get right?
- 08How does Django's authentication system work, and how do you customise the user model?
- 09How do you serve static and media files in production?
- 10What is the N+1 problem in a Django template, and how do you catch it?
- 11How would you deploy Django, and what runs where?
Walk me through the Django request/response cycle.
- WSGI/ASGI server (Gunicorn/uvicorn) hands the request to Django's handler.
- Middleware
__call__runs top-to-bottom fromMIDDLEWARE. - URL resolution —
ROOT_URLCONF, matchingpath()/re_path()in order, capturing kwargs. process_viewhooks, then the view runs (CBVdispatch→get/post).- The view returns a
HttpResponse(rendering a template lazily viaTemplateResponse). - Middleware runs bottom-to-top on the way out;
process_exceptionandprocess_template_responsefire if applicable. - The response is serialised to the client;
request_finishedcloses DB connections (subject toCONN_MAX_AGE).
The detail that shows you have debugged it: middleware is an onion. Order matters, and something placed after AuthenticationMiddleware sees request.user, while something before it does not.
Explain middleware ordering with a real consequence.
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", # must precede Auth
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", # must precede Auth for login CSRF
"django.contrib.auth.middleware.AuthenticationMiddleware",# sets request.user (lazily)
"django.contrib.messages.middleware.MessageMiddleware", # needs sessions
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]AuthenticationMiddleware reads the session, so SessionMiddleware must come first — otherwise request.user raises. Gzip middleware must be near the top to compress everything below it. A caching middleware placed above authentication will happily serve one user's page to another — that is the incident this question is really about.
Writing one:
class RequestIDMiddleware:
def __init__(self, get_response): self.get_response = get_response # once, at startup
def __call__(self, request):
request.id = request.headers.get("X-Request-ID") or uuid4().hex # before the view
response = self.get_response(request)
response["X-Request-ID"] = request.id # after the view
return responseFunction-based views vs class-based views vs generic views.
FBVs are explicit and obvious — best for anything with unusual logic. CBVs give you inheritance and mixins (LoginRequiredMixin, PermissionRequiredMixin) and remove boilerplate for standard CRUD. Generic CBVs (ListView, CreateView) are excellent until you need something they did not anticipate, at which point you are reading Django's source to find which of eleven methods to override.
Practical guidance to give: use generic CBVs for genuine CRUD, FBVs for everything bespoke, and never build a deep custom CBV hierarchy — the indirection costs more than the boilerplate it saves.
What is CSRF, and how does Django protect against it?
CSRF is an attacker's site causing the user's browser to send an authenticated request to yours (cookies ride along automatically). Django issues a secret in a cookie and requires a matching value in a form field or the X-CSRFToken header on unsafe methods; an attacker's page cannot read your cookie (same-origin policy), so it cannot produce the header.
Practical notes: {% csrf_token %} in every POST form; for a JS client, read the cookie and send the header; @csrf_exempt only for endpoints authenticated by something other than cookies (a webhook with a signature, an API with a bearer token); set CSRF_TRUSTED_ORIGINS when behind a proxy on a different host; SameSite=Lax cookies are defence in depth, not a replacement.
When should you use signals, and when should you not?
Signals (post_save, pre_delete, m2m_changed) decouple side effects from the code that triggers them — genuinely useful for cross-app concerns you do not own, and for reusable packages.
Reasons to avoid them for your own code: control flow becomes invisible (nothing at the call site says a handler will run), ordering is undefined, testing gets harder, and post_save fires inside the transaction, so enqueueing a Celery task there can run the task before the commit — the worker then reads a row that does not exist yet. The fix is transaction.on_commit(lambda: task.delay(pk)). Also note bulk_create/bulk_update/queryset.update() do not send signals — silently skipping your side effects.
Prefer an explicit service function that does the write and its consequences together.
How do you structure a Django project for a real team?
config/settings/{base,local,production}.py # split settings, env-driven
apps/
users/ models.py services.py selectors.py api.py tasks.py tests/
billing/ …Principles: apps are bounded contexts, not layers; put business logic in service functions (create_order(user, items)) rather than fat views or overloaded models; keep read logic in selectors; never import across apps' internals — go through their public functions. Settings come from environment variables (django-environ or os.environ), with DEBUG=False, a real SECRET_KEY, and ALLOWED_HOSTS locked down in production.
What are the production settings you must get right?
DEBUG = False # DEBUG=True leaks settings and SQL on every error page
SECRET_KEY = env("SECRET_KEY") # rotating it invalidates sessions and password-reset links
ALLOWED_HOSTS = ["example.com"]
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # only behind a trusted proxy
DATABASES["default"]["CONN_MAX_AGE"] = 60 # persistent connections; 0 opens one per requestRun python manage.py check --deploy — it audits exactly this list, and mentioning it is a quick credibility win.
How does Django's authentication system work, and how do you customise the user model?
AUTHENTICATION_BACKENDS are tried in order; the default checks username/password against the hashed value (PBKDF2 by default; Argon2 if installed). login() puts the user id in the session; AuthenticationMiddleware restores request.user lazily.
Custom user: do it in the first migration of a new project. Swapping AUTH_USER_MODEL later is genuinely painful because every FK points at the old table.
class User(AbstractUser): # keeps username/password/permissions plumbing
email = models.EmailField(unique=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
# AbstractBaseUser if you want to define permissions from scratch.Always reference the user model as settings.AUTH_USER_MODEL in FKs and get_user_model() in code — never import User directly.
How do you serve static and media files in production?
Static files are yours (CSS/JS); media files are user uploads. collectstatic gathers static files into STATIC_ROOT; serve them from WhiteNoise (with ManifestStaticFilesStorage for hashed, far-future-cacheable names) or a CDN/S3. Media goes to object storage (S3/R2 via django-storages) with signed URLs for private files. Django's static() dev helper must never serve files in production — it is unoptimised and only active with DEBUG=True.
What is the N+1 problem in a Django template, and how do you catch it?
A template loop that touches order.customer.name issues one query per row because the related object was not fetched. It is invisible in code review because the template looks innocent. Catch it with django-debug-toolbar in development (it shows duplicate queries), assertNumQueries in tests for critical views, and query-count logging or APM in production. Fix with select_related/prefetch_related — covered in depth in the Django ORM track.
How would you deploy Django, and what runs where?
Gunicorn (sync workers, or gthread) or uvicorn for ASGI, behind nginx or a cloud load balancer that terminates TLS. Workers = roughly 2 * cores + 1 for sync, tuned by measurement; a separate Celery worker deployment plus one beat scheduler; PostgreSQL with PgBouncer if connection counts are high; Redis for cache and broker. Migrations run as a separate release step before the new code rolls out, and must be backwards-compatible with the currently-running version (add columns nullable, deploy code, backfill, then make them non-null in a later release). Health checks hit a view that verifies the DB, logs go to stdout as JSON, and secrets come from the environment or a secret manager.