Guide

Django fundamentals explained

Django is a high-level Python web framework built around the principle of “batteries included.” Where microframeworks hand you routing and little else, Django ships an ORM, migration system, admin interface, authentication stack, form handling, template engine, and security middleware in one coherent toolkit. Teams reach for Django when they need a full-stack monolith — content sites, internal tools, SaaS dashboards, marketplaces — and want conventions that scale with headcount rather than reinventing auth and CRUD on every project. This guide covers MTV architecture and project layout, models and the ORM, views and URL routing, templates and forms, the admin and auth systems, Django REST Framework for APIs, a Harbor Supply inventory portal worked example, a framework decision table, pitfalls, and a checklist — alongside our Python fundamentals guide, PostgreSQL fundamentals overview, and FastAPI fundamentals guide.

What Django is and how requests flow

Django follows the MTV pattern (Model–Template–View), its variant of MVC. A model defines database tables and business rules. A view is a Python callable that processes a request and returns a response. A template renders HTML (or other formats) with context data. URL routing maps paths to views; middleware wraps the request/response cycle for cross-cutting concerns like sessions, CSRF protection, and security headers.

A typical request enters through WSGI or ASGI (Django 3.0+), passes through middleware in order, hits URL resolution in urls.py, executes the matched view, and returns a response — often rendered HTML, sometimes JSON when using Django REST Framework. The framework encourages apps — reusable modules (blog, accounts, billing) that bundle models, views, templates, and migrations under one namespace.

Core concepts

  • Project vs app — the project holds settings and root URLs; apps are installable feature modules listed in INSTALLED_APPS.
  • Model — a Python class subclassing models.Model; fields map to SQL columns; Meta controls table name, ordering, constraints.
  • Migration — versioned schema change files generated by makemigrations and applied by migrate.
  • QuerySet — lazy, chainable database queries; evaluated only when iterated or forced.
  • View — function-based (def) or class-based (View, ListView, etc.) handler returning HttpResponse.
  • Admin — auto-generated CRUD UI for staff users, customized via ModelAdmin classes.

Project structure and settings

django-admin startproject harbor_supply creates a top-level package with settings.py, urls.py, wsgi.py, and asgi.py. Each feature gets its own app: python manage.py startapp inventory. Production projects split settings into base/dev/prod modules, load secrets from environment variables (never commit SECRET_KEY or database passwords), and pin dependencies in a lockfile.

Key settings to understand early: DATABASES (PostgreSQL in production, SQLite acceptable for local dev), MIDDLEWARE order (security middleware must run first), STATIC_URL and MEDIA_ROOT for asset serving, ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS for host validation, and AUTH_USER_MODEL if you need a custom user table — set this before the first migration.

URL routing

Root urls.py includes app routes with include(). Named URL patterns (name='product-detail') let templates and views reverse URLs without hardcoding paths. Path converters (<int:pk>, <slug:sku>) validate segments before the view runs.

Models, ORM, and migrations

Django’s ORM translates Python to SQL across PostgreSQL, MySQL, SQLite, and Oracle. Define fields with types (CharField, DecimalField, ForeignKey, ManyToManyField), add unique=True or db_index=True where queries demand it, and use Meta.constraints for composite uniqueness. Relationships use on_delete policies — CASCADE for owned child rows, PROTECT when deletion must be blocked.

QuerySet patterns

  • Product.objects.filter(stock__lt=10) — low-stock alert query.
  • select_related('supplier') — JOIN for ForeignKey; one query instead of N+1.
  • prefetch_related('tags') — separate query for ManyToMany; still beats per-row fetches.
  • aggregate(Sum('quantity')) / annotate(Count('orders')) — SQL aggregation in Python.
  • F('price') * F('quantity') — database-side arithmetic without race-prone read-modify-write.

Run python manage.py makemigrations after model changes; migrate applies them. Never edit applied migration files in production — create a new migration instead. For zero-downtime deploys on large tables, use multi-step migrations: add nullable column, backfill in a management command, then add NOT NULL in a follow-up migration.

Views, templates, and forms

Function-based views are explicit and easy to test. Class-based views (CBVs) bundle common patterns: ListView paginates querysets, DetailView fetches by primary key, CreateView and UpdateView wire forms to models. Mixins add login requirements (LoginRequiredMixin) or permission checks without duplicating decorators.

Django’s template language deliberately limits logic — filters and tags only, no arbitrary Python. Templates inherit via {% extends %} and {% block %}, keeping layout DRY. For interactive pages, forms (forms.Form or forms.ModelForm) validate POST data server-side; ModelForm generates fields from model metadata and saves with form.save(). Always validate on the server even if the browser has client-side checks.

Security defaults

