excess.org

Ian Ward

Consulting
Boxkite Inc.
Software
CKAN contributor/tech lead
PyRF primary contributor
Urwid author
Speedometer author

Presentations
Contributing to Open Source
IASA E-Summit, 2014-05-16
Urwid Applications
2012-11-14
Urwid Intro
2012-01-22
Unfortunate Python
2011-12-19
Django 1.1
2009-05-16

Writing
Moving to Python 3
2011-02-17
Article Tags

Home

Ian Ward's email:
first name at this domain

wardi on OFTC, freenode and github

Locations of visitors to this page

Paranoid Django Templates

Posted on 2012-04-09.

If you've ever wanted to know if a Django template is using a variable it shouldn't be, or not using a variable it should, this code will make both cases fail loudly. Django's default template behaviour is to silently replace missing variables with an empty string, and ignore unused variables.

To use this code you can either:

  1. wrap your Context (or RequestContext) object in your view with a ParanoidContextProxy that will fail on any attempt to access a missing variable, or
  2. use the paranoid_render_to_response function (or similar) to also require that every variable you pass be used in the template.

Paranoid Django Templates Source Code:

class ParanoidKeyError(Exception):
    """
    This can't be a KeyError subclass or Django template
    rendering will swallow it.
    """

class ParanoidContextProxy(object):
    """
    This is a poor-man's proxy for a context instance.

    Make sure template rendering stops immediately on a KeyError.
    """
    def __init__(self, context):
        self.context = context
        self.seen_keys = set()

    def __getitem__(self, key):
        self.seen_keys.add(key)
        try:
            return self.context[key]
        except KeyError:
            raise ParanoidKeyError('ParanoidKeyError: %r' % (key,))

    def __getattr__(self, name):
        return getattr(self.context, name)
    def __setitem__(self, key, value):
        self.context[key] = value
    def __delitem__(self, key):
        del self.context[key]

@contextlib.contextmanager
def paranoid_context_manager(context, expected_keys=None):
    """
    use ParanoidContextProxy *and* make sure keys in expected_keys iterable
    are all referenced inside the with statement.
    """
    yield ParanoidContextProxy(context)

    if expected_keys:
        unref = set(expected_keys).difference(context.seen_keys)
        if unref:
            raise ParanoidKeyError('Keys not referenced: %r' % (unref,))

def paranoid_render_to_response(template_name, dictionary=None,
        context_instance=None, *args, **kwargs):
    """
    use ParanoidContextProxy *and* require that all variables passed
    in the data dictionary are used by templates at least once.
    """
    if dictionary is None:
        dictionary = {}
    with paranoid_context_manager(context_instance, dictionary.keys()) as c:
        return render_to_response(template_name, dictionary, c, *args, **kwargs)

I'll put this up on github once I've written some tests and better docs.

Tags: Django Software Python