Django Middleware - Comprehensive Guide

Django Middleware - Comprehensive Guide

In Python Django, middleware is a framework that provides a method to process requests and responses globally before they are processed by the view function or after they leave the view. Middleware components are designed so that they remain between the web server and the view, allowing us to perform various operations on requests and responses as they pass through the Django application and web browser.

Understanding Django Middleware

Django middleware operates during the request-response cycle, intercepting requests before they reach the view and responses before they leave the server. Middleware components are executed in the order they are defined in the MIDDLEWARE setting in your project's settings.py file.

Each middleware component is responsible for doing some specific function. For example, Django includes a middleware component, AuthenticationMiddleware , that associates users with requests using sessions. Django ships with some built-in middleware you can use right out of the box. They’re documented in the built-in middleware reference .

Writing your own middleware

A middleware factory is a callable that takes a get_response callable and returns a middleware. A middleware is a callable that takes a request and returns a response, just like a view.

A middleware can be written as a function or it can be written as a class whose instances are callable. We cover only class middlewares in this article:

## file django_project/middlewares.py

class ExampleMiddleware:
    def __init__(self, get_response):
        # One-time configuration and initialization.
        self.get_response = get_response


    def __call__(self, request):
        # Code to be executed for each request before the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after the view is called.

        return response

__init__(get_response)

Middleware factories must accept a get_response argument. You can also initialize some global state for the middleware. Keep in mind a couple of caveats:

  • Django initializes your middleware with only the get_response argument, so you can’t define __init__() as requiring any other arguments.
  • Unlike the __call__() method which is called once per request, __init__() is called only once, when the web server starts.

Activating middleware

Middleware can live anywhere on your Python path. To activate a middleware component, add it to the MIDDLEWARE list in your Django settings.

In MIDDLEWARE, each middleware component is represented by a string: the full Python path to the middleware factory’s class or function name. For example, we add our ExampleMiddleware from middlewares.py file to the top of Middleware list in django settings.py file:

MIDDLEWARE = [
    "django_project.middlewares.ExampleMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
]

A Django installation doesn’t require any middleware — MIDDLEWARE can be empty, if you’d like — but it’s strongly suggested that you at least use CommonMiddleware .

The order in MIDDLEWARE matters because a middleware can depend on other middleware. For instance, AuthenticationMiddleware stores the authenticated user in the session; therefore, it must run after SessionMiddleware . See Middleware ordering for some common hints about ordering of Django middleware classes.

Django Middleware order and layering

During the request phase, before calling the view, Django applies middleware in the order it’s defined in MIDDLEWARE list, top-down.

You can think of it like an onion: each middleware class is a "layer" that wraps the view, which is in the core of the onion. If the request passes through all the layers of the onion (each one calls get_response to pass the request in to the next layer), all the way to the view at the core, the response will then pass through every layer (in reverse order) on the way back out.

django middleware layers django middleware layers

If one of the layers decides to short-circuit and return a response without ever calling its get_response, none of the layers of the onion inside that layer (including the view) will see the request or the response. The response will only return through the same layers that the request passed in through.

django custom middleware example

# views.py
from django.shortcuts import render
import logging
logger = logging.getLogger(__name__)

def test(request):
    logger.info('running "test" view function')
    return render(request, "test.html")
# urls.py - simplified
path("test/", views.test, name="test"),
# file: project_path/middlewares/custom_middlewares.py

import logging

class Middleware01:
    def __init__(self, get_response):
        # One-time configuration and initialization.
        self.get_response = get_response
        self.logger = logging.getLogger('django')
        self.logger.info("initialize middleware 01")

    def __call__(self, request):
        # Code to be executed for each request before the view (and later middleware) are called.
        self.logger.info(f'middleware01 call before the view is called')

        response = self.get_response(request)

        # Code to be executed for each request/response after the view is called.
        self.logger.info(f'middleware01 call after view is called')
        return response
    

class Middleware02:
    def __init__(self, get_response):
        # One-time configuration and initialization.
        self.get_response = get_response
        self.logger = logging.getLogger('django')
        self.logger.info("initialize middleware 02")

    def __call__(self, request):
        # Code to be executed for each request before the view (and later middleware) are called.
        self.logger.info(f'middleware02 call before the view is called')

        response = self.get_response(request)

        # Code to be executed for each request/response after the view is called.
        self.logger.info(f'middleware02 call after view is called')
        return response

    
