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>&copy; 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>&copy; 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 comment
Thomas

Is « {% set debug_info = debug_info() %} » legal in Django templates ?

Peter Bengtsson

You know what, that's Jinja syntax. Imagine the equivalent in Django templates :)

Anonymous

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?

Peter Bengtsson

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.

Anonymous

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 ^^.

Tyler Davis

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.

Peter Bengtsson

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.

Emma

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

Luke Plant

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

Saverio Trioni

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

Your email will never ever be published.

Related posts

Go to top of the page