If you came here, you probably know what is a context processor, but let's quickly recap. It's a function, that returns dictionary of values. Those values will be injected to rendering context without explicitly defining them in view. They allows us to refactor commonly used variables out of views.
The most well-known context processor is Authentication, making it possible to use user
and perms
variable in our templates, pointing to currently authenticated user. Other useful build-in context processors are Messages and Request (full list here).
To enable context processor, we need to put fully qualified path in context_processors
array in TEMPLATE setting. Let's take a look at custom context processor.
Context processor example
# context_processors.py
from models import User
def users_online(request):
return {
"online_count": User.objects.online().count()
}
usage:
# in settings.py, under TEMPLATES[0]['OPTIONS']
'context_processors': [
...,
'project.context_processors.users_online'
]
Problem
Context processors are called every time we're rendering template. Even if provided variables aren't used, they are prepared and injected into context. Some processors may be expensive to calculate, so it may be better to postpone their execution.
Solution
Thanks to Django Template Language behaviour, it's possible to use functions as variables. When used in template, they return value will be used instead. I've written decorator to utilize that:
# utils.py
import functools
def memoize(method):
@functools.wraps(method)
def memoizer(*args, **kwargs):
method._cache = getattr(method, '_cache', {})
key = args
if key not in method._cache:
method._cache[key] = method(*args, **kwargs)
return method._cache[key]
return memoizer
Usage:
# context_processors.py
from models import User
from utils import memoize
def users_online(request):
return {
"online_count": memoize(
lambda: User.objects.online().count()
)
}
# can also be used like this
@memoize
def heavy_computations(a, b, c):
...
Explanation:
Memoize remembers return value of a function call, under the key made from it's arguments. For each arguments combination it's called at most one time. In our example, we're using argument-less lambda functions, so it's called either 0 or 1 time (on first usage).
Now our "online_count" value is lazy - it's calculated when it appear for first time inside templates. If not, it's not executed, and we save one DB query. Maybe it's not that much, but when you have many context variables similar to that one, it may really make a difference.
I'm using memoize
in all my context processors. If you care about performance of your application, use it too!