Cheetah recipes

Below are short HOWTOs (tutorials) on how to accomplish common tasks in Cheetah. Feel free to add your own. See also the Tips section in the Cheetah Users' Guide.

-- MikeOrr? - 02 Apr 2002

---

Non-Standard Installation Process

The standard setup routine didn't work for me on my Win2000 system, (I haven't bothered to setup my environment to work outside of IDLE), so here is a way to manually install Cheetah into Python 2.2 (and maybe 2.1?).

  1. in your Libsite-packages directory, create a new directory named 'Cheetah'.
  2. copy everything in the distribution's 'src' directory to your new 'Cheetah' directory.
  3. move all the files in the 'src' directory that are not in a subdirectory (e.g. Tools, Tests, etc.) to the 'Cheetah' directory

That's it! If you now go to a Python command line, you should be able to successfully do a 'from Cheetah.Template import Template'

If you care to do a further test, you can go to the 'Tests' directory under 'Cheetah' and run 'Test.py' and it should be successful.

Enjoy!

ViCy? -- 30 July 2002

---

Using Tools.SiteHierarchy

It took me a little while to manage to use this in a simple way so I thought I'd document what I did for posterity and save someone else some time.

What I did was create my own MenuHierarchy.py file which inherited from Hierarchy in order to customise the output methods:

class MenuHierarchy(Hierarchy):
     # Add a couple of extra parameters to initialisation method
     _width = 0
     _bgcolor = ''
     def __init__(self, hierarchy, currentURL, prefix='', menuCSSClass=None,
                                      crumbCSSClass=None, width=0, bgcolor=""):
              Hierarchy.__init__(self, hierarchy, pathToURL(currentURL), prefix, menuCSSClass, crumbCSSClass)
              self._currentURL = pathToURL(currentURL)
              self._width = width
              self._bgcolor = bgcolor

     def menuLink(self, url, text, indent):
              #format specific stuff here

Note that I have added extra initialisation variables for the class and stored away for later use in menuLink.

In my main SiteTemplate.tmpl I use the above as follows:

#attr hierarchy = [('/index.html', 'home'),
                            ('/about.html', 'About Us'),
                            [('/services', 'Services'),
                             [('/services/products/index.html', 'Products'),
                              ('/services/products/thimble.html', 'The Thimble'),
                              ],
                             ('/services/prices.html', 'Prices'),
                             ],
                            ('/contact.html', 'Contact Us'),
                            ]

     #set menu = MenuHierarchy($hierarchy, $self._filePath, menuCSSClass='nav', crumbCSSClass='crumb', prefix='/',
              width=125, bgcolor="#CCCC99")
     #echo menu.menuList()

The actual #set/#echo occurs wherever in the template is appropriate to create the menu bar.

Notice the use of $self._filePath - I pass in the automatically created member variable, and convert it into the name of the associated .html file so it's all done automatically and there's no need to do anything in the individual template file.

Anyway, hope the above is useful.

-- RobertCowham? - 12 Aug 2002

---

Another way to do a yahoo bar

I wanted just a simple yahoo bar on my site, not a multiline menu hierarchy. I decided to hardcode the first link ("Home") since it's on every page. In my site template I have:

#attr $crumbs = ()
<A HREF="/">Home</A>
#for $url, $label in $crumbs
&nbsp;>>&nbsp;<A HREF="${url}">${label}</A>
#end for

Any page template subclass that wants to add elements overrides $crumbs:

