Edgewall Software

Conversion example

Here we detail the conversion process of a sample Genshi template (wiki_view.html) into the corresponding Jinja2 template (wiki_view.html).

As will become apparent, we mainly use line statements for the Jinja2 control structures. Note that we have reformatted the Genshi template to use shorter line widths, so that both columns of the table below stay visible (a wide screen also helps).

wiki_view.html (Genshi template)

wiki_view.html (Jinja2 template)

<!--!  Copyright (C) 2006-2014 Edgewall Software

  This software is licensed as described in the file COPYING, which
  you should have received as part of this distribution. The terms
  are also available at http://trac.edgewall.com/license.html.

  This software consists of voluntary contributions made by many
  individuals. For the exact contribution history, see the revision
  history and logs, available at http://trac.edgewall.org/.
-->
{# Copyright (C) 2006-2014 Edgewall Software

  This software is licensed as described in the file COPYING, which
  you should have received as part of this distribution. The terms
  are also available at http://trac.edgewall.com/license.html.

  This software consists of voluntary contributions made by many
  individuals. For the exact contribution history, see the revision
  history and logs, available at http://trac.edgewall.org/.
#}

Block comments

# extends "layout.html"
This is the equivalent of Genshi's <py:include href="layout.html" />. We have to do the extends before outputing any content on our own, otherwise it would show up in the final result. Once we made the extends, only the content written within blocks will matter.
<!DOCTYPE html
    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!DOCTYPE html>

Let's directly make the jump to HTML5, while we're at it (I'm not sure what is the current state of the HTML5 support in Genshi, but it no longer matters)

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      xmlns:i18n="http://genshi.edgewall.org/i18n"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      py:with="modify_perm = 'WIKI_MODIFY' in perm(page.resource);
               create_perm = 'WIKI_CREATE' in perm(page.resource);
               admin_perm = 'WIKI_ADMIN' in perm(page.resource);
               is_not_latest = page.exists and page.version != latest_version">
<html>

No need for exotic namespace declarations.

Note that we can't use a with declaration that would encompass the head and content blocks we'll define in a moment, as this would force the generation of content twice, once by having the with block present in the template, and a second time because of the extends and the call of the blocks.

  <xi:include href="layout.html" />

See above, this will be converted to an initial extends statement.

  <head>
    <title py:if="title">$title</title>
  <head>
    <title>
      # block title
      #   if title:
      ${title} ${ super() }
      #   endif
      # endblock title
    </title>

    # block head
    #  set modify_perm = 'WIKI_MODIFY' in perm(page.resource)
    #  set is_not_latest = page.exists and page.version != latest_version

    ${ super() }

The content of the <title> element is placed in the title block, followed by the content of the parent title block (${ super() }). When closing the block, we reuse the name of the block.

Note that while the only thing that really matters in an extending template is the content of the blocks, we try hard to keep a correct HTML structure for the whole template page, so that its HTML content can be validated in a standalone way (cf. #jinjachecker). This is why we add the <html>, <head> and <title> tags.

We explained earlier why the title block has to be outside of the head block: to avoid appearing twice, once as part of the head block, and the second time when we call ${ super() } in order to retrieve the content of the parent head block.

Finally, we define some variables at the beginning of the block (corresponding to some which were part of the py:with attribute in the <html> tag, in the Genshi template).

    <meta py:if="version or page.author == 'trac'" 
          name="ROBOTS" content="NOINDEX, NOFOLLOW" />
    <link py:if="modify_perm" rel="alternate" 
          type="application/x-wiki"
          href="${href.wiki(page.name, action='edit', 
                  version=page.version if is_not_latest else None)}"
          title="${_('Revert page to this version') if 
                     is_not_latest else _('Edit this page')}"/>
    #   if version or page.author == 'trac':
    <meta name="ROBOTS" content="NOINDEX, NOFOLLOW" />
    #   endif
    #   if modify_perm:
    <link rel="alternate" type="application/x-wiki"
          href="${href.wiki(page.name, action='edit',
                version=page.version if is_not_latest)}"
          title="${_("Revert page to this version") if is_not_latest else
                                                  _("Edit this page")}"/>
    #   endif

