Edgewall Software

Version 3 (modified by Christian Boos, 8 years ago) ( diff )

well, #!html was used on purpose in this very specific example (see also PortingFromClearSilverToGenshi#Examples)

Warning

The following documentation corresponds to an experimental branch (Proposals/Jinja / cboos.git@jinja2).

Porting Templates from Genshi to Jinja2

The following documentation is primarily targeted at plugin developers who wish to adapt their Genshi templates to the Jinja2 template engine that will be used in Trac 1.4.

For migrating your own templates, a good way to start is to learn from examples. Compare the 'j…' Jinja templates found in source:cboos.git/trac/templates@jinja2 with their corresponding Genshi ones.

Note that Genshi will not be supported concurrently with Jinja2. If for some reason you're stuck to having to support Genshi templates, you'll have to stick to Trac 1.2.x. But you really should make the transition effort as Jinja2 templates are 5-10x faster than their Genshi equivalent, for a 1/5th of the cost in memory usage.

Changes in the template syntax

Most of the time, the porting is a straightforward operation.

expand a variable

  • Genshi
    <b>$the_variable</b>
    
  • Jinja2
    <b>${the_variable}</b>
    

Tip:

sed -ie 's,\$\([a-z_][a-z._]*\),${\1},g' template.html

expand a simple computation

  • Genshi
    <b>${the_variable + 1}</b>
    
  • Jinja2
    <b>${the_variable + 1}</b>
    

However, Jinja2 expressions are only similar to Python expressions, there are a few differences and limitations, see expressions doc.

include another template

  • Genshi
    <xi:include href="the_file.html"><xi:fallback/></xi:include>
    
  • Jinja2
    # include "the_file.html" ignore missing
    

See include doc.

simple if…then (no else)

  • Genshi
    <py:if test="flag"><b>OK</b></py:if>
    
    or simply:
    <b py:if="flag">OK</b>
    
  • Jinja2
    # if flag: 
    <b>OK</b>
    # endif
    

See if doc.

if…then…else

  • Genshi
    <py:choose test="flag">
      <py:when test="True">
        <b>OK</b>
      </py:when>
      <py:otherwise>
       <i>!!!</i>
      </py:otherwise>
    </py:choose>
    
    or simply:
    <py:choose>
     <b py:when="flag">OK</b>
     <i py:otherwise="">!!!</i>
    </py:choose>
    
  • Jinja2
     # if flag:
     <b>OK</b>
     # else:
     <i>!!!</i>
     # endif
    

If you really have to, you can also use the block style:

{% if flag %}<b>OK</b>{% else %}<i>!!!</i>{% endif %}

However this goes against readability and processing via the #jinjachecker tool, so we really advise that you stick to the use of line statements.

iterate over a collection

  • Genshi
    <ul>
      <py:for each="element in list">
        <li>$element</li>
      </py:for>
    </ul>
    
    or simply:
    <ul>
      <li py:for="element in list">$element</li>
    </ul>
    
  • Jinja2
    <ul> 
      # for element in list:
      <li>${element}</li>
      # endfor
    </ul>
    

See for doc.

No need for enumerate

  • Genshi:
              <tr py:for="idx,option in enumerate(section.options)"
                  class="${'modified' if option.modified else None}">
                <th py:if="idx == 0" class="section"
                    rowspan="${len(section.options)}">${section.name}</th>
    
    
  • Jinja2:
              #   for option in section.options:
              <tr ${{'class': 'modified' if option.modified}|htmlattr}>
                #   if loop.first:
                <th class="section"
                    rowspan="${len(section.options)}">${section.name}</th>
    
    

All common uses (and more) for such an idx variable are addressed by the special loop variable. See loop doc.

define a macro

  • Genshi
    <py:def function="entry(key, val='--')">
     <dt>$key</dt><dd>$val</dd>
    </py:def>
    
  • Jinja2
     # macro entry(key, val='--')
     <dt>${key}</dt><dd>${val}</dd>
     # endmacro
    

See macros doc.

set a variable

  • Genshi
    <py:with vars="count = len(collection)">
    We have ${count &gt; 10 and 'too much' or count} elements.
    </py:with>
    
    Note that we had to use &gt; in Genshi, we can't use > directly.
  • Jinja2
    # set count = len(collection)
    We have ${'too much' if count is greaterthan(10) else count} elements.
    
    Note that we avoid using > in Jinja2 expressions as well, but simply to avoid that XML/HTML text editors get confused. We added a few custom tests for that (greaterthan, greaterthanorequal, lessthan, lessthanorequal).

See tests doc.

set HTML attributes

