Dec 13

Django Template Debugging Made Easier with django-template-repl

When working with the Django template language, specifically writing template tags or trying out new template tags that are not well documented, it's easy to fall into a testing loop that involves modifying your code, saving the file, causing runserver to restart, which could take some time for large projects, switching to your web browser, hitting reload, and viewing the results. This workflow can be repetitive and unproductive. I decided to improve template interpreter interactivity by writing a REPL for it, and I released the project as django-template-repl, which is freely available on Github and PyPI. I was surprised and happy to see it was well accepted judging from the twitter chatter and github statistics, so I wrote this to better explain how to use it.

One of the greatest advantages of Python, Lisp, and other programming languages is the ease of debugging and understanding code behavior provided by REPLs. A REPL, which stands for Read-Eval-Print Loop, is a shell that gives you an interactive command line session with your language interpreter. The two REPLs most Django users should be familiar with are ipython and (i)pdb. These tools are incredibly helpful and really boost the usability of Python. I spend more time inside ipdb than I read code output in a web browser. django-template-repl provides this type of tool for the Django template language.

Invoking the REPL

There are two ways to invoke the REPL. One uses a management command and the other uses a template tag. Run "python manage.py templateshell" to open the REPL:

$ python manage.py templateshell
>>> test
test
>>> {% if 1 %}
... It is true!
... {% else %}
... It is false!
... {% endif %}
It is true!

This behaves almost exactly like a python shell. It uses the readline library, so it handles command history. It also detects when you are inside a block tag, giving you decent multi-line support.

Providing context

Context can be provided in a number of ways. You can pass a context dictionary with the command-line, for example:

$ python manage.py templateshell --context "{'testvar': 'this is a string'}"
>>> {{ testvar }}
this is a string

Note that this actually runs eval() on the context string. That's the first time I've ever used eval(), but it seems not so hacky for this purpose.

Context can also be extracted from a specific URL in your project. This is accomplished by using Django's test client to capture context used in a page and bootstrap a REPL with the captured context. For example:

$ python manage.py templateshell --url /admin/
>>> {{ title }}
Log in
>>> {{ user }}
AnonymousUser

If you need to capture context at a specific place in your page, like in a for loop, you can use the {% repl %} template tag. This will halt the template rendering process at a specific point and replace your runserver shell with a template REPL loaded with the context used in the calling template.

Use the same tools with PDB

The management command and template tag give you nice ways to capture context, so I've made it possible to use pdb or ipdb with them. The management command has an option, --pdb. If used in conjunction with --url or --context, you can load context into a PDB shell. For example:

$ python manage.py templateshell --url /admin/ --pdb
...
ipdb> vars
Out[0]: 
['app_path',
 'error_message',
 'root_path',
 'title',
 'MEDIA_URL',
 'LANGUAGES',
 'LANGUAGE_BIDI',
 'LANGUAGE_CODE',
 'perms',
 'messages',
 'user']
ipdb> title
Out[0]: u'Log in'
ipdb> user
Out[0]: <django.contrib.auth.models.AnonymousUser object at 0x1019d7490>

This functionality is also possible with the template tag, using {% repl pdb %}.

I do hope these tools help somebody out. I feel like it is much easier to debug issues with template context and try new template tags. If you want to try it out, just run

pip install django-template-repl
and add 'template_repl' to your installed apps.