All Things Pythonic

Django vs. Cheetah: 1-0

by Guido van van Rossum

January 31, 2006



Summary

I finally got some time to play with Cheetah and Django templates. After maybe an hour with each, I like Django best. Hopefully people are still watching this space...


Encouraged by Fredrik Lundh, I took a look at Django templating, separate from the rest of Django. This works fine, except there's a mysterious environment variable DJANGO_SETTINGS_MODULE that is somehow required. That may be fine for framework users, but it's a bit harsh if all you want to do is use the templating library. Setting it to empty didn't help. I ended up uing this hack:

import os os.environ["DJANGO_SETTINGS_MODULE"] = "__main__" from django.core.template import Template

With that out of the way, I managed to run this program:

t = Template("<h1>Hello {{name}}</h1>") print t.render({"name": "Phillip"}) print t.render({"name": "Adrian"})

This is nice! The output of course is:

<h1>Hello Phillip</h1> <h1>Hello Adrian</h1>

Unfortunately I had trouble installing Django for the Python 2.5 alpha that I've got installed; it uses the fancy new ez_install stuff, which is great when it works, but there's no Python 2.5 download of setuptools on PyPI yet, so it doesn't.

I tried the same exercise using Cheetah. Not too different:

from Cheetah.Template import Template names = {"name": "Tavis"} t = Template("<h1>Hello $name</h1>", searchList=[names]) print t names["name"] = "Ian" print t

with the following output:

<h1>Hello Tavis</h1> <h1>Hello Ian</h1>

(Ironically, Cheetah also failed under Python 2.5 -- Python 2.5 inadvertently (I believe) changes the syntax that's allowed before "from __future__ import ..." to disallow assignment to __author__ and __version__. That was easier to fix though -- commenting out the "from __future__ ..." line was sufficient.)

Side-by-side Comparison (BEWARE! I haven't used either system to build a real website yet. I'm just looking at the API and templating language designs.) Django definitely feels more "modern" than Cheetah. The templating languages are fairly similar, with Django writing {{foo.bar}} where Cheetah writes $foo.bar or ${foo.bar} for variable interpolation (== substitution). The biggest difference is that Cheetah allows pretty much arbitrary Python call syntax, e.g. ${foo.bar('hello', $name, 42+42)}. Yes, you have to prefix variable references in the argument list with another $, and there are confusing rules about when the $ is optional. Django only allows names separated by dots, using the old Zope trick of trying x['y'], x.y and x.y() when the input is {{x.y}}. When y looks like a number, it'll even try a sequence index, e.g. {{x.4}} means x[4]. (Aside: something similar is also found in web.py, but there it bothers me, because it's invoked from normal-looking Python code. E.g. foo["x"] and foo.x are equivalent, when foo is a "Storage" object. But this leaves me wondering, what does foo.keys mean? The keys method or the value stored under "keys"? Zope 2 was similarly confusing with implicit acquisition.) A big difference: if Django doesn't find a name, it inserts nothing; but Cheetah raises an exception. ISTM that Django is more user-friendly here, even if its approach could be considered error-prone (typos are easily missed since they simply suppress a small bit of output). In my experience, missing variables are very common in substitution data, and Cheetah requires you to provide explicit default values in this case. Both templating languages also have a "statement" syntax to complement their "expression" interpolating syntax. In Django, this is written as {% keyword %} while in Cheetah you use #keyword. Here I initially found Cheetah a bit more readable, since it resembles C preprocessor syntax: #if $name <h1>Hello $name</h1> #else <h1.Hello there</h1> #end if This is written in Django as: {%if name%} <h1>Hello {{name}}</h1> {%else%} <h1>Hello There</h1> {%endif%} which is harder on the eyes and prints more unnecessary whitespace. But then I stumbled upon Cheeta's inline form, which is pretty unreadable due to the symmetric delimiters: # if $name # <h1>Hello $name</h1> # else # <h1>Hello There</h1> # end if # Both languages have tons of other statement-level constructs, to do loops, set variables, invoke other templates, define blocks that can be used by other templates, and more. I haven't explored this much yet, but they both seem to cover a similar terrain. I guess this is what template authors actually need; or perhaps it points to some common ancestor in PHP or JSP? If you need your variable interpolations to be HTML-escaped (replacing "<" with "<" and so on), Cheetah lets you specify a default filter in the template (#filter) or when the template is created in Python code. In Django you must add a pipeline to the interpolation syntax, like this: ${foo.bar|escape}. Both support various other filters as well, with Django really going wild. I'm somehow surprised that HTML-escaping isn't the default -- failing to HTML-escape data is the number one vulnerability leading to XSS attacks. And in theory you should almost never need to provide HTML for interpolation: you're supposed to invoke HTML fragments using #include or {%include%}. Cheetah at least provides a way to make HTML-escaping the default filter throughout a template. Python API See the examples near the top. I like Django's version better: you pass the variable bindings in to the render() method. Cheetah lets you specify multiple dictionaries with variable bindings, which are searched one after another, but it bothers me that these all have to be passed to the Template() constructor instead of to the render method (which is called __str__() in Cheetah :-). Django's template compilation is much simpler and IMO more elegant than Cheetah: Django parses the template text into nodes of various types using a big regular expression, and each node has an appropriate render() method. Rendering the template in a given context simply concatenates the results of rendering each node in that context. I imagine this could easily be turned into a generator compatible with WSGI. Cheetah, OTOH, compiles each template to a Python class! This is much slower, and in my experience brittle -- on my first attempt I introduced a syntax error in the template that caused a Python syntax error in the resulting Python class, which was hard to debug. It also seems overkill, and I worry that it might cause security problems -- given that the compiler isn't too smart (see above) I could see a malicious template author "breaking out" of the templating languages and invoking unauthorized Python code. Now, I wouldn't let people I don't trust edit templates on my website anyway, but it appears to be a common pattern that certain people are allowed to edit templates but not code. Cheetah blurs the distinction a little too much for my comfort.