In Genshi, an attribute with a None value wouldn't be output. However, Jinja2 doesn't know what an attribute is (or anything else about HTML, XML for that matter), so we have to use a special filter, htmlattr, to reproduce this behavior:

  • Genshi:
              <tr class="${'disabled' if all(not component.enabled for module in plugin.modules.itervalues()
                                             for component in module.components.itervalues()) else None}">
    
  • Jinja2:
              #   set components = plugin.modules|map(attribute='components')|flatten
              <tr ${{'class': 'disabled' if not components|selectattr('enabled')
                  }|htmlattr}>
    
    

Note that Jinja2 expressions are a subset of Python expressions, and for the sake of simplicity the generator expressions are not part of that subset. This limitation often requires one to make creative use of filters, built-in or custom (min, max, trim, flatten).

A few more examples:

Genshi Jinja2
${', '.join([p.name for p in faulty_plugins])} ${faulty_plugins|map('name')|join(', ')}

conditional wrappers

There's no direct equivalent to the py:strip, but it can often be emulated with an if/else/endif.

  • Genshi
      <a py:strip="not url" href="$url">$plugin.name</a>
    
  • Jinja2
      # if url:
      <a href="${url}">${plugin.name}</a>
      # else:
      ${plugin.name}
      # endif
    

If the repeated content is complex, one can use a block assignment (see below).

i18n

Genshi had a pretty good notion of what was a piece of translatable text within an HTML template, but Jinja2 doesn't, so there's no "guessing" and no i18n will happen unless explicitly asked.

This can be done in two ways. First, with translation expressions, using the familiar _() gettext function (gettext and ngettext also supported).

  • Genshi:
    <strong>Trac detected an internal error:</strong>
    
  • Jinja2:
    <strong>${_("Trac detected an internal error:")}</strong>
    

Second, using trans directives.

  • Genshi:
                 <p i18n:msg="create">Otherwise, please ${create_ticket(tracker)} a new bug report
                 describing the problem and explain how to reproduce it.</p>
    
  • Jinja2:
                 <p>
                   # trans create = create_ticket(tracker)
    
                   Otherwise, please ${create} a new bug report
                   describing the problem and explain how to reproduce it.
    
                   # endtrans
                 </p>
    

Note that only direct variable expansions are supported in trans blocks, nothing more complex. So one way to deal with complex translatable content is to factor out the complex parts in variable blocks. See block assignments doc.

  • Genshi:
               <p i18n:msg="">There was an internal error in Trac.
                 It is recommended that you notify your local
                 <a py:strip="not project.admin" href="mailto:${project.admin}">
                     Trac administrator</a> with the information needed to
                 reproduce the issue.
               </p>
    
    
  • Jinja2:
                  <p>
                    # set trac_admin = _("Trac administrator")
                    # set project_admin
                    #   if project.admin:
                    <a href="mailto:${project.admin}">${trac_admin}</a>
                    #   else:
                    ${trac_admin}
                    #   endif
                    # endset
                    # trans project_admin = project_admin|safe
    
                    There was an internal error in Trac.
                    It is recommended that you notify your local
                    ${project_admin} with the information needed to
                    reproduce the issue.
    
                    # endtrans
                  </p>
    
    (the need for the |safe filter in the above might go away once the following issue Should set blocks be safe by default gets fixed)

Examples

Let's first take a simple full-contained example from the Trac source, the simple index.html / jindex.html templates.

  • 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>
    
  • Jinja jindex.html:
    <!DOCTYPE html>
    <html>
      <head>
        <title>${_("Available Projects")}</title>
      </head>
    
      <body>
        <h1>${_("Available Projects")}</h1>
        <ul>
          # for project in projects:
          <li>
            # if 'href' in project:
            <a href="${project.href}"
               title="${project.description}">${project.name}</a>
            # else:
            <small>${project.name}: <em>${_("Error")}</em> <br />
              (${project.description})</small>
            # endif
          </li>
          # endfor
        </ul>
      </body>
    </html>
    

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 jdiff_form.html, another small template.

Note that a Jinja2 .html template can usually be rendered directly in the browser, to have a rough taste of how it will look like:

${_("Available Projects")}

    # for project in projects:
  • # if 'href' in project: ${project.name} # else: ${project.name}: ${_("Error")}
    (${project.description})
    # endif
  • # endfor

Though there's no absolutely no constraints on a Jinja2 template, it helps to have an .xml or .html template be itself a well-formed XML document.

Never go back to the bad old Clearsilver habits were sometimes the logic in those templates took advantage of the lack of well-formedness constraints, e.g. by conditionally inserting end/start pairs of tags. Such templates were hard to maintain, and you always have cleaner alternatives.

The #jinjachecker tool should also help you maintain well-formed templates.

Changes in the controllers

Implementing the IRequestHandler interface

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. This hasn't changed with Jinja2.

Generating content

When one wants to directly render a template, the Chrome component facilities can be used, as before:

return Chrome(self.env).render_template(
                req, 'query_results.html', data, None, fragment=True)

implementing Ticket Query macro (table mode)

Note: See TracWiki for help on using the wiki.