Django enables CSRF tokens on POST forms, sets secure cookie flags when configured, escapes template output by default, and provides @login_required and permission decorators. SQL injection is prevented by parameterized queries through the ORM — raw SQL with string interpolation is the escape hatch that causes incidents.

Admin, authentication, and Django REST Framework

Register models in admin.py to give operations staff a polished CRUD interface without building custom pages. Customize list displays, filters, search fields, inline related objects, and read-only fields. The admin is not a customer-facing UI — restrict it to staff (is_staff=True) and protect it behind VPN or IP allowlists in production.

Django’s auth system handles users, groups, and permissions. django.contrib.auth provides login/logout views, password hashing (PBKDF2 by default), and session management. For APIs, Django REST Framework (DRF) adds serializers (request/response validation), viewsets, routers, pagination, throttling, and browsable API docs. DRF is the standard choice when a Django monolith also serves JSON to mobile apps or SPAs — pair with token or JWT auth rather than session cookies for pure API clients.

Worked example: Harbor Supply inventory portal

Harbor Supply runs a wholesale parts catalog. Operations need a web portal where warehouse staff adjust stock levels, buyers search by SKU, and managers export reorder reports — without paying for a separate ERP seat per user.

Data model

An inventory app defines Supplier, Product (SKU, name, unit price, reorder threshold), and StockMovement (quantity delta, reason, timestamp, user FK). Product.supplier is a ForeignKey with select_related on list views. Movements append-only; stock is derived from the sum of movements or cached on Product.on_hand updated in a transaction.

Views and admin

Staff use the admin for bulk imports via django-import-export. Buyers get a ListView with search on SKU and name, paginated at 50 rows. A StockAdjustmentForm (ModelForm on StockMovement) records adjustments with audit trail. Managers trigger a nightly management command that emails a CSV of products below reorder threshold — scheduled via cron or Celery beat.

API layer

A read-only DRF ProductViewSet exposes JSON for the Harbor mobile scanner app. Serializers exclude cost fields buyers should not see. IsAuthenticated plus group-based permissions gate write endpoints. OpenAPI schema is generated for the partner team’s TypeScript client.

Deployed behind Gunicorn + nginx on PostgreSQL, with static files on S3 via django-storages. Migrations run in CI before deploy; smoke tests hit /health/ and a fixture-backed product search.

Framework decision table

Need Prefer Why
Full-stack monolith, admin, auth, ORM Django Batteries included; conventions reduce decision fatigue
Typed async JSON API only FastAPI Lighter; auto OpenAPI; no template/admin overhead
Minimal microservice, few endpoints Flask or Starlette Less framework; you choose every library
Django monolith + mobile/SPA API Django + DRF Shared models and auth; serializers parallel ORM
Real-time WebSockets at scale Django Channels or separate Node service WSGI is request/response; ASGI adds async channel layers
Content site with minimal custom code Django + Wagtail CMS Editor-friendly pages on Django’s foundation

Common pitfalls

  • N+1 queries — listing 500 products without select_related fires 501 SQL round trips; use django-debug-toolbar in dev.
  • Fat views — business logic buried in views becomes untestable; move to model methods, managers, or service modules.
  • Changing AUTH_USER_MODEL late — must be set before first migration; retrofitting custom users is painful.
  • Synchronous external calls in views — blocking HTTP to payment APIs stalls workers; offload to Celery tasks.
  • Running migrations on every web dyno — race conditions on concurrent deploys; run migrations once in a release phase.
  • Trusting is_safe templates — marking user HTML safe enables XSS; sanitize or use a allowlist library.
  • SQLite in production — fine for prototypes; concurrent writes lock the file; use PostgreSQL for real traffic.

Production checklist

  • Set DEBUG=False, configure ALLOWED_HOSTS, and rotate SECRET_KEY from environment variables.
  • Use PostgreSQL with connection pooling (PgBouncer or CONN_MAX_AGE).
  • Run Gunicorn (sync) or Uvicorn (ASGI) behind nginx; collect static with collectstatic to S3 or CDN.
  • Enable HTTPS-only cookies, HSTS, and SECURE_* settings.
  • Schedule backups; test restore; version migrations in CI before deploy.
  • Add structured logging and error tracking (Sentry); expose /health/ for probes.
  • Pair with authentication vs authorization and SQL query optimization for scale.

Key takeaways

  • Django is a batteries-included Python framework for full-stack monoliths with ORM, admin, auth, and templates.
  • Apps modularize features; migrations version schema changes safely across environments.
  • QuerySet optimization (select_related, prefetch_related) separates fast sites from N+1 disasters.
  • Django REST Framework extends the same models to JSON APIs without a separate codebase.
  • Production Django means PostgreSQL, environment-based settings, async task queues for slow work, and migrations run once per deploy.

Related reading