Migrating from Genshi

Kajiki uses syntax derived from the syntax of Genshi. In particular, the following directives are supported, with semantics intended to be nearly identical to those of Genshi.

  • py:def

  • py:choose – renamed py:with

  • py:when – renamed py:case

  • py:otherwise – renamed py:else

  • py:for

  • py:if

  • py:with

  • py:replace

  • py:content

  • py:attrs

  • py:strip

  • xi:include – renamed py:include

Note that, in particular, py:match in Kajiki differs from Genshi, implementing PEP634.

Kajiki also supports the following additional directives not in Genshi:

  • py:extends - indicates that this is an extension template. The parent template will be read in and used for layout, with any py:block directives in the child template overriding the py:block directives defined in the parent.

  • py:block - used to name a replaceable ‘slot’ in a parent template, or to specify a slot override in a child template. The py:block semantics are modeled after the {% block %} semantics of Jinja2.

Generally, migration consists of a few steps that can be simple or quite difficult based on your fondness of the py:match directive in Genshi. In simple cases where you have one master.html template with a few py:match directives that is xi:included into all your page templates, the following steps should suffice:

  • Rename tags and attributes as indicated above; e.g. xi:include becomes py:include.

  • Rewrite your include directives to use Kajiki’s module naming system and relative imports.

  • In a simple case where you have only a few py:match directives, all of which are in a master.html template that is being included from child templates, I recommend that you rewrite the master.html as layout.html, defining named py:block regions that will be overridden in child templates.

  • In your child templates, remove the <xi:include href="master.html"> that probably lurks near the top. Then add a py:extends directive to the top-level tag (usually <html>). The tag the parts of the child template that are intended to override parts of the parent template with the py:block directive.

Kajiki also provides some helper functions of Genshi:

  • defined('some_variable') (which returns True if ‘some_variable’ exists in the template context),

  • value_of('name', default_value), and

  • Markup(some_string) (which marks a string so it won’t be escaped in the output), though Kajiki prefers to call this literal(some_string).

Example Migration

Suppose you have a couple of Genshi templates, one of which called master.html and one of which is index.html. (TurboGears developers may recognize these files as slightly modified versions of the default templates deposited in a TG quickstarted project.) The contents of master.html are:

 1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 2                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 3<html xmlns="http://www.w3.org/1999/xhtml"
 4      xmlns:py="http://genshi.edgewall.org/"
 5      xmlns:xi="http://www.w3.org/2001/XInclude"
 6      py:strip="">
 7    <xi:include href="header.html" />
 8    <xi:include href="sidebars.html" />
 9    <xi:include href="footer.html" />
10<head py:match="head" py:attrs="select('@*')">
11    <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
12    <title py:replace="''">Your title goes here</title>
13    <meta py:replace="select('*')"/>
14    <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/style.css')}" />
15    <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/admin.css')}" />
16</head>
17
18<body py:match="body" py:attrs="select('@*')">
19  ${header()}
20  <ul id="mainmenu">
21    <li class="first"><a href="${tg.url('/')}" class="${('', 'active')[defined('page') and page=='index']}">Welcome</a></li>
22        <li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page=='about']}">About</a></li>
23        <li py:if="tg.auth_stack_enabled"><a href="${tg.url('/auth')}" class="${('', 'active')[defined('page') and page=='auth']}">Authentication</a></li>
24        <li><a href="${tg.url('/environ')}" class="${('', 'active')[defined('page') and page=='environ']}">WSGI Environment</a></li>
25        <li><a href="http://groups.google.com/group/turbogears">Contact</a></li>
26    <span py:if="tg.auth_stack_enabled" py:strip="True">
27        <li py:if="not request.identity" id="login" class="loginlogout"><a href="${tg.url('/login')}">Login</a></li>
28        <li py:if="request.identity" id="login" class="loginlogout"><a href="${tg.url('/logout_handler')}">Logout</a></li>
29        <li py:if="request.identity" id="admin" class="loginlogout"><a href="${tg.url('/admin')}">Admin</a></li>
30    </span>
31  </ul>
32  <div id="content">
33    <py:if test="defined('page')">
34    <div class="currentpage">
35     Now Viewing: <span py:replace="page"/>
36     </div>
37    </py:if>
38    <py:with vars="flash=tg.flash_obj.render('flash', use_js=False)">
39        <div py:if="flash" py:content="XML(flash)" />
40    </py:with>
41    <div py:replace="select('*|text()')"/>
42    <!-- End of main_content -->
43    ${footer()}
44  </div>
45</body>
46</html>