With Jinja2, the logic is now clearly distinct from the content.

    <script type="text/javascript">
      jQuery(document).ready(function($) {
        $("#content").find("h1,h2,h3,h4,h5,h6")
          .addAnchor(_("Link to this section"));
        $("#content").find(".wikianchor").each(function() {
          $(this).addAnchor(babel.format(_("Link to #%(id)s"), 
                                         {id: $(this).attr('id')}));
        });
        $(".foldable").enableFolding(true, true);
      });
    </script>
    <script type="text/javascript">
      jQuery(document).ready(function($) {
        $("#content").find("h1,h2,h3,h4,h5,h6")
          .addAnchor(_("Link to this section"));
        $("#content").find(".wikianchor").each(function() {
        $(this).addAnchor(babel.format(_("Link to #%(id)s"), {
                                       id: $(this).attr('id')}));
        });
        $(".foldable").enableFolding(true, true);
      });
    </script>

No changes here, except for some reformatting (we try to stay below 80 chars for nicer future side-by-side diffs).

  </head>

    # endblock head
  </head>

We close the head block.

  <body>
    <div id="content" class="${classes('wiki', create=not page.exists)}">

  <body>
    # block content
    #   set modify_perm = 'WIKI_MODIFY' in perm(page.resource)
    #   set create_perm = 'WIKI_CREATE' in perm(page.resource)
    #   set admin_perm = 'WIKI_ADMIN' in perm(page.resource)
    #   set is_not_latest = page.exists and page.version != latest_version

    <div id="content" class="${classes('wiki', create=not page.exists)}">

We start the content block. As before with the header block, we define some variables at the beginning of the block.

      <py:if test="version">
        <br />
        <table id="info" summary="Revision info">
          <tr><th scope="row" i18n:msg="version, author, date">
             Version $page.version (modified by ${authorinfo(page.author)},
                                            ${pretty_dateinfo(page.time)})
             (<a href="${href.wiki(page.name, action='diff', 
                                   version=page.version)}">diff</a>)
          </th></tr>
          <tr><td class="message" xml:space="preserve">
            ${wiki_to_html(context, page.comment or '--')}
          </td></tr>
        </table>
      </py:if>
      # if version:
      <br />
      <table id="info" summary="${_("Revision info")}">
        <tr><th scope="row">
            # with
            #   set version = page.version
            #   set author = authorinfo(page.author)
            #   set date = pretty_dateinfo(page.time)
            #   set hef = href.wiki(page.name, action='diff', version=page.version)
            #   trans version, author, date, href

            Version ${version} (modified by ${author}, ${date})
            (<a href="${href}">diff</a>)

            #   endtrans
            # endwith
        </th></tr>
        <tr><td class="message">
            ${wiki_to_html(context, page.comment or '--')}
        </td></tr>
      </table>
      # endif

Here we illustrate the i18n changes.

First, any sentence that corresponds to visible end-user text that should be translated has to be marked somehow.

One way is to use the standard i18n calls, _, ngettext(), etc.

The other way is to use trans blocks. There are two big differences with their Genshi i18n equivalent:

  • variable substitutions can only be those of direct variables, no kind of expression is allowed, even as simple as attribute lookup
  • Jinja2, markup neutral as it is, will not do any substitutions on the markup found in a trans block; what would have ended in a Genshi bracketed expression ... [1:diff] in the catalogs will now remain HTML markup: ... <a href="${href}">diff</a>.

