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;Metacontrols table name, ordering, constraints. - Migration — versioned schema change files generated by
makemigrationsand applied bymigrate. - QuerySet — lazy, chainable database queries; evaluated only when iterated or forced.
- View — function-based (
def) or class-based (View,ListView, etc.) handler returningHttpResponse. - Admin — auto-generated CRUD UI for staff users, customized via
ModelAdminclasses.
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_relatedfires 501 SQL round trips; usedjango-debug-toolbarin dev. - Fat views — business logic buried in views becomes untestable; move to model methods, managers, or service modules.
- Changing
AUTH_USER_MODELlate — 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_safetemplates — 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, configureALLOWED_HOSTS, and rotateSECRET_KEYfrom environment variables. - Use PostgreSQL with connection pooling (PgBouncer or
CONN_MAX_AGE). - Run Gunicorn (sync) or Uvicorn (ASGI) behind nginx; collect static with
collectstaticto 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
- Python fundamentals explained — syntax, packaging, and virtual environments
- FastAPI fundamentals explained — async typed APIs when Django is too heavy
- PostgreSQL fundamentals explained — MVCC, indexes, and pooling
- REST API design explained — resources, status codes, and pagination