Version 9 (modified by 17 years ago) ( diff ) | ,
---|
TracDev/ApiChanges/0.11
Note: Development of Trac 0.11 has started with r3804 and the trunk now uses Genshi instead of ClearSilver, for its template engine. Nevertheless, you should have in mind that the information in this page corresponds to a work in progress.
Migrating away from Clearsilver
ClearSilver has proven a bit uncomfortable to work with, and search for better alternatives were done a few months ago. The Kid templating language was unanimously found appealing, to the point cmlenz did a porting of Trac to Kid, during the DrProject fork. This in turn was found painful, and prompted Christopher to start his own, enhanced, version of Kid currently maturing as Genshi.
The migration from ClearSilver to Genshi was done on trunk in r3832.
The following documentation is by no means a replacement for the excellent documentation you'll find in the Genshi site. You should go there for a more in-depth understanding of how Genshi actually works and should be used. Here we'll focus on the differences with Clearsilver.
For migrating your own templates, a good way to start is to learn by example. Compare the Clearsilver templates found in source:trunk/templates@3831 and their corresponding Genshi ones in source:sandbox/genshi/templates@3831.
Then, in the same way, compare the various web_ui.py controllers you'll find in both branches.
Changes in the template syntax
Most of the time, the porting is a straightforward operation.
expand a variable
- Clearsilver
<b><?cs var:the_variable ?></b>
- Genshi
<b>$the_variable</b>
expand a simple computation
- Clearsilver
<b><?cs var:the_variable+1 ?></b>
- Genshi
<b>${the_variable+1}</b>
include another template
- Clearsilver
<?cs include:the_file.cs ?>
- Genshi
<xi:include href="the_file.html"><xi:fallback/></xi:include>
simple if…then (no else)
- Clearsilver
<?cs if:flag ?><b>OK</b><?cs /if ?>
- Genshi or simply
<py:if test="flag"><b>OK</b></py:if>
<b py:if="flag">OK</b>
if…then…else
- Clearsilver
<?cs if:flag ?> <b>OK</b> <?cs else ?> <i>!!!</i> <?cs /if ?>
- Genshi
or simply:
<py:choose test="flag"> <py:when test="True"> <b>OK</b> </py:when> <py:otherwise> <i>!!!</i> </py:otherwise> </py:choose>
The <py:choose>/<py:when>/<py:otherwise> is a bit heavy-weight for a simple if/else, but on the other hand, the construct is more general (think switch/case, or the equivalent choose/when/otherwise in XSLT).<py:choose> <b py:when="flag">OK</b> <i py:otherwise="">!!!</i> </py:choose>
iterate over a collection
- Clearsilver
<ul><?cs each:element = list ?> <li><?cs var:element ?></li><?cs /each ?> </ul>
- Genshi
or simply:
<ul> <py:for each="element in list"> <li>$element</li> </py:for> </ul>
<ul> <li py:for="element in list">$element</li> </ul>
define a macro
- Clearsilver
<?cs def:entry(key, val)?> <dt><?cs var:key ?></dt><dd><?cs var:val ?></dd> <?cs /def ?>
- Genshi
As you can see, with Genshi it's also easy to specify default values for the macro arguments.
<py:def function="entry(key, val='--')"> <dt>$key</dt><dd>$val</dd> </py:def>
set a variable
- Clearsilver
<?cs set:count = len(collection) ?> We have <?cs if:count > 10 ?>too much<?cs else ?><?cs var:count ?><?cs /if ?> elements.
- Genshi
Note that we had to use
<py:with vars="count = len(collection)"> We have ${count > 10 and 'too much' or count} elements. </py:with>
>
in Genshi, instead of directly>
as in Clearsilver.
Examples
Let's first take a simple full-contained example from the Trac source, the simple index.cs / index.html templates.
- Clearsilver index.cs:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head><title>Available Projects</title></head> <body><h1>Available Projects</h1><ul><?cs each:project = projects ?><li><?cs if:project.href ?> <a href="<?cs var:project.href ?>" title="<?cs var:project.description ?>"> <?cs var:project.name ?></a><?cs else ?> <small><?cs var:project.name ?>: <em>Error</em> <br /> (<?cs var:project.description ?>)</small><?cs /if ?> </li><?cs /each ?></ul></body> </html>
- Genshi index.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" xmlns:xi="http://www.w3.org/2001/XInclude"> <head><title>Available Projects</title></head> <body> <h1>Available Projects</h1> <ul> <li py:for="project in projects" py:choose=""> <a py:when="project.href" href="$project.href" title="$project.description"> $project.name</a> <py:otherwise> <small>$project.name: <em>Error</em> <br /> ($project.description)</small> </py:otherwise> </li> </ul> </body> </html>
Some remarks:
- Note the possible use of multiple genshi attributes in the same element
(in the above, the
<li>
element has apy:for
and apy:choose
attribute). - When there's only one element to output conditionally, one should use a genshi attribute
(the
py:for="project in projets"
and thepy:when="project.href"
in the above). Otherwise, one should use a genshi element (here, the<py:otherwise>
). - In this small example, there's no common Trac layout used (as the index is a bit special). For how a "normal" template looks like, see for example diff_form.html, another small template.
Note that a Genshi template can usually be rendered directly to have a taste of how it will look like:
Available Projects
This comes from an important property of Genshi templates: they must themselves be well-formed XML documents.
That was not a constraint in Clearsilver, and sometimes the logic in those templates took "advantage" of that, e.g. by conditionally inserting end/start pairs of tags. Such templates are the hardest to port, because you actually have to think a bit… See for example the query.html template.
Of course, the great benefit of this constraint is that you'll end up quite naturally with well-formed content, which was far from being a trivial achievement using Clearsilver templates. Granted, you could still insert directly some non well-formed Markup
data in your template, but again, if you use the genshi.builder tag facility for this, that's hardly a risk.
Another example from Trac, a bit more complex.
This illustrates how to use <py:def>
and <py:with>
, to convert a Clearsilver macro using <?cs def: ?>
and <?cs set: ?>
.
- Clearsilver
def:browser_path_links(path, file) ?><?cs <?cs set:first = #1 ?><?cs each:part = path ?><?cs set:last = name(part) == len(path) - #1 ?><a<?cs if:first ?> class="first" title="Go to root directory"<?cs set:first = #0 ?><?cs else ?> title="View <?cs var:part.name ?>"<?cs /if ?> href="<?cs var:part.href ?>"><?cs var:part.name ?></a><?cs if:!last ?><span class="sep">/</span><?cs /if ?><?cs /each ?><?cs /def ?><?cs
- Genshi
<py:def function="browser_path_links(path_links)"> <py:for each="idx, part in enumerate(path_links)"> <py:with vars="first = (idx == 0); last = (idx == len(path_links) - 1)"> <a class="${first and 'first' or None}" title="${first and 'Go to root directory' or 'View ' + part.name}" href="$part.href">$part.name</a> <py:if test="not last"><span class="sep">/</span></py:if> </py:with> </py:for> </py:def>
Changes in the controllers
Implementing the IRequestHandler
interface
Previously, all the data fed to a template had to be placed inside the req.hdf
HDF wrapper object.
With Genshi, the data for the template is basically a dict
, which has to be returned by process_request
at the same time as the template name. Check trac.wiki.web_ui for an example.
Generating content
When one wants to directly render a template, the Chrome component facilities should be used.
Check the Chrome.load_template
and render_method
methods. Note however that this API is still rapidly evolving.
Usage examples:
- Implementing Ticket Query macro (table mode)
- Sending notification e-mails
Date and Time Manipulations
Since r3935, Trac uses datetime
objects internally, instead of timestamps.
More precisely, the database layer still uses int
timestamps, but manipulation of time values is now done on datetime
objects as soon as possible, see e.g. the Timeline module.
Those datetime
values are directly added by the controllers to the data model, and it's the responsibility of the templates to pick up the appropriate time representation, using one of the built-in date formatting utilities: format_date
, format_datetime
, format_datetime
, http_date
, pretty_timedelta
(see chrome.py), or even the $dateinfo()
macro.
Those utilities automatically take into account the timezone information set by the user, so that the dates are presented in a meaningful way to him.