1. Problem

You wish to use the Cheetah templating library with Quixote.

2. Solution

Aaron Brady described using Cheetah under Quixote.

3. Discussion

Aaron's approach simply creates a specific template in a Quixote handler. A more complicated alternative approach would be the StaticFile approach described on the TemplatingWithZpt page.

4. A more complicated alternative approach ;-)

Here's a little module that implements a StaticDirectory-like class that is Cheetah-aware.

Simple intro: if you have a Cheetah Directory handling requests at /foo/, then a call to /foo/bar will look for bar.tmpl (a Cheetah template) or bar.py (a Python script) and use that element to process the request. See the source (below) for more information.

Warning: it's slow. Templates are recompiled on every request. Perhaps someone could fix that if this actually gets used. Precompliation would be useful if you make use of the use_cache parameter when creating your Cheetah Directory.

There's a sample application that uses this module. To run it out of the box, you'll need Quixote, Cheetah and Twisted (but you can always rewrite the server script, and use a different Web server).

"""
Tools to make Cheetah play nicely with Quixote.
Author: Graham Fawcett, 
"""

from quixote.util import StaticDirectory
from Cheetah.Template import Template
import os
from StringIO import StringIO



class BaseHandler:

    def make_environ(self, request):
        environ = {
            'cgi': request.environ,
            'form': request.form,
            'request': request,
            'context': getattr(request, 'context', None),  # see qxmods module
            'template': self.filename,
            'thisdir': request._thisdir + '/',   # see CheetahDirectory
            'rootdir': request._rootdir + '/',   # see CheetahDirectory
        }
        return environ



class CheetahTemplate(BaseHandler):
    """
    Represents a Cheetah template (a file ending with .tmpl) in your Web app.

    Todo: probably should pre-compile the Template;
    without precompilation, request handling is slow...
    """

    def __init__(self, fn):
        self.filename = fn

    def __call__(self, request):
        request.response.set_header('Cache-control', 'no-cache')
        environ = self.make_environ(request)
        x = str(Template(file=self.filename, searchList=[environ]))
        return x



class PythonScript(BaseHandler):
    """
    Represents a Python script (a file ending with .py) in your Web app.
    Python scripts must define a function, 'def respond(request)' which
    will process a Quixote request. They may return any valid Quixote
    response type. Alternately, you can 'return printed', which will
    return any text which the script printed to stdout.
    """

    def __init__(self, fn):
        self.filename = fn
        codeobj = compile(
                    file(self.filename).read().strip() + '\n\n',
                    os.path.basename(self.filename),
                    'exec')
        self.namespace = {}
        exec(codeobj, self.namespace)

    def __call__(self, request):
        request.response.set_header('Cache-control', 'no-cache')
        environ = self.make_environ(request)
        printed = StringIO()
        environ['printed'] = printed
        self.namespace.update(environ)
        try:
            sys.stdout = printed
            x = eval('respond(request)', self.namespace)
            if x == printed:
                x = printed.getvalue()
        finally:
            sys.stdout = sys.__stdout__
        return x



class CheetahDirectory(StaticDirectory):

    """
    Like StaticDirectory, wrap a filesystem directory containing static files
    as a Quixote namespace. But also allow special handling for .tmpl and .py files.

    See StaticDirectory.__init__ for signature of the constructor.
    """

    def _q_index(self, request):
        """
        Return a response from index.tmpl or index.py, if exists;
        else return a directory listing if allowed.
        """
        item = self._q_lookup(request, 'index')
        if item:
            return item(request)
        else:
            return StaticDirectory._q_index(self, request)


    def _q_lookup(self, request, name):
        """
        Get a file from the filesystem directory and return the StaticFile
        or StaticDirectory wrapper of it; use caching if that is in use.
        """

        # set _thisdir and _rootdir attributes on the request.
        # _rootdir is set once, at the first occurrence of a CheetahDirectory.
        if not hasattr(request, '_rootdir'):
            request._rootdir = self.path
        request._thisdir = self.path

        if name in ('.', '..'):
            raise errors.TraversalError(private_msg="Attempt to use '.', '..'")

        if self.cache.has_key(name):
            # Get item from cache
            item = self.cache[name]
        else:
            # check if there's a dot in the name.
            # if not, then /foo might refer to /foo.tmpl or /foo.py.

            # Get item from filesystem; cache it if caching is in use.
            item_filepath = os.path.join(self.path, name)
            item = None
            if os.path.isdir(item_filepath):
                item = self.__class__(item_filepath, self.use_cache,
                                      self.list_directory,
                                      self.follow_symlinks, self.cache_time,
                                      self.file_class)
            elif os.path.isfile(item_filepath):
                item = self.file_class(item_filepath, self.follow_symlinks,
                                       cache_time=self.cache_time)
            elif not '.' in name:
                tmpl_name = item_filepath + '.tmpl'
                py_name = item_filepath + '.py'
                if os.path.isfile(tmpl_name):
                    item = CheetahTemplate(tmpl_name)
                if os.path.isfile(py_name):
                    item = PythonScript(py_name)
            if not item:
                raise errors.TraversalError
            if self.use_cache:
                self.cache[name] = item
        return item


Edd Dumbill posted a blog entry that includes the above script updated to cache compiled Cheetah templates.


CategoryCookbook