#attr $crumbs = [('/writings/', 'My Writings'), 
                                      ('/writings/book1/', 'This Book's Title')] 

-- MikeOrr? - 12 Aug 2002


This recipe describes a way to implement WebwareSkins or, maybe better, a template selection process during runtime. It is a variation of the containment approach.

-- StephanDiehl? - 2 Nov 2002 <br/>

---

Here is a Makefile that can be dropped into the root of your context, and build all of your templates with a simple 'make' command. It is also set up to clean up all generated files with a 'make clean' command. Remember to preserve tabs:

.SUFFIXES: .py .tmpl

CHEETAH = cheetah
CHEETAH_FLAGS =
TEMPLATES := $(wildcard *.tmpl */*.tmpl */*/*.tmpl */*/*/*.tmpl)
SERVLETS := $(patsubst %.tmpl, %.py, $(TEMPLATES))
PYBAK := $(patsubst %.tmpl, %.py_bak, $(TEMPLATES))
TMPLTILDE := $(patsubst %.tmpl, %.tmpl~, $(TEMPLATES))

all: $(SERVLETS)

.tmpl.py:
              $(CHEETAH) compile $(CHEETAH_FLAGS) $<

clean:
              rm -f $(TMPLTILDE)
              rm -f $(PYBAK)
              rm -f $(SERVLETS)
              rm -rf *.py~ */*.py~ */*/*.py~ */*/*/*.py~
              rm -rf *.pyc */*.pyc */*/*.pyc */*/*/*.pyc

-- WayneLarsen? - 14 Feb 2002 <br/>

---

Encoding with Unicode

Here is a custom filter to encode Unicode data:

import Cheetah.Filters

class EncodeUnicode(Cheetah.Filters.Filter):
     def filter(self, val, **kw):
              """Encode Unicode strings, by default in UTF-8"""

              if kw.has_key('encoding'):
                            encoding = kw['encoding']
              else:
                            encoding='utf8'
                            
              if type(val) == type(u''):
                            filtered = val.encode(encoding)
              else:
                            filtered = str(val)
              return filtered

import Cheetah.Template

t = Cheetah.Template.Template('''
$myvar
${myvar, encoding='utf16'}
''', searchList=[{'myvar': u'Asni\xe8res'}], filter=EncodeUnicode)

print t

-- RenePijlman? - 20 Nov 2003<br/>

Integration with Apache and mod_python

Here is how I do it:

In Apache's httpd.conf:

<IfModule mod_python.c>
 <Directory /var/www/python>
  Options -Indexes
  SetHandler python-program
  PythonHandler cheetah
  PythonDebug On
  <Files ~ "\.tmpl$">
    Order allow,deny 
    Deny from all   
  </Files>
  <Files ~ "^Makefile">
    Order allow,deny        
    Deny from all   
  </Files>
 </Directory>
</IfModule>

Here is the cheetah.py file which implements the cheetah module defined in PythonHandler:

start = "/python/"

from mod_python import apache
import string

def handler(req):

     req.content_type = "text/html"
     module_name = string.replace (req.uri, start, "")
     try:                                                                                                                   
              exec ("""import %s
tmpl = %s.%s()
tmpl.req = req""" % (module_name, module_name, module_name))
     except ImportError:
              return apache.HTTP_NOT_FOUND
     req.send_http_header()
     req.write(tmpl.respond())

     return apache.OK
Warning: no security study has been done yet, specially about the
consequences of import.

It now allows me to write templates like:

#silent from mod_python import apache
<P>You are
#echo self.req.get_remote_host()

which displays the remote name or IP address.

Using NameMapper? is left as an exercice :-)

You cannot use mod_python's Publisher (instead of my custom cheetah.py above) with most Cheetah setups. If your pages are compiled from a template, the code is in a class and the mod_python.publisher cannot find it. (If your pages are Python code, you can use http://www.modpython.org/pipermail/mod_python/2003-December/014688.html.)

A better Cheetah publisher (without exec) written by Graham Dumpleton:

from mod_python import apache

import os

# It is assumed that Apache config or .htaccess file
# blocks access to ".tmpl", ".py" and ".pyc" files.

def handler(req):

    # Assume REST style URLs. Ie., no extension is used
    # for accessing Cheetah pages. On this basis, first
    # perform a check that there is a ".py" file in
    # existance. This is done because can't distinguish
    # between a non existant module and a module which has
    # a coding error in it when using the function
    # "apache.import_module()". By returning DECLINED,
    # Apache will then serve up any static files in the
    # directory which may otherwise be matched.

    target = req.filename + ".py"

    if not os.path.exists(target):
       return apache.DECLINED

    # Grab the module name to look for from the last part
    # of the path. This means that pages can be spread
    # across subdirectories as well.

    directory,module_name = os.path.split(req.filename)

    # Import the module. Any coding error in the module
    # being imported is thrown back to the user. Error
    # also results if by chance the target just vanished.

    ### module = apache.import_module(module_name,[directory]) # WRONG
    module = apache.import_module(module_name,path=[directory])

    # Ensure that there is a class defined in the module
    # of the appropriate name.

    if not hasattr(module,module_name):
       return apache.DECLINED

    # Create instance of the class and setup request object.

    tmpl = getattr(module,module_name)()
    tmpl.req = req

    # Assume that HTML is being generated.

    req.content_type = "text/html"
    req.send_http_header()

   # Now generate the actual content and return it.

   req.write(tmpl.respond())

   return apache.OK

The above didn't work for me.

I use Debian Woody, and the packages they supply. Apache 1.3, Python 2.1, mod_python 2.7.8.

After a lot research (I thought I'd screwed up my Apache .conf, or something like that), I started debugging, and found two problems.

  1. req.filename had the .py extension already appended, so the script was trying import mptest.py.py

This may have been caused by my allowing .py, .tmpl, etc. file extensions to not be blocked. But even when I left .py off the URL, it was appended by the time req.filename was passed to the script.

The problem I kept getting when trying to access my template.py (not mptest.py!) was that the server was sending a MIME type of 'application/x-httpd-cgi' to the browser, but what was happening was the import of my template failed, and apache.DECLINED was getting returned. Apache was sending my script, rather the running it, in other words.

  1. the call to apache.import_module() was missing the req parameter.

Since I had mod_python 'PythonDebug On' Apache directive in place for my script dir, I then started getting tracebacks, pointing to apache.py get_config(). Looking at the source for apache.py, I noticed that import_module call was missing the 'req' parameter. I put that in, and it all started working!

FWIW, I've here is an updated version of Graham's script, with doc strings added that other n00bs like me might find useful.:

"""Run Cheetah .tmpl/.py files via mod_python.

Provide a handler for Cheetah objects that is compatible with
mod_python calling conventions.  This is required, since Cheetah objects
are organized in such as way as to prevent mod_python from finding the
proper classes and entry points; this module remedies that situation.

As an additional benefit, using this module allows Cheetah objects to
be called without the .py extension, so that URLs look 'ReST'-ful.


Imports:

mod_python.apache - objects to interface with Apache Request, etc.,
objects.

os - Python-supplied interface to the operating system.


Exports:

handler - the entry point required by mod_python for invoking an
object as a request handler.


Credits:

This code courtesy of Graham Dumpleton <grahamd@dscpl.com.au> and was
found on the mod_python mailing list.

The original version was found at
http://www.modpython.org/pipermail/mod_python/2005-March/017639.html

"""
from mod_python import apache

import os

# It is assumed that Apache config or .htaccess file
# blocks access to ".tmpl", ".py" and ".pyc" files.

def handler(req):

    """Handle Apache request via mod_python.

    This function is the entry point required by mod_python to be
    implemented by Python objects that wish to handle URL requests.

    It searches for a file by matching the file (last) part of a URL
    + .py.  If one is found, it tries to import it and instantiate
    class by the same name, then invoke the .respond() method of that
    class instance. (.respond() is the standard Cheetah method
    invoked for output of the template.)

    For example, URL http://www.example.com/home will cause this
    function to look for home.py, import it, instantiate an object of
    class home from it, and then invoke home.respond(), sending
    whatever is returned to req.write().


    Required arguments:

    req - Apache request object.  Contains information about the
    request, and contains the objects/methods needed to respond to the
    request.


    Side effects:

    output stream - writes the return value of an object's .respond()
    method to the output stream via req.write().

    object attribute - Sets attribute req on the instantiated object
    to value passed as the argument to this function.

    content type - Looks for attribute apache_content_type in the
    instantiated object, and if found, sets req.content_type to that
    value, otherwise sets req.content_type to text/html


    Returns:

    apache.OK - requested was handled.

    apache.DECLINED - request not handled.  Either a file by the
    requested name was not found, or was found but did not contain a
    class with same name.

    """

    # Assume REST style URLs. Ie., no extension is used
    # for accessing Cheetah pages. On this basis, first
    # perform a check that there is a ".py" file in
    # existance. This is done because can't distinguish
    # between a non existant module and a module which has
    # a coding error in it when using the function
    # "apache.import_module()". By returning DECLINED,
    # Apache will then serve up any static files in the
    # directory which may otherwise be matched.

    #target = req.filename + ".py"
    target = req.filename

    if not os.path.exists(target):
        return apache.DECLINED

    # Grab the module name to look for from the last part
    # of the path. This means that pages can be spread
    # across subdirectories as well.

    directory,module_name = os.path.split(req.filename)

    # Strip off file extension prior to attempting import
    module_name = module_name.split('.')[0]

    # Import the module. Any coding error in the module
    # being imported is thrown back to the user. Error
    # also results if by chance the target just vanished.

    module = apache.import_module(module_name, req, [directory])

    # Ensure that there is a class defined in the module
    # of the appropriate name.

    if not hasattr(module,module_name):
        return apache.DECLINED

    # Create instance of the class and setup request object.

    tmpl = getattr(module,module_name)()
    tmpl.req = req

    ## Assume that HTML is being generated.
    #req.content_type = "text/plain"
    #req.content_type = "text/html"

    # If content type not specified in tmpl, assume HTML
    req.content_type = hasattr(tmpl, "apache_content_type") and tmpl.apache_content_type or "text/html"
    req.send_http_header()

    # Now generate the actual content and return it.

    req.write(tmpl.respond())

    return apache.OK

Ensure MultiViews is turned off with mod_python

There are two reasons why original code for integration of Cheetah and mod_python by Graham didn't work, however the changes made in subsequent version don't actually address them. The first problem was that the line:

module = apache.import_module(module_name,[directory])

should have been:

module = apache.import_module(module_name,path=[directory])

This has been fixed in the original code now.

The subsequent poster used:

module = apache.import_module(module_name, req, [directory])

in their version but that is incorrect also. The line above where the argument supplying the directory to search is named as "path" should be used there also.

The second problem wasn't in the code itself, but because the person trying to use it who had problems most likely had "MultiViews" enabled for the directory when using Apache. When using mod_python it is highly recommended that you ensure that "MultiViews" is disabled else Apache can rewrite URLs and confuse mod_python handlers:

Options -MultiViews

The original code depended on "MultiViews" being disabled.

And if that doesn't work

Last solution, you can use Aquarium http://aquarium.sourceforge.net.

-- StephaneBortzmeyer? - 15 march 2005

The Vampire package, which is a set of glue extensions for mod_python, also includes a more developed version of the above code for integrating Cheetah into mod_python. Actual handler code for when using Vampire and Cheetah can be seen at:

http://svn.dscpl.com.au/vampire/trunk/examples/cheetah/_handler.py

All the code in that directory is viewable as:

http://svn.dscpl.com.au/vampire/trunk/examples/cheetah/

There is also an example of session based login using Vampire and Cheetah in:

http://svn.dscpl.com.au/vampire/trunk/examples/session/

The important bit managing sessions being in the "access.py" file.

Vampire itself can be found at:

http://www.dscpl.com.au/projects/vampire/

varyBy implemented

I have implemented varyBy for #cache. It works. http://www.cherrypy.org/wiki/Cheetah

-- JaroslawZabiello - 4 Jan 2005

Cheetah and Webware

http://wiki.w4py.org/webwareandcheetah.html

-- JaroslawZabiello - 13 Oct 2005