class Middleware03:
    def __init__(self, get_response):
        # One-time configuration and initialization.
        self.get_response = get_response
        self.logger = logging.getLogger('django')
        self.logger.info("initialize middleware 03")

    def __call__(self, request):
        # Code to be executed for each request before the view (and later middleware) are called.
        self.logger.info(f'middleware03 call before the view is called')

        response = self.get_response(request)

        # Code to be executed for each request/response after the view is called.
        self.logger.info(f'middleware03 call after view is called')
        return response
# settings.py - simplified
MIDDLEWARE = [
    'project_path.middlewares.custom_middlewares.Middleware01',
    'project_path.middlewares.custom_middlewares.Middleware02',
    'project_path.middlewares.custom_middlewares.Middleware03',
    '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',
]

Start your django app with manage.py runserver 0.0.0.0:8000 and navigate your browser to http://127.0.0.1/test. This is output:

~] manage.py runserver 0.0.0.0:8000
Performing system checks...

System check identified no issues (0 silenced).
2024-12-06 16:51:07,515 INFO [None] initialize middleware 03 django custom_middlewares __init__
2024-12-06 16:51:07,516 INFO [None] initialize middleware 02 django custom_middlewares __init__
2024-12-06 16:51:07,516 INFO [None] initialize middleware 01 django custom_middlewares __init__
December 06, 2024 - 16:51:07
Django version 5.1.2, using settings 'django.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CTRL-BREAK.