Note that one can use a with statement for breaking up the assignments needed on multiple separate lines. Smaller lists of variables can be placed on the trans line directly.

      <div class="wikipage searchable" py:choose="" xml:space="preserve">
        <py:when test="page.exists">
          <div id="wikipage" class="trac-content"
               py:content="wiki_to_html(context, text)" />
          <?python
            last_modification = (page.comment and
                 _('Version %(version)s by %(author)s: %(comment)s',
                   version=page.version, author=format_author(page.author),
                   comment=page.comment) or
                 _('Version %(version)s by %(author)s',
                   version=page.version, author=format_author(page.author)))
          ?>
          <div py:if="not version" class="trac-modifiedby">
            <span i18n:msg="reldate">
              <a href="${href.wiki(page.name, action='diff', 
                                   version=page.version)}"
                 title="$last_modification">Last modified</a>
                 ${pretty_dateinfo(page.time)}
            </span>
            <span class="trac-print" i18n:msg="date">Last modified on
              ${format_datetime(page.time)}</span>
          </div>
        </py:when>
        <py:otherwise>
          <p i18n:msg="name">The page <strong>${name_of(page.resource)}</strong>
          does not exist. You can create it here.</p>
        </py:otherwise>
      </div>
      <div class="wikipage searchable">
        # if page.exists:
        <div id="wikipage" class="trac-content">${
          wiki_to_html(context, text)
          }</div>
        #   set last_modification = (page.comment and
        _('Version %(version)s by %(author)s: %(comment)s',
        version=page.version, author=format_author(page.author),
        comment=page.comment) or
        _('Version %(version)s by %(author)s',
        version=page.version, author=format_author(page.author)))
        #   if not version:
        <div class="trac-modifiedby">
          <span>
            # with
            #   set href = href.wiki(page.name, action='diff',
                                     version=page.version),
            #   set date = pretty_dateinfo(page.time)
            #   trans href, last_modification, date

            <a href="${href}"
               title="${last_modification}">Last modified</a> ${date}

            #   endtrans
            # endwith
          </span>
          <span class="trac-print">
            ${_("Last modified on %(date)s", date=format_datetime(page.time))}
          </span>
        </div>
        #   endif
        # else:
        <p>
          # trans name = name_of(page.resource)

          The page <strong>${name}</strong> does not exist.
          You can create it here.

          # endtrans
        </p>
        # endif
      </div>
      <xi:include href="list_of_attachments.html"
                  py:with="alist = attachments; compact = True; 
                           foldable = True"/>
      # with
      #   set alist = attachments
      #   set compact = True
      #   set foldable = True
      #   include "list_of_attachments.html"
      # endwith

Jinja2 includes also know about their context, so that make them kind of parametric.

      <py:with vars="delete_perm = 'WIKI_DELETE' in perm(page.resource);
                     rename_perm = 'WIKI_RENAME' in perm(page.resource)">
        <py:if test="modify_perm or create_perm or delete_perm">
          <div class="buttons">
            <py:if test="modify_perm or create_perm">
      # with
      #   set delete_perm = 'WIKI_DELETE' in perm(page.resource)
      #   set rename_perm = 'WIKI_RENAME' in perm(page.resource)
      #   if modify_perm or create_perm or delete_perm:
      <div class="buttons">
        #   if modify_perm or create_perm:

Again, even when there's a lot of control lines, it's a bit easier to immediately spot the actual content in Jinja2 templates, due to the clearer syntactic difference.

              <form method="get" action="${href.wiki(page.name)}" id="modifypage">
                <div>
                  <input type="hidden" name="action" value="edit" />
                  <py:choose>
                    <py:when test="is_not_latest and modify_perm">
                      <input type="hidden" name="version" value="${page.version}"/>
                      <input type="submit" value="${_('Revert to this version')}"/>
                    </py:when>
                    <py:when test="page.exists and modify_perm">
                      <input type="submit" value="${_('Edit this page')}" 
                             accesskey="e" />
                    </py:when>
                    <py:when test="not page.exists and create_perm">
                      <input type="submit" value="${_('Create this page')}" 
                             accesskey="e" />
                      <div py:if="templates" id="template">
                        <label for="template">using the template:</label>
                        <select name="template">
                          <option selected="${not default_template in templates
                                              or None}"
                                  value="">(blank page)</option>
                          <option py:for="t in sorted(templates)" value="$t"
                                  selected="${t == default_template or None}"
                                  >$t</option>
                        </select>
                      </div>
                    </py:when>
                  </py:choose>
                </div>
              </form>
        <form method="get" action="${href.wiki(page.name)}" id="modifypage">
          <div>
            <input type="hidden" name="action" value="edit" />
            # if is_not_latest and modify_perm:
            <input type="hidden" name="version" value="${page.version}"/>
            <input type="submit" value="${_('Revert to this version')}"/>
            # elif page.exists and modify_perm:
            <input type="submit" value="${_('Edit this page')}"
                   accesskey="e" />
            # elif not page.exists and create_perm:
            <input type="submit" value="${_('Create this page')}"
                   accesskey="e" />
            #   if templates:
            <div id="template">
              <label for="template">${_("using the template:")}</label>
              <select name="template">
                <option ${{'selected': not default_template in templates
                        }|htmlattr}
                        value="">${_("(blank page)")}</option>
                # for t in sorted(templates):
                <option value="${t}"
                        ${{'selected': t == default_template
                        }|htmlattr}>${t}</option>
                # endfor
              </select>
            </div>
            #   endif
            # endif
          </div>
        </form>