Likewise, the contents of index.html are as follows:

 1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 2                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 3<html xmlns="http://www.w3.org/1999/xhtml"
 4      xmlns:py="http://genshi.edgewall.org/"
 5      xmlns:xi="http://www.w3.org/2001/XInclude">
 6
 7  <xi:include href="master.html" />
 8
 9<head>
10  <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
11  <title>Welcome to TurboGears 2.0, standing on the 
12  shoulders of giants, since 2007</title>
13</head>
14
15<body>
16    ${sidebar_top()}
17  <div id="getting_started">
18    <h2>Presentation</h2>
19    <p>TurboGears 2 is rapid web application development toolkit designed to make your life easier.</p>
20    <ol id="getting_started_steps">
21      <li class="getting_started">
22        <h3>Code your data model</h3>
23        <p> Design your data model, Create the database, and Add some bootstrap data.</p>
24      </li>
25      <li class="getting_started">
26        <h3>Design your URL architecture</h3>
27        <p> Decide your URLs, Program your controller methods, Design your 
28            templates, and place some static files (CSS and/or JavaScript). </p>
29      </li>
30      <li class="getting_started">
31        <h3>Distribute your app</h3>
32        <p> Test your source, Generate project documents, Build a distribution.</p>
33      </li>
34    </ol>
35  </div>
36  <div class="clearingdiv" />
37  <div class="notice"> Thank you for choosing TurboGears. 
38  </div>
39</body>
40</html>

In order to perform our kajiki migration, we begin by creating two empty templates. The first one will replace our master.html, and we will call it layout.html:

 1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 2                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 3<html>
 4    <py:include href="header" />
 5    <py:include href="footer" />
 6
 7    <head>
 8      <title py:block="title">Your title goes here</title>
 9      <py:block name="head_content">
10        <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/style.css')}" />
11        <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/admin.css')}" />
12      </py:block>
13    </head>
14
15    <body>
16      <py:block name="body_header"><h1>My Header</h1></py:block>
17      <ul py:block="mainmenu" id="mainmenu">
18        <li class="first"><a href="${tg.url('/')}" class="${('', 'active')[defined('page') and page=='index']}">Welcome</a></li>
19        <li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page=='about']}">About</a></li>
20        <li py:if="tg.auth_stack_enabled"><a href="${tg.url('/auth')}" class="${('', 'active')[defined('page') and page=='auth']}">Authentication</a></li>
21        <li><a href="${tg.url('/environ')}" class="${('', 'active')[defined('page') and page=='environ']}">WSGI Environment</a></li>
22        <li><a href="http://groups.google.com/group/turbogears">Contact</a></li>
23      </ul>
24      <div id="content">
25        <py:if test="defined('page')">
26          <div class="currentpage">
27            Now Viewing: <span py:replace="page"/>
28          </div>
29        </py:if>
30        <py:with vars="flash=tg.flash_obj.render('flash', use_js=False)">
31          <div py:if="flash" py:content="XML(flash)" />
32        </py:with>
33        <py:block name="body_text">Your body text goes here</py:block>
34        <py:block name="bodyfooter">${footer()}</py:block>
35      </div>
36    </body>
37</html>

Note the introduction of the py:block directive, and the disappearance of the py:match directives from master.html. py:block mimics the behavior of Jinja2 “blocks”, providing a name to a construct in a parent template which can be replaced by the contents of py:block -named constructs in child templates. For instance, the “title” slot in layout.html:

1      <title py:block="title">Your title goes here</title>

can be replaced by a similarly-named slot in the child document index_kajiki.html:

1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
2                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3<html py:extends="layout">
4    <py:include href="sidebars" />
5
6    <title py:block="title">Welcome to TurboGears 2.0, standing on the 
7      shoulders of giants, since 2007</title>

We also provide a way of including the contents of the parent template’s slot in a child template’s slot using ${parent_block()}. The following slot in layout.html:

1      <py:block name="body_header"><h1>My Header</h1></py:block>

can be replaced in include/index_kajiki.html with:

1    <py:block name="body_header">
2      ${parent_block()}
3      <h1>Some extra header data</h1>
4    </py:block>

Yielding the following html once rendered:

1 <h1>My Header</h1>
2 <h1>Some extra header data</h1>