How Django works in 5 minutes¶
If Django is your day job, skim this and move on. If you've mostly used FastAPI, Flask, Node, or Rails — this is the version of Django that fits on one page, written for folks who want enough mental model to read the rest of the docs comfortably.
The big idea¶
Django turns an HTTP request into an HTTP response. That's it. Everything else — the ORM, the admin, migrations, sessions — is a helper that lives inside that one cycle.
Three pieces are doing the work: URLs, middleware, and views.
URLs¶
Django reads a Python file called urls.py and matches the incoming URL against a list of patterns:
# config/urls.py
from django.urls import path
from shop import views
urlpatterns = [
path("admin/", admin.site.urls),
path("products/", views.product_list),
path("products/<int:pk>/", views.product_detail),
]
If the URL is /products/42/, Django calls views.product_detail(request, pk=42). Nothing fancy.
Views¶
A view is a Python function (or class). It receives an HttpRequest and returns an HttpResponse:
# shop/views.py
from django.shortcuts import render
from shop.models import Product
def product_detail(request, pk: int):
product = Product.objects.get(pk=pk)
return render(request, "product.html", {"product": product})
The view can talk to the database, read cookies, write to the session, render a template, or return JSON. It's just a Python function.
Middleware — the layered onion¶
Before your view runs, the request walks through a list of middleware classes. Each one can read or modify the request, short-circuit with a response, or do nothing.
# settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", # fills request.session
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", # CSRF protection
"django.contrib.auth.middleware.AuthenticationMiddleware", # fills request.user
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
Think of it as an onion. The request goes in through each layer, hits the view, then the response comes out through the same layers in reverse.
By the time the view runs, the request has been decorated with goodies:
def some_view(request):
request.user # the logged-in Django user (or AnonymousUser)
request.session # a dict-like session store backed by the DB or cache
request.COOKIES # the raw cookies the browser sent
request.LANGUAGE_CODE # the user's locale, set by LocaleMiddleware
request._messages # one-shot flash messages
Custom middleware works exactly the same way. If you write a MultiTenantMiddleware that puts request.tenant on every request, every view downstream sees it.
Remember this: middleware only runs on HTTP requests. It does not run on Reflex WebSocket events by default. That gap is exactly what
reflex-djangocloses.
Models and the ORM¶
Django ships with a database ORM. You declare your tables in Python:
# shop/models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
Run python manage.py makemigrations and python manage.py migrate, and the table exists. Query it like this:
Product.objects.create(name="Coffee", price="3.50")
Product.objects.filter(price__lt=5).order_by("name")
There's also an async version of every method (acreate, aget, asave, adelete). That matters a lot in reflex-django because Reflex event handlers are async. You'll use these constantly:
await Product.objects.acreate(name="Coffee", price="3.50")
async for p in Product.objects.filter(price__lt=5):
...
The admin¶
Register a model in admin.py and Django builds a full CRUD UI for it:
# shop/admin.py
from django.contrib import admin
from shop.models import Product
admin.site.register(Product)
Visit /admin/, log in, and you can list, create, edit, and delete Product rows. It's one of Django's superpowers and reflex-django lets you keep using it untouched.
Sessions and auth¶
Django has a built-in user model and session system. The flow is:
- User submits
/admin/login/with a username and password. - Django validates them with
django.contrib.auth.authenticate(). - If valid, Django creates a row in the
django_sessiontable and sends back asessionidcookie. - On every subsequent request,
SessionMiddlewarereads the cookie and loads the session;AuthenticationMiddlewarereads the session and fillsrequest.user.
You don't write this code. It just works once you have those two middlewares enabled.
This is the magic
reflex-djangoextends to WebSockets. Samesessionidcookie, same session row, same user — just inside a@rx.eventhandler now.
Settings¶
settings.py is one Python file that configures everything: INSTALLED_APPS, MIDDLEWARE, DATABASES, SECRET_KEY, ALLOWED_HOSTS, static files, templates. reflex-django adds a small number of REFLEX_DJANGO_* keys to this file — that's where you tune the integration.
The shape of a Django project¶
myproject/
├── manage.py # run admin commands: migrate, runserver, shell
├── config/
│ ├── settings.py # INSTALLED_APPS, MIDDLEWARE, DATABASES, ...
│ ├── urls.py # top-level URL patterns
│ └── asgi.py # ASGI entry point (modern deploys)
└── shop/ # a Django "app" — a feature module
├── models.py
├── views.py # your views go here (and in reflex-django, your Reflex pages too)
├── admin.py
└── migrations/
A project contains many apps. An app is just a folder of related code (models, views, admin, templates). INSTALLED_APPS in settings.py lists them.
You now know enough¶
That's it. That's Django for the purposes of these docs. If you can read the four bullets below without confusion, you're good:
- Django matches a URL to a view function and runs it.
- Before the view runs,
settings.MIDDLEWAREdecorates the request withuser,session, and friends. - The ORM lets you query the database with both sync and async APIs.
- Sessions are server-side rows looked up by a
sessionidcookie.
The next page does the same thing for Reflex.