PylonsHQ.

Layout: Fixed-width

Genshi templates

Authors: Mike Orr, with advice from JJ Behrens and the Genshi mailing list. Based on a page on the PylonsHQ wiki by <unknown>.

Enabling Genshi

To use Genshi templates in Pylons, first install Genshi ("easy_install Genshi") and then adjust environment.py. To keep Mako the default but add Genshi as an alternate engine:

1
2
config.init_app(global_conf, app_conf, package="<appname>")
config.add_template_engine("genshi", "<appname>.templates", {})

To make Genshi the default:

1
config.init_app(global_conf, app_conf, package="<appname>", template_engine="genshi")

A more flexible way is to clear out the default and add your own engine(s). The third argument is a dict of engine-specific options. (See the engine documentation for possible options; they will eventually be documented here. You may have to do some experimentation to see which options work.)

1
2
3
4
config.init_app(global_conf, app_conf, package='<appname>')
config.template_engines = []
config.add_template_engine('genshi', '<appname>.templates', {})
# Add Mako or any other engine(s) here.

In any case, the first engine configured is the default for render_response("<template>")}. To choose a non-default engine:

1
render_response("genshi", "<template>")

Note: Genshi includes a second engine "genshi-text" which implements the "Genshi Text Template Engine" described in the Genshi documentation. Most Web programmers won't need it, but it's available if your application needs to do non-HTML output.

Preparing the templates directory and your controllers

Create an "__init__.py" file in the "templates" directory and in any subdirectories you may have. This file may be empty but it must exist, or Genshi will not find your templates!!!

Name your template files like this: templates/index.html, templates/mydir/mytemplate.html.

Invoke your templates this way: render_response("index"), render_response("mydir.mytemplate").

To pass data to the template, use the c global just like with Mako:

1
c.title = "Hello, world!"

The template would then contain a line like this or this:

1
2
<title>${c.title}</title>
<title py:content="c.title">Page Title</title>

See the Genshi Guide for more information about template syntax, especially the required "xmlns:" attributes.

Future changes

As we've seen above, there's a contradiction in that template files are named in the URI manner ("templates/mydir/mytemplate.html") but accessed as nonexistent Python modules ("mydir.mytemplate"). This is because Genshi's template plugin is modeled after Kid usage in TurboGears, with filenames changed from "*.kid" to "*.html". In the future Genshi may switch to the URI syntax used by Mako and Myghty: "/mydir/mytemplate.html". This would allow easier access to templates with dots in the name or with alternate extensions (".xhtml", ".xml") for non-HTML output. But for now you must do it as described above.

WebHelpers

Because Genshi automatically escapes all input values (replacing "<" with "& lt;" for instance), to use WebHelpers effectively in a Genshi template you have to wrap the return values in a Genshi Markup object. You can either wrap each use in the template:

1
${Markup(h.url_for("whatever"))}

Or add some code in lib/helpers.py that wraps every helper function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from genshi.core import Markup

def wrap_helpers(localdict):
    def helper_wrapper(func):
        def wrapped_helper(*args, **kw):
            if not callable(func(*args, **kw)):
                return Markup(func(*args, **kw))
            else:
                return Markup(func(*args, **kw)())
        wrapped_helper.__name__ = func.__name__
        return wrapped_helper
    for name, func in localdict.iteritems():
        if not callable(func) or not func.__module__.startswith('webhelpers.rails'):
            continue
        localdict[name] = helper_wrapper(func)
wrap_helpers(locals())

And then import it in "<appname>/config/middleware.py" to create the wrappers at application initialization:

1
from <appname>.lib import helpers

You might also think about adding to "<appname>/lib/helpers.py":

1
from genshi.builder import tag

The Genshi tag object is just like the content_tag()' WebHelper, but faster since it bypasses the wrapper.

There is discussion among the Pylons developers on how to make this more automatic, and how use the same wrappers in both Genshi and non-Genshi templates in the same application without overescaping or underescaping, but no solution has been found.

Site templates

Often sites wish to have a common site template containing the page header/footer/sidebar, so the page templates can concentrate on the content unique to this page. Some sites need a three-level hierarchy (site-section-page). Mako and Cheetah would use inheritance for this, but Genshi does not have inheritance. Instead use "xi:include" combined with "py:match". The following is based on the TurboGears sample application in the Genshi source.

Site template (site.html)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<html xmlns="http://www.w3.org/1999/xhtml"
           xmlns:py="http://genshi.edgewall.org/"
           xmlns:xi="http://www.w3.org/2001/XInclude"
           lang="en"
           py:strip="">
  <head py:match="head" py:attrs="select('@*')">
    <!-- Put any HEAD tags which all pages should have here. -->
    <meta py:replace="select('*|text()')" />
  </head>
  <body py:match="body" py:attrs="select('@*')">
    <!-- Put anything that should appear before the page content here (e.g., logo, menu, left sidebar) -->

    <div py:replace="select('*|text()')">This page has no content.</div>

    <!-- Put anything that should appear after the page content here (e.g., page footer, copyright, "Contact Us" link). -->
  </body>
</html>

Page template (mypage.html)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      lang="en">
<xi:include href="site.html" />
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>MySite: Using This Site</title>
    <link rel="stylesheet" type="text/css" href="default.css" 
        py:attrs="{'href': '/default.css'}" />
</head><body>

<h1 class="page_title">Using This Site</h1>

<p>Under construction.</p>

</body></html>

Commentary

The above example uses several Genshi features. The page template has the required xmlns: declarations and recommended doctype and charset declaration. (You may of course use a different doctype or charset, or find a way to pull in Pylons' default charset.) The page template is a complete HTML document with a dummy stylesheet link that can be previewed standalone in a browser or edited in a WYSIWYG editor like GoLive.

The page template has a "xi:include" near the top that includes the site template. The site template has "py:match" attributes on the <head> and <body>. This means the site template's head and body will not be output directly but will instead modify the page template's head and body, kind of like a function. select() is used inside the match tag to output various parts of the page template, using XPath syntax as an argument. The argument "*|text()" selects all text and subtags. The argument "@*" in "py:attrs" selects all attributes.

Note that the site template's <html> does not have a py:match. This would cause both the site's <html> and the page's <html> to appear in the output, except that the site template has py:strip="" to prevent this.

There are several other ways to construct site templates and page templates using various combinations of "py:match", "py:def", and "xi:include". For instance, the site template can be used to output the majority of the skeleton, and the page can use match statements to insert text in the right spots, such as the main body of the page.

The stylesheet link has a dummy href that will work in a standalone browser or GoLive if you put the stylesheet file in the same directory. But the running application will use a different URL specified in the "py:attrs" expression.

Doctype

Genshi will output the first doctype seen; i.e., the page template's. You can also override the doctype programmatically, although this may not be available in Pylons yet. The expected configuration variable is "genshi.default_doctype = html-strict", The native Genshi construct from Python is:

1
2
from genshi.output import DocType
stream.render('html', DocType.HTML_STRICT)

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

Powered by Pylons - Contact Administrators