in blog | Django Beats |
---|---|
original entry | DRY: Template Rendering with Context Processors |
Mariusz Felisiak, a Django and Python contributor and a Django Fellow, explores how to simplify template rendering with context processors. Django on Fly.io is pretty sweet! Check it out: you can be up and running on Fly.io in just minutes.
Django has many hidden gems that are neither widely used nor well known. In this article, I will discuss context processors which are one of my favorites. They allow following the DRY (don’t repeat yourself) principle in template rendering and keep your code more maintainable. Let’s check how it works and why it’s worth using.
Context is a dictionary-like object that a template renders together with a request. In most cases we pass it as a standard dictionary to the render()
shortcut. For example:
# views.py
from django.shortcuts import render
def my_hello_view(request):
greetings = "Welcome aboard ⛵"
return render(request, "my_app/hello.html", context={"greetings": greetings})
<!-- hello.html -->
{{ greetings }}
So far, so good, but what if we have information that should be available for every template. How do we inject them without rewriting everything and repeating ourselves? 🤔
Context processors are helpers designed for exactly this. They are functions that accept a request and return a dictionary that is populated to the template context when any template is rendered. See the following stub:
def my_context_processor(request):
...
return {"key": value}
The list of used context processors is controlled by the "context_processors"
option of the TEMPLATES
setting.
Django includes many built-in context processors, but we can also implement our own. To put it all together, let’s see a full example.
We need an existing or new Django project. Here are some great resources for getting started with Django or deploying your Django app to Fly.io.
With a project ready, let’s get started!
Let’s assume that we want to share information about the current version and environment of our project for all templates. Environment can be defined as a setting:
# my_project/settings.py
ENVIRONMENT = "staging"
and version is stored in the project:
# my_project/__init__.py
VERSION = (2, 23, 1, "rc", 2)
__version__ = ".".join(str(part) for part in VERSION)
We can define a new context processor called metadata()
that will add both information to the template context:
# my_project/context_processors.py
from django.conf import settings
from . import __version__
def metadata(request):
return {
"version": __version__,
"environment": settings.ENVIRONMENT,
}
Then we add the dotted Python path to it ("my_project.context_processors.metadata"
) to the list of context processors:
# my_project/settings.py
...
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
# Our context processor:
"my_project.context_processors.metadata",
],
},
},
]
That’s it! Information about version
and environment
are now available for all templates 🥳
We can use them, for example, to display a header about the current version:
<!-- templates/main.html -->
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
...
<style>
.version-header {
text-align: center;
font-size: 1.3em;
font-weight: 400;
padding: 20px;
}
.staging-header {
background-color: yellow;
color: black;
}
.development-header {
background-color: green;
color: white;
}
</style>
</head>
<body>
{% if environment != "production" %}
<div class="{{environment}}-header version-header">
{{ environment|capfirst }} environment, version {{ version }}.
</div>
{% endif %}
...
</body>
</html>
Believe me, it has saved me many times from making a mistake on a production environment. This is what it looks like in the development version when the ENVIRONMENT
setting is set to "development"
:
and this is how it appears on the staging when the ENVIRONMENT
setting is set to "staging"
:
Context processors are really handy for sharing information between templates and I believe they should be used more widely.
Built-in context processors mainly provide different settings to the templates, but there are more sophisticated uses, such as providing an instance of currently logged-in user
with its permission by django.contrib.auth.context_processors.auth
, or passing token needed by CSRF (Cross Site Request Forgery) protection by django.template.context_processors.csrf
. Sky’s the limit 🚀
Do you already have an idea how to use them in you project? 💡 Try them and share!