The idea with template context processors in Django is to inject some defaults thing to be available when rendering a template that is rendered with a request.
I.e. instead of...:
def view1(request):
context = {
'name': 'View 1',
'on_dev_server': request.get_host() in settings.DEV_HOSTNAMES
}
return render(request, 'view1.html', context)
def view2(request):
context = {
'name': 'View 2',
'other': 'things',
'on_dev_server': request.get_host() in settings.DEV_HOSTNAMES
}
return render(request, 'view2.html', context)
And in your nominal templates/base.html
you might have something like this:
...
<footer>
<p>© You 2015</p>
{% if on_dev_server %}
<p color="red">Note! We're currently on a dev server!</p>
{% endif %}
</footer>
...
Instead you do this trick; in your settings.py
you write down the list of defaults plus the one you want to always have available:
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.static",
"myproject.myapp.context_processors.debug_info",
)
And to accompany that you define your myprojects/myapp/context_processors.py
like so:
def debug_info(request):
return {
'on_dev_server': request.get_host() in settings.DEV_HOSTNAMES,
}
So far so good.
However, there's a problem with this. Two problems in fact.
First problem is that when all the templates in your big complicated website renders, it's quite possible that some pages don't need everything you set up in your context processors. That might mean a heck of a lot of extra computation when it won't ever be displayed.
For example, I have a project where most pages have a sidebar where I show "Trending Events" which is something I compute in a context_processors.py
function called def sidebar_events(request):
. But the sidebar is not always shown and on the pages where it's not shown it's a waste to compute the stuff that sidebar_events
computes. Also, I have management pages which uses a totally different base.html
template. So there's a big chance you're wasting precious CPU.
Another problem is that of code-readability (aka. how frustrating is this to debug for someone else or yourself after months of idle activity). If you're skimming through your base.html
and you see this "random" variable called on_dev_server
it's very very hard to tell where the heck that's defined. Hopefully grepping the whole source code is a way to go. A much better way to solve that problem would be sensible namespace naming.
And also, by being too liberal with globally scoped variables there's a chance you might clash from a different piece of functionality that uses the same variable names. That chance is smaller when you use namespaces.
So, to remedy this, let your template context processor functions return closures. It wraps the request
automagically.
Let's rewrite our trivial example from above, the context_processors.py
should now look like this:
def debug_info(request):
def inner():
return {
'on_dev_server': request.get_host() in settings.DEV_HOSTNAMES,
}
return {'debug_info': inner}
Now executing that becomes more optional and more deliberate in the template instead. E.g.
...
<footer>
<p>© You 2015</p>
{% set debug_info = debug_info() %}
{% if debug_info['on_dev_server'] %}
<p color="red">Note! We're currently on a dev server!</p>
{% endif %}
</footer>
...
This makes it more explicity which is a good thing. It also has the potential to be avoided if the stuff in there isn't needed in some templates.
Comments
Post your own commentIs « {% set debug_info = debug_info() %} » legal in Django templates ?
You know what, that's Jinja syntax. Imagine the equivalent in Django templates :)
http://late.am/post/2015/05/07/optimize-python-with-closures.html
Wouldn't be better to implement this with templatetags? As you said, context processors are for inserting variables for all your templates so, instead of doing that, why not write a templatetag and call whenever you need it?
It's not a bad point but isn't templatetags (that take in the request for context) there to inject content into the template? With template context processors you get *information* which you then deliberately insert into the template when you will.
I get your point. I guess there is a thin line between when to use one or the other in these cases but in my opinion, template tags feel less hacky than using closures for context processors. Mb some1 else can give his opinion ^^.
This is the difference between a filter or simple_tag and an assignment_tag. Definitely agree that if performance is a consideration, this is more cleanly accomplished with an assignment tag.
I don't even know what an "assignment_tag" is.
I've been living in Jinja-land so long that maybe this is a newfangled feature in modern Django templates.
Even though it was already possible to write such a tag before assignment_tag is indeed a pretty new feature it's only 3 years old https://docs.djangoproject.com/en/1.8/releases/1.4/#assignment-template-tags
An extra trick to use with this is to memoize the closure so that it is only called once if used multiple times. I use this decorator to convert closures into memoized ones:
def memoize_nullary(f):
"""
Memoizes a function that takes no arguments. The memoization lasts only as
long as we hold a reference to the return value.
"""
def func():
if not hasattr(func, 'retval'):
func.retval = f()
return func.retval
return func
Why not a simple lazy object?
from django.utils.functional import SimpleLazyObject
def debug_info(request):
return {
'on_dev_server': SimpleLazyObject(lambda: request.get_host() in settings.DEV_HOSTNAMES),
}
The lambda won't be executed until needed and then the result will be inserted in the object's dlegation chain instead of the lambda.
>>> s = SimpleLazyObject(lambda: True)
>>> s
<SimpleLazyObject: <function <lambda> at ...>>
>>> if s: print 8
...
8
>>> s
<SimpleLazyObject: True>
>>>
>>> s = SimpleLazyObject(lambda: False)
>>> s
<SimpleLazyObject: <function <lambda> at ...>>
>>> if s: print 8
...
>>> s
<SimpleLazyObject: False>
>>>