Introduction & Architecture

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Django 6.1 (6.1.1 was the latest release on 2026-10-03, when the facts and examples below were checked), Python 3.12 to 3.14, and PostgreSQL 15 or later as the reference database. Where the 5.2 LTS series behaves differently, the text says so. The examples were executed on Django 6.1.1 and Python 3.13 in a scratch project with SQLite; each one is followed by the official page it derives from. It explains what Django is made of and where it fits, before the rest of the section builds the Bookshelf — a catalogue of books, authors and reviews with server-rendered pages, an admin, an API and background work — one concept at a time.

What Django Is

Django is a high-level Python web framework that ships with nearly everything a database-backed website needs: an object-relational mapper (ORM) with migrations, URL routing, a template language, form handling and validation, an automatically generated admin, authentication and permissions, sessions, caching, internationalization, security protections and a test framework. Its motto, "the web framework for perfectionists with deadlines", describes its origin.

The official FAQ: General gives the history. Django grew inside World Online, the web department of a newspaper in Lawrence, Kansas, where developers had to take complicated applications from concept to launch in hours. In fall 2003 Adrian Holovaty and Simon Willison moved from PHP to Python, extracted a generic framework from the sites they built over the next two years, and open-sourced it in summer 2005. It is named after the jazz guitarist Django Reinhardt, is pronounced "JANG-oh", is licensed under the 3-clause BSD license, and is today run by an international team of volunteers backed by the Django Software Foundation.

Two clarifications from the same FAQ save time. Django is a framework, not a content-management system: you use it to build things like a CMS, and the admin is one module among many. And Django scales through a "shared-nothing" architecture, in which web servers, database servers and cache servers are added independently.

Source: FAQ: General

The section is for Python developers who want to build server-rendered sites, back offices and APIs with one coherent toolkit. If you are choosing between stacks, the comparison table later on this page compares Django with the alternatives that this site documents.

Design Philosophies

The official Design philosophies page explains the decisions behind the framework. Knowing them predicts most of Django’s behaviour: when an API surprises you, a philosophy usually explains it.

Philosophy What it says Where you see it

Loose coupling, tight cohesion

The layers of the stack should not know about each other unless necessary: the template system knows nothing about requests, the database layer knows nothing about display, the view layer does not care which template system you use.

You can replace the template engine (Django Template Language or Jinja2), the database backend, the session store or the cache backend through settings alone.

Less code, no boilerplate

Django takes advantage of Python’s dynamic features such as introspection.

A model class is the single description of a table, its validation, its admin screen and its form.

Don’t repeat yourself (DRY)

Every distinct concept lives in one place; the framework deduces as much as it can from as little as possible.

ModelForm derives a form from a model; the admin derives CRUD screens from ModelAdmin.

Explicit is better than implicit

A principle from PEP 20: no "magic" unless it buys a large convenience that cannot be had otherwise.

You call save() yourself; field behaviour comes from keyword arguments, never from a field’s name; URLs are listed in a urlpatterns list.

Models include all domain logic

Models follow the Active Record pattern: data and information about it (verbose names, default ordering) live in the model class.

class Meta, custom managers and model methods are the idiomatic home of domain behaviour.

SQL efficiency and an escape hatch

The database API runs as few statements as possible, and makes dropping to raw SQL easy.

select_related, prefetch_related, the 6.1 fetch modes, raw() and connection.cursor().

URL design

URLs are decoupled from Python names, infinitely flexible, free of file extensions, and normalized so that /bar and /bar/ are not two pages.

path() and include(), URL names with reverse(), and the APPEND_SLASH setting.

Templates separate logic from presentation

The template language offers just enough branching and looping for presentation, is not tied to HTML, treats whitespace literally and forbids arbitrary Python.

the for and if tags, template inheritance, autoescaping, custom tags and filters.

Views are simple

A view is a function that receives a request object, never reads a global, and distinguishes GET from POST.

def book_list(request): …​ and request.method, easy to test with a fake request.

MTV Versus MVC