2024-12-06 16:52:08,268 INFO middleware01 call before the view is called django custom_middlewares __call__
2024-12-06 16:52:08,268 INFO middleware02 call before the view is called django custom_middlewares __call__
2024-12-06 16:52:08,268 INFO middleware03 call before the view is called django custom_middlewares __call__
2024-12-06 16:52:08,273 INFO running "test" view function django.views views test
2024-12-06 16:52:08,310 INFO middleware03 call after view is called django custom_middlewares __call__
2024-12-06 16:52:08,310 INFO middleware02 call after view is called django custom_middlewares __call__
2024-12-06 16:52:08,310 INFO middleware01 call after view is called django custom_middlewares __call__
2024-12-06 16:52:08,311 INFO "GET /test/ HTTP/1.1" 200 57931 django.server basehttp log_message
```

Mastering Django Middleware

Besides the basic request/response middleware pattern described earlier, you can add three optional other special methods to class-based middleware:

process_view()

process_view(request, view_func, view_args, view_kwargs)

request is an HttpRequest object. view_func is the Python function that Django is about to use. (It’s the actual function object, not the name of the function as a string.) view_args is a list of positional arguments that will be passed to the view, and view_kwargs is a dictionary of keyword arguments that will be passed to the view. Neither view_args nor view_kwargs include the first view argument (request).

It should return either None or an HttpResponse object. If it returns None, Django will continue processing this request, executing any other process_view() middleware and, then, the appropriate view. If it returns an HttpResponse object, Django won’t bother calling the appropriate view; it’ll apply response middleware to that HttpResponse and return the result.

process_template_response()

process_template_response(request, response)

request is an HttpRequest object. response is the TemplateResponse object (or equivalent) returned by a Django view or by a middleware.

It must return a response object that implements a render method. It could alter the given response by changing response.template_name and response.context_data, or it could create and return a brand-new TemplateResponse or equivalent.

You don’t need to explicitly render responses – responses will be automatically rendered once all template response middleware has been called.

render() shortucut function from django.shortcuts is not a TemplateResponse object. It is HttpResponse object.

process_exception()

Please, refer to official django documentation: process_exception()

complete django middleware scheme

We can extend our simple django middleware scheme to include process_view() and process_template_response() functions:

full django middleware schema full django middleware schema

django middleware example

Extend our custom django middlewares with process_view() and process_template_response() functions and edit our custom view function:

# views.py (simplified)
from django.template.response import TemplateResponse
import logging
logger = logging.getLogger(__name__)

def test(request):
    logger.info('running "test" view function')
    response = TemplateResponse(request, "test.html", context = {}, status=200)
    return response
# urls.py - simplified
path("test/", views.test, name="test"),
# file: project_path/middlewares/custom_middlewares.py
import logging

class Middleware01:
    def __init__(self, get_response):
        # One-time configuration and initialization.
        self.get_response = get_response
        self.logger = logging.getLogger('django')
        self.logger.info("initialize middleware 01")

    def __call__(self, request):
        # Code to be executed for each request before the view (and later middleware) are called.
        self.logger.info(f'middleware01 call before the view is called')

        response = self.get_response(request)

        # Code to be executed for each request/response after the view is called.
        self.logger.info(f'middleware01 call after view is called')
        return response
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        # This code is executed just before the view is called
        self.logger.info(f'middleware01 process_view just before the view is called from view: {view_func}, with args: {view_args} and kwargs: {view_kwargs}')
        return None

    def process_template_response(self, request, response):
        # This code is executed if the response contains a render() method
        self.logger.info("middleware01 This code is executed if the response contains a render() method")
        return response
    

class Middleware02:
    def __init__(self, get_response):
        # One-time configuration and initialization.
        self.get_response = get_response
        self.logger = logging.getLogger('django')
        self.logger.info("initialize middleware 02")

    def __call__(self, request):
        # Code to be executed for each request before the view (and later middleware) are called.
        self.logger.info(f'middleware02 call before the view is called')

        response = self.get_response(request)

        # Code to be executed for each request/response after the view is called.
        self.logger.info(f'middleware02 call after view is called')
        return response
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        # This code is executed just before the view is called
        self.logger.info(f'middleware02 process_view just before the view is called from view: {view_func}, with args: {view_args} and kwargs: {view_kwargs}')
        return None

    def process_template_response(self, request, response):
        # This code is executed if the response contains a render() method
        self.logger.info("middleware02 This code is executed if the response contains a render() method")
        return response


class Middleware03:
    def __init__(self, get_response):
        # One-time configuration and initialization.
        self.get_response = get_response
        self.logger = logging.getLogger('django')
        self.logger.info("initialize middleware 03")

    def __call__(self, request):
        # Code to be executed for each request before the view (and later middleware) are called.
        self.logger.info(f'middleware03 call before the view is called')

        response = self.get_response(request)

        # Code to be executed for each request/response after the view is called.
        self.logger.info(f'middleware03 call after view is called')
        return response
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        # This code is executed just before the view is called
        self.logger.info(f'middleware03 process_view just before the view is called from view: {view_func}, with args: {view_args} and kwargs: {view_kwargs}')
        return None

    def process_template_response(self, request, response):
        # This code is executed if the response contains a render() method
        self.logger.info("middleware03 This code is executed if the response contains a render() method")
        return response
# settings.py - simplified
MIDDLEWARE = [
    'project_path.middlewares.custom_middlewares.Middleware01',
    'project_path.middlewares.custom_middlewares.Middleware02',
    'project_path.middlewares.custom_middlewares.Middleware03',
    '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',
]

Start your django app with manage.py runserver 0.0.0.0:8000 and navigate your browser to http://127.0.0.1/test. This is output:

~] manage.py runserver 0.0.0.0:8000
System check identified no issues (0 silenced).
2024-12-06 17:51:21,466 INFO [None] initialize middleware 03 django custom_middlewares __init__
2024-12-06 17:51:21,466 INFO [None] initialize middleware 02 django custom_middlewares __init__
2024-12-06 17:51:21,467 INFO [None] initialize middleware 01 django custom_middlewares __init__
December 06, 2024 - 17:51:21
Django version 5.1.2, using settings 'django.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CTRL-BREAK.

2024-12-06 17:51:43,551 INFO middleware01 call before the view is called django custom_middlewares __call__
2024-12-06 17:51:43,551 INFO middleware02 call before the view is called django custom_middlewares __call__
2024-12-06 17:51:43,552 INFO middleware03 call before the view is called django custom_middlewares __call__
2024-12-06 17:51:43,557 INFO middleware01 process_view just before the view is called from view: , with args: () and kwargs: {} django custom_middlewares process_view
2024-12-06 17:51:43,558 INFO middleware02 process_view just before the view is called from view: , with args: () and kwargs: {} django custom_middlewares process_view
2024-12-06 17:51:43,558 INFO middleware03 process_view just before the view is called from view: , with args: () and kwargs: {} django custom_middlewares process_view
2024-12-06 17:51:43,559 INFO running "test" view function django.views views test
2024-12-06 17:51:43,559 INFO middleware03 This code is executed if the response contains a render() method django custom_middlewares process_template_response
2024-12-06 17:51:43,559 INFO middleware02 This code is executed if the response contains a render() method django custom_middlewares process_template_response
2024-12-06 17:51:43,560 INFO middleware01 This code is executed if the response contains a render() method django custom_middlewares process_template_response
2024-12-06 17:51:43,597 INFO middleware03 call after view is called django custom_middlewares __call__
2024-12-06 17:51:43,597 INFO middleware02 call after view is called django custom_middlewares __call__
2024-12-06 17:51:43,598 INFO middleware01 call after view is called django custom_middlewares __call__
2024-12-06 17:51:43,598 INFO "GET /test/ HTTP/1.1" 200 57931 django.server basehttp log_message
```

SUBSCRIBE FOR NEW ARTICLES

@
comments powered by Disqus