Other Templating Solutions I guess we're closer than I thought to the mix-and-match approach that I requested in my previous blog. Cheetah is just a templating engine, and several web frameworks use or recommend it, e.g. web.py and Subway. (Is Subway dead? The website is super-incomplete and it still downloads an old Cheetah version.) Django's templates are almost usable independent from the rest of Django; I expect the situation will improve once they release the magic-removal branch. I didn't find too many other templating solutions. TurboGears uses Kid which XML-based, like Nevow, TAL etc. IMO these are no contenders because they are XML-based. As the Django folks mention, they use templates to generate non-HTML text files as well. And even if they could be used for this, the verbosity of the XML syntax and the inability to do a proper if-then-else in XML make template writing using XML a non-starter.

Final Thought Django and Cheetah both define a 'Template' class. Python's standard library also has a Template class (in string.py), which serves a similar purpose (but with only a fraction of the functionality). All these have different APIs. But are the differences important? It seems pretty arbitrary whether to use Template("...").render(locals()) (Django), or str(Template("...", searchList=[locals()]))` (Cheetah) or ``Template("...").substitute(locals()) (string.py). Perhaps we could attempt some standardization here similar to WSGI?

Talk Back!

Have an opinion? Readers have already posted 86 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Guido van van Rossum adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Guido van Rossum is the creator of Python, one of the major programming languages on and off the web. The Python community refers to him as the BDFL (Benevolent Dictator For Life), a title straight from a Monty Python skit. He moved from the Netherlands to the USA in 1995, where he met his wife. Until July 2003 they lived in the northern Virginia suburbs of Washington, DC with their son Orlijn, who was born in 2001. They then moved to Silicon Valley where Guido now works for Google (spending 50% of his time on Python!).

This weblog entry is Copyright © 2006 Guido van van Rossum. All rights reserved.