Django looks like a Model-View-Controller framework but renames the parts, and the FAQ explains why. In Django’s interpretation the "view" describes which data is presented to the user, not how it looks. That is the job of a Python callback for a particular URL, so Django calls it a view. How the data is presented belongs to the template. The classic "controller" — the machinery that sends a request to the right handler — is the framework itself, driven by the URL configuration. Hence MTV: Model, Template, View.

MVC role Django name Where it lives in a project

Model

Model

models.py — classes derived from django.db.models.Model, mapped to tables by migrations.

View (presentation)

Template

templates/ directories — Django Template Language files that render HTML or any text format.

Controller (request handling)

View

views.py — functions or classes that take an HttpRequest and return an HttpResponse.

Controller (routing)

URL dispatcher

urls.py and the ROOT_URLCONF setting — the framework sends each request to a view.

Do not over-read the naming: a Django view is closer to a Spring @Controller or an ASP.NET Core controller action than to a Rails view. What matters in practice is the separation of concerns. The smallest complete MTV slice of the Bookshelf is a model, a view, a template and a URL; the example below is the whole of it.

The MTV slice of the Bookshelf: model, view, template and URL
# books/models.py
from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=200)
    year = models.PositiveSmallIntegerField()

    class Meta:
        ordering = ["title"]

    def __str__(self):
        return self.title
# books/views.py
from django.shortcuts import render

from .models import Book