A <py:choose> and its series of <py:when> translates very smoothly into a sequence of if / elif statements.

Special care should be taken when producing attributes dynamically, as it's the case here for selected. We use the htmlattr filter that takes care of producing the right value for the attribute depending on its content: with a truth value, we'll output selected="selected" otherwise we'll just omit the parameter.

        <py:if test="page.exists">
          <xi:include href="attach_file_form.html"
                      py:with="alist = attachments"/>
        </py:if>
      </py:if>
        #   if page.exists:
        #     with alist = attachments
        #       include "attach_file_form.html"
        #     endwith
        #   endif
        # endif

We pass attachments as alist to the included template.

            <form method="get" action="${href.wiki(page.name)}" 
                  id="rename" py:if="page.exists and rename_perm">
              <div>
                <input type="hidden" name="action" value="rename" />
                <input type="submit" value="${_('Rename page')}" />
              </div>
            </form>
            <form method="get" action="${href.wiki(page.name)}" 
                  id="delete" py:if="page.exists and delete_perm">
              <div>
                <input type="hidden" name="action" value="delete" />
                <input type="hidden" name="version" value="$page.version" />
                <py:if test="page.version == latest_version">
                  <input type="submit" name="delete_version" 
                         value="${_('Delete this version')}" />
                </py:if>
                <input type="submit" value="${_('Delete page')}" />
              </div>
            </form>
          </div>
        </py:if>
      </py:with>
        # if page.exists and rename_perm:
        <form method="get" action="${href.wiki(page.name)}" id="rename">
          <div>
            <input type="hidden" name="action" value="rename" />
            <input type="submit" value="${_('Rename page')}" />
          </div>
        </form>
        # endif
        # if page.exists and delete_perm:
        <form method="get" action="${href.wiki(page.name)}" id="delete">
          <div>
            <input type="hidden" name="action" value="delete" />
            <input type="hidden" name="version" value="${page.version}" />
            # if page.version == latest_version:
            <input type="submit" name="delete_version" value="${_('Delete this version')}" />
            # endif
            <input type="submit" value="${_('Delete page')}" />
          </div>
        </form>
        # endif
      </div>
      # endif
      # endwith

      <div class="wikipage searchable" py:if="not page.exists and higher">
        <p>You could also create the same page higher in the hierarchy:</p>
        <ul>
          <li py:for="markup in higher">${markup}</li>
        </ul>
      </div>
      # if not page.exists and higher:
      <div class="wikipage searchable">
        <p>You could also create the same page higher in the hierarchy:</p>
        <ul>
          # for markup in higher:
          <li>${markup}</li>
          # endfor
        </ul>
      </div>
      # endif

Here's the first use of the for statement. Straightforward.

      <div class="wikipage searchable" py:if="not page.exists and related">
        <p>The following pages have a name similar to this page,
           and may be related:</p>
        <ul>
          <li py:for="markup in related">${markup}</li>
        </ul>
      </div>
      # if not page.exists and related:
      <div class="wikipage searchable">
        <p>${_("The following pages have a name similar to this page, and may be related:")}</p>
        <ul>
          # for markup in related:
          <li>${markup}</li>
          # endif
        </ul>
      </div>
      # endif
    </div>
  </body>
</html>
    </div>

    ${ super() }

    # endblock content
  </body>
</html>

Once we're done with the <div id="content">, we also call ${ super() } to re-insert the parent content of the content block, i.e. the altlinks div and the late_links/late_scripts logic.

Last modified 18 months ago Last modified on Feb 2, 2017, 12:15:40 AM
Note: See TracWiki for help on using the wiki.