Guides Long read Django

How is a Django request processed?

MVT layers, WSGI/ASGI entry, and the full middleware chain.

26 January 2026 20 min read
Share
X in

Introduction

Django is a full-stack web framework built on the Model-View-Template (MVT) pattern. Unlike classic MVC, the framework itself takes the Controller role; developers separate business logic in Views, data structure in Models, and presentation in Templates. This separation reduces maintenance cost and improves testability in growing projects.

In this article we trace every stage of an HTTP request from the WSGI/ASGI server until the response is returned. We show how urlpatterns, views, ORM queries, template rendering, and the middleware chain connect using real code examples.

Responsibilities of MVT Layers

The Model layer defines the database schema and data-side business rules. The View layer receives HTTP requests, runs business logic, and produces the appropriate response. The Template layer is presentation; it can output HTML, JSON, or other formats.

  • Model: models.Model, Manager, QuerySet, signals
  • View: function-based view (FBV), class-based view (CBV), API view
  • Template: Django Template Language (DTL), context, inheritance
  • URL: path(), re_path(), include(), reverse()

Project and Application Structure

A Django project holds global configuration: settings, urls, and wsgi/asgi. Each app (blog, accounts, etc.) has its own models, views, urls, and templates. An app not listed in INSTALLED_APPS will not have its models migrated or templates discovered by the loader.

# mysite/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
]

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('<slug:slug>/', views.post_detail, name='post_detail'),
]

URL Resolver and View Matching

When a request arrives, Django walks urlpatterns from ROOT_URLCONF. path() parameters are passed to the view as kwargs. include() attaches sub-URL modules and namespaces prevent name collisions.

Using reverse() and the {% url %} tag instead of hard-coded URLs prevents breakage during refactors. The resolve() function shows which view a path maps to for debugging.

from django.urls import reverse
from django.shortcuts import redirect

def create_post(request):
    # ... save logic
    return redirect(reverse('post_detail', kwargs={'slug': post.slug}))

View Layer: FBV and CBV

Function-based views are explicit and easy to learn; ideal for small endpoints. Class-based views reduce repetitive CRUD with generics like ListView, DetailView, and CreateView. In both approaches, request.method checks, form validation, and permission checks belong in the view.

from django.views.generic import ListView
from .models import Post

class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10
    queryset = Post.objects.filter(status='published').select_related('author')
Keep views thin; move heavy business logic to a service layer or model methods.

Model and Template Layers

The view builds a QuerySet from the Model and passes results to the template context. The template loader scans DIRS first, then app template folders. extends and block tags provide layout inheritance.

  • HTML response with render(request, template, context)
  • API endpoint with JsonResponse
  • Presentation logic via template tags and filters
# blog/models.py
from django.db import models
from django.contrib.auth import get_user_model

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    body = models.TextField()
    author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
    published_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ['-published_at']

Request Lifecycle Step by Step

Django's request processing loop starts and ends with middleware. Each middleware can run on both request and response. A typical GET request is processed in the following order.

  1. WSGI/ASGI handler passes the request to Django
  2. Middleware request phase (security, session, auth)
  3. URL resolver finds the view function
  4. View runs; ORM query and template render
  5. HttpResponse is created
  6. Middleware response phase (gzip, security headers)
  7. Response returns to the client
# settings.py - middleware order matters
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Common Mistakes and Conclusion

N+1 queries in views, wrong middleware order, and ROOT_URLCONF mistakes are the most common production issues. Detailed error pages shown when DEBUG=True must never be left enabled in production.

By preserving MVT separation and knowing which layer owns each step of the lifecycle, you gain a major advantage in performance tuning and security audits. Next we cover ORM optimization and the API layer with DRF in depth.

  • Putting business logic inside templates
  • Repeating auth checks in every view (use decorators/mixins)
  • Monolithic urls.py without include()
  • Changing middleware order without reading the docs