DRY: Template Rendering with Context Processors

Dark blue background with colorful doodles, flying balloons, and a pink hair pony.
Image by Annie Ruygt

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.

Let’s get some context

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.

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":

Staging Environment

and this is how it appears on the staging when the ENVIRONMENT setting is set to "staging":

Development Environment

Django really flies on Fly.io

You already know Django makes it easier to build better apps. Well now Fly.io makes it easier to _deploy_ those apps and move them closer to your users making it faster for them too!

Deploy a Django app today!ย ย โ†’

Closing thoughts

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!