Building a Django middleware (injecting data into a view's context)
In this post you'll learn how to build your own Django middleware and how to inject data into a view's context directly from the middleware.
What is a Django middleware and what is used for?
I had an interesting use case recently where I needed to inject dynamic data into a Django view's context.
The data didn't come from the database. I needed to serve different objects depending on the request META HTTP_ACCEPT_LANGUAGE
, and to make that data accessible from a JavaScript frontend.
Building a Django middleware has been the natural solution. A Django middleware is like a plug-in that you can hook into the Django's request/response cycle.
In this post you'll learn how to build your own Django middleware and how to inject data into a view's context directly from the middleware.
Setting up the project
Create a new folder for the Django project and move into it:
mkdir make-me-middleware && cd $_
Once inside create a Python virtual environment and activate it:
python3 -m venv venv
source venv/bin/activate
Next up install Django:
pip install django
and create the new Django project:
django-admin startproject make_me_middleware .
Finally create a new Django app, I'll call mine middleware_demo:
django-admin startapp middleware_demo
And now let's get to work!
Building the Django middleware
A Django middleware can live inside a Python class implementing at least two dunder methods: init and call.
In a Django middleware init is called once the server starts, while call is called for every new request to the Django application.
With this knowledge in hand create a new file called middleware.py
in middleware_demo
and create a new middleware named JSONTranslationMiddleware:
# file: middleware_demo/middleware.py
class JSONTranslationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
You can also see that init takes get_response
, while call
returns the same object after taking request as a parameter.
This step is important to make the Django app work. get_response
in fact will be the actual view or just another middleware in the chain.
The init method can have one-time configurations and instance variables as well, in my case I declared a Python dictionary with a couple other nested dictionaries:
# file: middleware_demo/middleware.py
class JSONTranslationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.translations = {
"en": {"greeting": "Hello", "header": "Welcome Django!"},
"nl": {"greeting": "Hallo", "header": "Welkom Django!"},
}
def __call__(self, request):
response = self.get_response(request)
return response
In the next section you'll see where the magic happens ...
Template responses and middleware hooks
A middleware can have hooks, that is, class methods which intercept Django responses or views during their lifecycle.
My requirements were clear: I needed to inject self.translations
into the view's context. (The real app is more complex and loads translations from multiple files).
For those new to Django, the context is any data that the view should render to the user.
Luckily the middleware offers an hook made for context manipulation: process_template_response
. It takes request and response, and has access to the context through response.context_data
.
Here's the implementation:
# file: middleware_demo/middleware.py
class JSONTranslationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.translations = {
"en": {"greeting": "Hello", "header": "Welcome Django!"},
"nl": {"greeting": "Hallo", "header": "Welkom Django!"},
}
def __call__(self, request):
response = self.get_response(request)
return response
def process_template_response(self, request, response):
response.context_data["translations"] = self.translations
return response
Since process_template_response
has access to the request you can query any key on request.META
.
Imagine I want to serve self.translations["nl"]
only if the user has the dutch language in the Django HTTP_ACCEPT_LANGUAGE
header. Here's how it would look like:
# file: middleware_demo/middleware.py
class JSONTranslationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.translations = {
"en": {"greeting": "Hello", "header": "Welcome Django!"},
"nl": {"greeting": "Hallo", "header": "Welkom Django!"},
}
def __call__(self, request):
response = self.get_response(request)
return response
def process_template_response(self, request, response):
if "nl" in request.META["HTTP_ACCEPT_LANGUAGE"]:
response.context_data["translations"] = self.translations
return response
return response
Only the sky is your limit with a middleware.
Hold tight, in the next section we'll wire up all the pieces together.
But wait ...
If you're an intermediate Django developer you might argue that a middleware is just too much, I could have checked request.META
directly in the view.
But the point of having a middleware is a clear separation of concerns, plus the ability to plug the middleware in as needed.
Setting up the view and the url
Open up make_me_middleware/urls.py
and include the urls from middleware_demo:
# file: make_me_middleware/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("middleware_demo.urls")),
]
Then create a new file called urls.py
in middleware_demo:
# file: middleware_demo/urls.py
from django.urls import path
from .views import index
urlpatterns = [
path("demo/", index),
]
Finally let's create a Django view with TemplateResponse
:
# file: middleware_demo/views.py
from django.template.response import TemplateResponse
def index(request):
context = {}
return TemplateResponse(request, "middleware_demo/index.html", context=context)
This view is a bit different from a stock Django view like those you can see in the introductory tutorial.
It uses TemplateResponse
, a special helper which is hooked by process_template_response
from the middleware. TemplateResponse does not return any data back to the user until it reaches the middleware.
To touch the result by hand let's finally create a Django template.
Building a Django middleware: setting up the template
My Django template is an humble HTML page, yet with an interesting addition: the json_script
Django filter.
Starting from any key from the context json_script
creates a new script tag inside the page, with the desired id.
Create a new folder named middleware_demo/templates/middleware_demo
and inside it create index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Make me a Django middleware</title>
</head>
<body>
<div id="root"></div>
</body>
{{ translations|json_script:"translations" }}
As the last step we're going to activate the middleware in a minute.
Activating the middleware
First things first open up make_me_middleware/settings.py
and enable the app:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# ENABLE THE NEW APP
'middleware_demo.apps.MiddlewareDemoConfig'
]
Then enable the middleware:
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",
# ENABLE THE NEW MIDDLEWARE
"middleware_demo.middleware.JSONTranslationMiddleware",
]
Keep in mind that middlewares are run from top to bottom when there is a request to the Django application and from bottom to top when the response leaves the app.
Now run the server:
python manage.py runserver
and visit the page http://127.0.0.1:8000/demo/. Open up a browser's console and you should have access to the dictionary, now a JavaScript object which has been injected by the middleware:
Now you can parse the text with JSON.parse as well as accessing any key on the object:
JSON.parse(document.getElementById('translations').textContent).en.greeting
Great job!
Conclusion
In this tutorial you learned how to create a Django middleware, how to inject data into the context, and how to use json_script in your templates.
I really hope you learned something new! Django middlewares are the right place for abstracting custom functionality that needs to alter the request/response cycle of a Django application.
Keep your Django apps clean.
Thanks for reading and stay tuned!