How to create custom Django Middleware

Django middleware is a light framework that globally alters Django inputs and outputs. We discussed Django middleware and the in-built Django middleware in detail here.  You can check it out and get a basic knowledge of it before learning how to create custom Django middleware.

Writing our own custom Django middleware.

Middleware act just like a view. It takes a request and returns a response. The middleware is a callable that takes a get_response callable and returns a middleware.

The middleware can be written in two forms: As a function and as a Class.

The function format for representing middleware is as follows:

def simple_middleware(get_response):
    # One-time configuration and initialization.

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

        response = get_response(request)

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

        return response

    return middleware

According to the code, the middleware function simple_middleware takes a mandatory get_response parameter which is a reference to the next middleware in the chain or the actual view. It returns the middleware function defined within it.

The function middleware()  defines the core of the middleware and it takes the parameter, request, representing the incoming request to the server. The commented part is the code that will be executed before the view is called.

 response = get_response(request)  calls the next middleware in the chain or the final view. According to the code, the get_response function is provided as a parameter when creating the middleware, and it's responsible for passing the request through the rest of the middleware chain until it reaches the view. 

Then the next commented code is codes that are executed after the view is called.

Finally, the simple_middleware function returns the middleware function. 

Let's represent the class-based format for creating a custom Django middleware. In this case, the instance is callable.

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

    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

classSimpleMiddleware: is a class used to define the middleware called SimpleMiddleware

def __init__(self, get_response) represents a constructor. The constructor takes two parameters, self and get_response. The self refers to the instance of the class and  get_response represents the next middleware in the chain or the final view. 

get_response is assigned to the instance of the class, self.get_response.

def __call__(self, request) is a special method that allows class instances to be called like a function. It takes two parameters: self and request.

 response = self.get_response(request) : The  get_response function takes the request and passes it along to the next middleware in line or the final view.

Finally, the __call__ method returns the response generated by the view(or the subsequent middleware) to the framework.

How does the middleware work?

The Django middleware is located in the settings.py file. All the middleware is housed in a list and each is represented as a string. The string format describes the path of the middleware function.

MIDDLEWARE = [
    "myapp.middleware.CustomMiddleware", # Adjust this path to match your project structure    
"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", ]

Each middleware follows an order because a middleware may depend on another to function effectively. 

When a request is sent, it passes through each layer of the middleware class (each of the layers calls get_request to pass the request to the next layer) until it gets to the view. The response will follow the same pattern in reverse order. Note that the response must follow the same layer the request followed but in reverse order, and if any of the layers develop a faulty circuit and return a response without attaching the get_reponse, none of the middleware layers will be able to view the request or response.

 Apart from the mentioned methods, there are also other special methods that can be applied to class-based middleware.

process_view()

When a request is received, the middleware process_view method is called before the actual view function is invoked. 

Generally, this method is used to alter the view function or view processing before the view logic is executed. 

The process_view method takes four parameters  as follows:

process_view(request, view_func, view_args, view_kwargs)

request : The incoming HttpRequest object.

view_func : The view function that is about to be called.

view_args : The positional argument that will be passed to the view.

view_kwargs : The keyword argument in the form of a Python dictionary that will be passed to the view.

A typical way to use process_view is represented as follows: 

class SimpleMiddleware: 
def __init__(self, get_response):
self.get_response = get_response

def
__call__(self, request):
#This code runs before the view
response = self.process_view(request)

if
response is None:
response = self.get_response(request)

# This code runs after the view

return response

def
process_view(self, request):
# Implement your logic here
if condition:
# Modify view_args, view_kwargs, or perform other actions pass

According to the code, the process_view takes two parameters self and request which later does some processing before the actual view is executed. 

process_exception():

The process_exception method is a Django hook that is called when an exception is raised during request processing. 

The process_exception method handles the exception globally before the Django application propagates it to the view. 

The process_exception takes two parameters as follows:

process_exception(request, exception)

request: The incoming HttpRequest object.

exception: The exception object raised by the view function.

Step-by-step on how the process_exception works.

  • The Django middleware processing chain checks for the process_exception method when an exception is raised by the Django middleware classes.
  • The process_exception method is called in reverse order. This is because the response phase happens in reverse order. This means that the process_exception method that is called due to the exception raised will be responded to from bottom to top in the middleware settings.
  • The process_exception will either return None or an  HttpResponse object, and it will be applied to the middleware response.

An example of how you can use the process_exception to create a custom middleware is as follows:

class SimpleMiddleware: 
def __init__(self, get_response):
self.get_response = get_response

def
__call__(self, request):
# This code runs before the view
response = self.get_response(request)
# This code runs after the view return response

def process_exception(self, request, exception):
# Handle the exception or perform actions
# For example, log the exception or modify the response
# If you don't want to handle the exception, you can just pass
pass

 process_template_response()

process_template_response(request, response) is a hook in Django used to modify a response generated by a view when rendering a template. This method alters the response before it gets to the client. 

In a more specific sense, if there is a TemplateResponse or equivalent, the process_template_response() is called after the view is executed. 

The process_template_response() method takes in two parameters as follows:

request: The incoming  HttpRequest object.

response: This is the  TemplateResponse object (or equivalent) returned by a Django view or by a middleware.

An example of how you can use the process_template_response is as follows:

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

def __call__(self, request):
        # This code runs before the view
        response = self.get_response(request)
       
# This code runs after the view
        return response

def process_template_response(self, request, response):
      # Modify the response content after template rendering
       
if response.status_code == 200 and 'song' in response.content.decode():
          new_content = response.content.replace(b'song', b'changesong')
            response.content = new_content
        return response

According to the process_template_response function, if the response status code is 200 and there is song in the response content, the song will be replaced by changesong

Conclusion

Custom Django middleware can be created using functions or classes in Python. The basic request/response middleware and special methods like __calls__, __init__ are also used to create custom Django middleware. Other hooks or methods that are used includes process_view(), process_exception() and the process_template_response(). This custom middleware class can perform tasks like authentication, logging, modifying headers, or any other preprocessing and postprocessing steps you need.