Django inclusion tags can modify page context 🤯

Django inclusion tags are super useful, but they have a fun quirk that could create some havoc.

The setup

Creating custom template tags in Django takes a little to get used to, but a coworker recently stumbled on a weird bug and it took me by surprise. It was related to a custom inclusion tag and the takes_context parameter.

An example inclusion tag yanked from the Django documentation:

@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
    return {
        'link': context['home_link'],
        'title': context['home_title'],

However, instead of returning a dictionary from the function, my coworker had modified the context and returned it directly:

@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
    context['link'] = context['home_link']
    context['title'] = context['home_title']
    return context

Maybe unsurprisingly, the template tag worked just fine, however it had unintended ramifications if the variables being returned conflicted with a key already defined in the page context. So, if the view code contained link in the page context, then the second example of jump_link would override the that template variable from that point on -- making the value of the view context dependent on where the inclusion tag is placed in the template.

The following Django template should hopefully make clear what could potentially happen:

Initial view context variable: {{ "{{ link " }}}}<br />
{{ "{% jump_link " }}%}<br />
Clobbered view context variable from the inclusion tag: {{ "{{ link " }}}}<br />

This _makes sense_ once I realized that modifying the context while the template is rendering is going to affect rendering later portions of the template, however I definitely did not expect it to happen. In general, I would recommend against changing the context inside of the template tag because it makes debugging what happens in the template much more difficult.

Related Content

Hi, I'm Adam 👋

I've been a backend programmer for ~20 years in a variety of different languages before I discovered Python 10 years ago and never looked back. alldjango includes all the hard-won experience I've gained over the years building production-scale Django websites.

Feel free to reach out to me on Mastodon or make a GitHub Issue with questions, comments, or bitter invectives.

All code is licensed as MIT.


Made with 🤟 and built with Coltrane 🎵

© Adam Hill