def book_list(request):
    books = Book.objects.all()
    return render(request, "books/book_list.html", {"books": books})
{# books/templates/books/book_list.html #}
<h1>Bookshelf</h1>
<ul>
{% for book in books %}
  <li>{{ book.title }} ({{ book.year }})</li>
{% empty %}
  <li>No books yet.</li>
{% endfor %}
</ul>
# books/urls.py (included from config/urls.py with path("books/", include("books.urls")))
from django.urls import path

from . import views

urlpatterns = [
    path("", views.book_list, name="book-list"),
]

A test written with Django’s own test framework walks the whole slice — URL, view, ORM, template — through the test client:

# books/tests.py
from django.test import TestCase
from django.urls import reverse

from .models import Book


class BookListTests(TestCase):
    def test_lists_books_through_the_mtv_slice(self):
        Book.objects.create(title="Dune", year=1965)
        response = self.client.get(reverse("book-list"))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Dune (1965)")
        self.assertTemplateUsed(response, "books/book_list.html")

    def test_empty_state(self):
        self.assertContains(self.client.get("/books/"), "No books yet.")
$ python manage.py makemigrations books
Migrations for 'books':
  books/migrations/0001_initial.py
    + Create model Book
$ python manage.py check
System check identified no issues (0 silenced).
$ python manage.py test books
Found 2 test(s).
...
Ran 2 tests in 0.011s

OK

The later pages treat each part in depth: Models & Fields, URLs & Routing, Function-Based Views & Shortcuts and Templates.

Batteries Included

Django follows Python’s "batteries included" philosophy. Everything in django.contrib is optional, lives in the Django distribution, and is switched on by listing it in INSTALLED_APPS. Apps that ship models (for example auth, sessions or sites) additionally need manage.py migrate; apps that ship template tags need the app installed so the tags can be loaded. The official contrib packages page lists them:

Package What it provides

admin

The automatic administrative interface, built from your models. Requires auth and contenttypes.

auth

Users, groups, permissions, password hashing, login and logout views.

contenttypes

A registry of the installed models, used by generic relations and by permissions.

sessions

Per-visitor data, stored in the database, the cache, files, signed cookies or a custom backend.

messages

One-shot messages stored in cookies or the session (the "flash message" pattern).

staticfiles

Collecting and serving CSS, JavaScript and images.

sites, sitemaps, syndication

Multiple sites on one database, XML sitemaps and RSS or Atom feeds.

flatpages, redirects, humanize

HTML pages stored in the database, managed redirects and "human touch" template filters.

gis, postgres

GeoDjango (geospatial data) and PostgreSQL-specific fields, indexes and aggregates.

Beyond django.contrib, the framework core also includes the cache framework, the signals dispatcher, the django.core.mail package, the validation and serialization tools, the system check framework, internationalization and, since 6.0, the built-in django.tasks framework and Content Security Policy support. "Batteries included" does not mean "no ecosystem": REST APIs, WebSockets and distributed task queues are provided by well-known packages (see the component map and Reusable Apps & Third-Party Packages).

The Component Map

The figure shows how the pieces relate. The top row is the journey of a request: a client sends an HTTP request, the WSGI or ASGI server hands it to Django, the middleware stack processes it in the order of the MIDDLEWARE setting, and the URL dispatcher picks a view. The view is the hub: it talks to the model layer (ORM and database) for data, to forms to validate input, and to a template to render the answer. The response travels back out through the middleware in reverse order.

A diagram titled Django component map. The top row shows a request path: Client, WSGI or ASGI server, Middleware, URL dispatcher, with a dashed arrow returning the HttpResponse back through the middleware to the client. Below the URL dispatcher is the View, which renders a Template, validates input with Forms, and reads and writes data through the Model and ORM, which talks to the Database. At the left a dashed panel lists the django.contrib batteries: admin, auth, sessions, messages, staticfiles, contenttypes, sites, sitemaps, syndication, flatpages, redirects, humanize, gis and postgres, plus your own apps

The table maps each component to its configuration, its package and the page of this section that covers it:

Component Configured by Lives in Covered in

ORM and migrations

DATABASES, models.py

django.db

Models & Fields, Making Queries, Migrations

URL dispatcher

ROOT_URLCONF, urlpatterns

django.urls

URLs & Routing

Views and the request/response objects

views.py

django.views, django.http

The Request/Response Cycle, Class-Based Views

Templates

TEMPLATES

django.template

Templates

Forms

forms.py

django.forms

Forms

Admin

admin.py, INSTALLED_APPS

django.contrib.admin

The Admin Site

Authentication and authorization

AUTH_USER_MODEL, AUTHENTICATION_BACKENDS

django.contrib.auth

Authentication

Middleware

MIDDLEWARE

django.middleware, contrib apps

Middleware

Settings

DJANGO_SETTINGS_MODULE

django.conf

Settings & Configuration

Application registry

INSTALLED_APPS, AppConfig

django.apps

Project Structure & Apps

You do not have to take the map on trust: the application registry, the settings and the URL resolver can be asked which components are active in a running project. The script below prints them; run it with python manage.py shell < inspect_components.py.

# inspect_components.py -- run with: python manage.py shell < inspect_components.py
from django.apps import apps
from django.conf import settings
from django.template import engines
from django.urls import resolve

print("apps      :", [c.label for c in apps.get_app_configs()])
print("middleware:", [m.rsplit(".", 1)[-1] for m in settings.MIDDLEWARE])
print("templates :", [e.name for e in engines.all()])
match = resolve("/books/")
print("url ->    :", match.view_name, "->", match.func.__module__ + "." + match.func.__name__)
print("models    :", [m.__name__ for m in apps.get_app_config("books").get_models()])

On a fresh django-admin startproject project with the books app added to INSTALLED_APPS and routed at /books/, it prints:

apps      : ['admin', 'auth', 'contenttypes', 'sessions', 'messages', 'staticfiles', 'books']
middleware: ['SecurityMiddleware', 'SessionMiddleware', 'CommonMiddleware', 'CsrfViewMiddleware', 'AuthenticationMiddleware', 'MessageMiddleware', 'XFrameOptionsMiddleware']
templates : ['django']
url ->    : book-list -> books.views.book_list
models    : ['Book']

The Pieces Are Modular

Loose coupling is real, not a slogan. Django does not require the startproject layout, and a complete application fits in one file: settings.configure() replaces a settings module, the module itself can be the URLconf, and get_wsgi_application() returns a WSGI callable.

# mini.py -- a whole Django "project" in one file
import django
from django.conf import settings
from django.core.wsgi import get_wsgi_application
from django.http import JsonResponse
from django.urls import path

settings.configure(
    DEBUG=False,
    SECRET_KEY="not-a-real-key-for-a-one-file-demo",
    ROOT_URLCONF=__name__,
    ALLOWED_HOSTS=["testserver", "localhost"],
    MIDDLEWARE=["django.middleware.common.CommonMiddleware"],
)


def ping(request):
    return JsonResponse({"django": django.get_version(), "path": request.path})


urlpatterns = [path("ping/", ping)]

application = get_wsgi_application()  # a WSGI callable, no project layout needed

if __name__ == "__main__":
    from django.test import Client

    response = Client().get("/ping/")
    print(response.status_code, response.json())
$ python mini.py
200 {'django': '6.1.1', 'path': '/ping/'}

The literal secret in this demo is for a throw-away script only; the real configuration pages show how to read it from the environment. Single-file Django is a teaching tool and the way to embed Django in a script or a test, not a recommended application layout; the standard layout is described in Project Structure & Apps.

WSGI Versus ASGI

Django applications are served through one of two Python server interfaces:

WSGI ASGI

Specification

PEP 3333; a synchronous callable application(environ, start_response).

ASGI; an asynchronous callable application(scope, receive, send).

Protocols

HTTP request/response only.

HTTP, WebSockets and other long-lived protocols.

Django entry point

get_wsgi_application() in <project>/wsgi.py.

get_asgi_application() in <project>/asgi.py.

Servers documented by Django

Gunicorn, uWSGI, Apache with mod_wsgi.

Daphne, Granian, Hypercorn, Uvicorn.

Sync views

Run directly in the worker’s thread.

Run in a thread pool so they do not block the event loop.

Async views

Run in their own one-off event loop per request, at a small adaptation cost; no efficient long-running requests.

Run natively on the server’s event loop; hundreds of slow connections without hundreds of threads.

startproject generates both files. They are only a few lines each:

# config/wsgi.py
import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()
# config/asgi.py
import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()

Servers take the dotted path to that object: gunicorn config.wsgi:application or uvicorn config.asgi:application. The development server runserver does not use either file.

The same Django code runs under both. A view is asynchronous when the callable returns a coroutine, which in practice means async def; a class-based view declares its get() and post() handlers as async def. Django inspects the callable and picks the right path:

# async_views.py
import asyncio
import time

from django.http import JsonResponse


def sync_view(request):
    time.sleep(0.2)  # blocking: occupies a worker thread
    return JsonResponse({"style": "sync"})


async def async_view(request):
    await asyncio.sleep(0.2)  # cooperative: the event loop is free meanwhile
    return JsonResponse({"style": "async"})

After routing both views (path("sync/", async_views.sync_view) and path("async/", async_views.async_view)) and running uvicorn config.asgi:application --port 8101, then gunicorn config.wsgi:application --workers 1 --threads 8 for the WSGI check, the results were:

$ python manage.py check
System check identified no issues (0 silenced).
$ curl -s localhost:8101/async/   # Uvicorn, ASGI
{"style": "async"}
$ curl -s localhost:8101/sync/    # Uvicorn, ASGI: sync view served from a thread
{"style": "sync"}
$ curl -s localhost:8102/async/   # Gunicorn, WSGI: async view still works
{"style": "async"}

Ten concurrent requests to each view against Uvicorn took 0.24 seconds for /sync/ and 0.22 seconds for /async/. That is not a mistake in the benchmark but the point of the table: under ASGI a sync view runs in a thread pool, so for a handful of concurrent requests the two styles are indistinguishable. Async pays off when a request spends its time waiting — calling external APIs, streaming, long polling, WebSockets — and you need many of them open at once; it does not make CPU-bound code faster.

Pitfalls and rules worth knowing early:

  • Do not block the event loop. Never call a blocking library from an async def view. Wrap legacy synchronous code with asgiref.sync.sync_to_async(). If you call a synchronous-only part of Django from async code, Django’s async-safety protection raises an error instead of risking data corruption.

  • Async needs async middleware end to end to be fully efficient. Django adapts synchronous middleware by running it in its own thread; the bundled middleware supports both modes, third-party middleware may not.

  • The ORM has async methods: every QuerySet method that runs SQL has an a-prefixed variant (aget(), acreate(), afirst()), and async for works on any QuerySet. Transactions do not yet work in async mode; write that part as one synchronous function and call it through sync_to_async(), and keep CONN_MAX_AGE at its default in async code.

  • Choose one deployment model per process. WSGI plus async def works but gives none of the async benefits; WebSockets need ASGI (see Channels & WebSockets).

Async Support covers async views, the async ORM, sync_to_async() and the performance trade-offs in depth, and Deployment covers production servers.

When Each Piece Arrived

Version Change

3.0

ASGI support (get_asgi_application()), the first step towards async.

3.1

Async views and middleware.

4.1

Asynchronous ORM interface (async for, aget() and the other a-prefixed methods).

4.2

StreamingHttpResponse accepts async iterators under ASGI.

5.0

Async authentication (alogin(), aget_user()) and asynchronous signal sending (asend()).

6.0

AsyncPaginator and AsyncPage.

Source: 3.0, 3.1, 4.1, 4.2, 5.0 and 6.0 release notes

Release Cadence, LTS and Deprecation Policy

Django’s release process is documented in Django’s release process, and the practical consequences are simple enough to remember.

  • Feature releases are numbered A.B and, under the policy that applies up to 6.2, arrive roughly every eight months (6.0 shipped on 2025-12-03 and 6.1 on 2026-08-05). They are mostly backwards compatible; exceptions are listed in the release notes. Each cycle has alpha, beta and release-candidate stages.

  • Patch releases (A.B.C) fix bugs and security issues and are 100% compatible with the feature release except when a security or data-loss fix cannot be made otherwise. "Should I upgrade to the latest patch release?" is always "yes". As of 2026-10-03 the latest is 6.1.1, and 6.1.2 is scheduled for 2026-10-06 with security fixes.

  • Long-term support (LTS) releases are designated feature releases that receive security and data-loss fixes for a guaranteed period, typically three years. Since Django 2.0 the version numbers follow a loose form of semantic versioning: each release after an LTS bumps to the next ".0" (2.0, 2.1, 2.2 LTS, 3.0, 3.1, 3.2 LTS, and so on).

  • Support levels: the current download page distinguishes mainstream support (security fixes, data-loss bugs, crashing bugs, major bugs in new features, regressions) from extended support (security and data-loss fixes only).

As read from the download page on 2026-10-03:

Series Latest End of mainstream support End of extended support

5.2 LTS

5.2.17

December 3, 2025

April 2028

6.0

6.0.8

August 4, 2026

April 2027

6.1

6.1.1

April 2027

December 2027

The roadmap on the same page lists Django 6.2 LTS for April 2027 (mainstream support until December 2027, extended until April 2030), then Django 2028 in January 2028 and Django 2029 in January 2029. In practice: use the newest release for new projects (it is supported longest and has the best documentation); use 5.2 LTS if you must stay on Python 3.10 or 3.11, which 5.2 supports and 6.0 and later do not; and plan LTS-to-LTS upgrades by going through the intermediate releases' deprecation warnings.

Calendar Versioning From Django 2028

Django 6.2 is the last release under the previous versioning and support policy. As the 6.1 release notes say under "Adopting calendar versioning", following DEP 20 the releases that had been planned as Django 7.0 and 7.1 are now Django 2028 and Django 2029. From Django 2028 on:

  • feature releases use the version format YYYY and are released every January;

  • every feature release receives the same three-year support period (the LTS distinction goes away);

  • patch releases keep the pattern of the year followed by a counter (the download page gives 2028.3 as an example);

  • the deprecation warning classes are renamed after the calendar version in which a feature is removed. The notes state that RemovedInDjango70Warning becomes RemovedInDjango2028Warning. In the 6.1.1 package used for this page, django.utils.deprecation still defines RemovedInDjango70Warning (a PendingDeprecationWarning) and RemovedInNextVersionWarning (a DeprecationWarning), and the deprecation timeline lists the removals planned for the first calendar release under the heading "2028".

Code that only catches DeprecationWarning and PendingDeprecationWarning, as the examples below do, is unaffected by the renaming; only code that imports the Django-specific classes by name needs to change.

The Deprecation Policy in Practice

The official policy guarantees that a deprecated feature keeps working, with a warning, for at least two feature releases. A feature deprecated in A.x continues to work in all A.x versions and is removed in B.0, or in B.1 when it was deprecated in the last A.x release (the LTS), so that deprecations always span at least two releases. The worked example in the docs: a function deprecated in 4.2 (an LTS) still exists in 5.0 and is removed in 5.1. Third-party apps that support LTS-to-LTS upgrades therefore need compatibility back to the earlier LTS.

The warnings are silent by default. Turn them on in the three places they matter:

# 1. Run the development server or any command with deprecation warnings visible
$ python -Wd manage.py runserver

# 2. Turn them into errors for the system check and for the test suite
$ python -W error::DeprecationWarning -W error::PendingDeprecationWarning manage.py check
System check identified no issues (0 silenced).
$ python -W error::DeprecationWarning -W error::PendingDeprecationWarning manage.py test books
Found 2 test(s).
...
OK

With pytest and pytest-django, the equivalent is a filterwarnings entry that makes new deprecations fail the build instead of scrolling past:

# pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = config.settings
python_files = tests.py test_*.py
filterwarnings =
    error::DeprecationWarning
    error::PendingDeprecationWarning
$ python -m pytest books -q
..                                                                       [100%]
2 passed in 0.25s

A good upgrade routine is: move to the latest patch of your current series, run the tests with warnings as errors, fix what is flagged, then move to the next feature release and repeat. The tool django-upgrade automates many of the mechanical rewrites; What’s Changed & Upgrading walks through the process release by release.

How Django Compares

A comparison is only useful when it states what each framework is for, so the table below is a short orientation drawn from each project’s own documentation, not a ranking. The other Python web frameworks have their own sections under Backend Development: Python Flask, Python Tornado and Python Pyramid.

Framework Language and model Where it overlaps with Django When it is the better fit

Flask (see Python Flask)

Python micro-framework on WSGI, with a large extension ecosystem; async views are supported but the framework is not async-first.

Routing, templating (Jinja2), a development server, a test client.

Small services and teams that want to choose every component (ORM, forms, admin) themselves.

FastAPI (official site)

Python, ASGI, type-hint driven; Pydantic validation, generated OpenAPI, dependency injection.

Python web back ends; both run on ASGI servers such as Uvicorn.

API-first services where typed declarations should drive validation and documentation, with no server-rendered UI or admin.

Django Ninja (official site)

An API layer for Django in the FastAPI style: type hints, Pydantic schemas, OpenAPI.

It is Django: same ORM, auth, settings, admin and migrations.

You want FastAPI-style endpoints inside a Django project (see also Django REST Framework).

Ruby on Rails (guides)

Ruby, MVC, "convention over configuration"; Active Record ORM, Action View templates, generators.

The closest philosophical sibling: full-stack, batteries included, ORM with migrations, server-rendered pages.

A Ruby team, or when its conventions and generators match your workflow better than Django’s explicit settings.

Laravel (site)

PHP, MVC; Eloquent ORM, Blade templates, Artisan command-line tool.

Full-stack with ORM, migrations, templating, queues and a command line.

A PHP team or a PHP hosting environment.

ASP.NET Core (official docs)

C#, MVC, Razor Pages, Minimal APIs and Blazor on the .NET runtime; Entity Framework Core for data (see Data Access with EF Core).

Routing, middleware pipeline, model binding, authentication and dependency injection.

A .NET organization, strongly typed compile-time checking, or Windows-centric infrastructure.

Spring Boot (official site)

Java or Kotlin on the JVM; Spring MVC or WebFlux, Spring Data JPA and auto-configuration (see Spring Data JPA).

Controllers or views, ORM, security, a rich ecosystem; production-grade defaults.

A JVM organization, long-lived enterprise services, or workloads that need the JVM’s concurrency and tooling.

For readers moving between stacks, the concept mapping is more useful than the feature list. Django names on the left, the closest counterparts on the right (approximate: the frameworks differ in detail):

Django Rails Laravel ASP.NET Core Spring Boot

Model and QuerySet

Active Record model

Eloquent model

EF Core entity and DbSet

JPA entity and Spring Data repository

Migrations (makemigrations, migrate)

Active Record migrations

Laravel migrations

EF Core migrations

Flyway or Liquibase (external)

URLconf (path())

config/routes.rb

routes/web.php

Routing and endpoints

@RequestMapping and its variants

View (function or class)

Controller action

Controller method

Controller action or Minimal API handler

@Controller or @RestController method

Template (Django Template Language)

ERB view

Blade view

Razor view

Thymeleaf or other template engine

Middleware

Rack middleware

HTTP middleware

Middleware pipeline

Servlet filters and interceptors

Forms and validators

Strong parameters and validations

Form requests

Model binding and data annotations

Bean Validation

Admin site

none built in (gems)

none built in (packages)

none built in

none built in

The comparison mixes first-party and third-party pieces on purpose (migrations in Spring Boot come from Flyway or Liquibase rather than from Spring itself). The principle behind each choice is the same across the table: pick the framework your team can run and hire for, then use the idioms of that framework rather than porting another’s.

The sections of this site on FastAPI, Spring Boot and ASP.NET Core give the other half of each comparison, and PHP and Laravel covers Laravel; there is no page for Rails on this site, so that entry links to the official documentation only.

Source: FAQ: General and each framework’s official site linked in the table

Where the Books Are Outdated

Books written for Django 1.11 to 3.x (the books listed in the section’s bibliography are of that age) describe a correct architecture but several details that no longer match Django 6.1. For this page’s topics:

  • Python and compatibility layers. Django 1.11 supported Python 2.7. The six helpers and python_2_unicode_compatible were removed in 3.0, and ugettext and force_text in 4.0. Django 6.1 needs Python 3.12 to 3.14; 5.2 LTS needs 3.10 to 3.14.

  • Settings names. MIDDLEWARE_CLASSES was replaced by MIDDLEWARE (the old setting was removed in 2.0), and url() and is_ajax() were removed in 4.0 (use path() or re_path(), and inspect the X-Requested-With request header yourself if you still need it).

  • Versioning and support. Numbering changed to loose semantic versioning at 2.0, and from 2028 it becomes calendar versioning with uniform three-year support, so any "an LTS every third release" rule of thumb is dated.

  • Server model. Django was WSGI-only until 3.0. ASGI (3.0), async views (3.1) and the async ORM (4.1) mean the 2018 advice that real-time features always require a separate process or only Channels is out of date: Channels remains the right tool for WebSockets and background consumers, but plain async views cover many long-polling and streaming needs.

  • Defaults. USE_TZ defaults to True since 5.0 and pytz was dropped in 5.0 in favour of zoneinfo (the default since 4.0); DEFAULT_AUTO_FIELD was introduced in 3.2 and the default is BigAutoField since 6.0; USE_L10N was removed in 5.0.

  • Naming. Everything said about MTV versus MVC in the books still holds; what is older is the vocabulary around "function-based views are the only way", because class-based and async views are both first-class today.

What Changed Recently

Release Relevant changes

5.2 LTS (April 2025)

Composite primary keys, automatic model imports in shell, and extended support until April 2028.

6.0 (December 2025)

Python 3.12 to 3.14 only; built-in Content Security Policy support; template partials ({% partialdef %} and {% partial %}); the django.tasks background-tasks framework; AsyncPaginator; the modern email API.

6.1 (August 2026)

Model field fetch modes (FETCH_ONE, FETCH_PEERS, FETCH_RAISE), database-level on_delete options (DB_CASCADE, DB_SET_NULL, DB_SET_DEFAULT), the MAILERS setting, UUID4 and UUID7 database functions, PostgreSQL 15 as the minimum, and calendar versioning (DEP 20).

6.2 LTS (April 2027, planned)

The last release under the old versioning and support policy.

2028 (January 2028, planned)

The first calendar-versioned release, then one release every January.

Source: 5.2, 6.0 and 6.1 release notes

Where to Go Next

Start with Getting Started to install Django and build a first page, then read Project Structure & Apps and Settings & Configuration. The Python language is not re-taught here; see Python Reference for the language and Virtual Environments & Packaging for environments.