= Conversion example Here we detail the conversion process of a sample Genshi template ([source:tags/trac-1.3.1/trac/wiki/templates/wiki_view.html wiki_view.html]) into the corresponding Jinja2 template ([source:cboos.git/trac/wiki/templates/wiki_view.html@jinja2-trunk-r15341 wiki_view.html]). As will become apparent, we mainly use [http://jinja.pocoo.org/docs/dev/templates/#line-statements 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). {{{#!th wiki_view.html (Genshi template) }}} {{{#!th wiki_view.html (Jinja2 template) }}} |------ ||= ||= || {{{#!td {{{#!html+genshi }}} }}} {{{#!td {{{#!html+jinja {# 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 || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi }}} }}} {{{#!td {{{#!html+jinja # extends "layout.html" }}} }}} |----------------------------------------------------------------------------- |||| This is the equivalent of Genshi's ``. 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. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi }}} }}} {{{#!td {{{#!html+jinja }}} }}} |----------------------------------------------------------------------------- |||| 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) || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi }}} }}} {{{#!td {{{#!html+jinja }}} }}} |----------------------------------------------------------------------------- {{{#!td 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. }}} |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi }}} }}} {{{#!td {{{#!html+jinja }}} }}} |----------------------------------------------------------------------------- |||| See above, this will be converted to an initial `extends` statement. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi $title }}} }}} {{{#!td {{{#!html+jinja # block title # if title: ${title} ${ super() } # endif # endblock title # block head # set modify_perm = 'WIKI_MODIFY' in perm(page.resource) # set is_not_latest = page.exists and page.version != latest_version ${ super() } }}} }}} |----------------------------------------------------------------------------- {{{#!td colspan=2 The content of the 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). }}} |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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')}"/> }}} }}} {{{#!td {{{#!html+jinja # 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. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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> }}} }}} {{{#!td {{{#!html+jinja <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). || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi </head> }}} }}} {{{#!td {{{#!html+jinja # endblock head </head> }}} }}} |----------------------------------------------------------------------------- |||| We close the ''head'' block. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <body> <div id="content" class="${classes('wiki', create=not page.exists)}"> }}} }}} {{{#!td {{{#!html+jinja <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. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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> }}} }}} {{{#!td {{{#!html+jinja # 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 }}} }}} |----------------------------------------------------------------------------- {{{#!td colspan=2 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. }}} |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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> }}} }}} {{{#!td {{{#!html+jinja <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> }}} }}} |----------------------------------------------------------------------------- |||| || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <xi:include href="list_of_attachments.html" py:with="alist = attachments; compact = True; foldable = True"/> }}} }}} {{{#!td {{{#!html+jinja # 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. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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"> }}} }}} {{{#!td {{{#!html+jinja # 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. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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> }}} }}} {{{#!td {{{#!html+jinja <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> }}} }}} |----------------------------------------------------------------------------- {{{#!td 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. }}} |----------------------------------------------------------------------------- |||| || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <py:if test="page.exists"> <xi:include href="attach_file_form.html" py:with="alist = attachments"/> </py:if> </py:if> }}} }}} {{{#!td {{{#!html+jinja # if page.exists: # with alist = attachments # include "attach_file_form.html" # endwith # endif # endif }}} }}} |----------------------------------------------------------------------------- |||| We pass `attachments` as `alist` to the included template. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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> }}} }}} {{{#!td {{{#!html+jinja # 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 }}} }}} |----------------------------------------------------------------------------- |||| || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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> }}} }}} {{{#!td {{{#!html+jinja # 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. || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi <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> }}} }}} {{{#!td {{{#!html+jinja # 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 }}} }}} |----------------------------------------------------------------------------- |||| || |----------------------------------------------------------------------------- {{{#!td {{{#!html+genshi </div> </body> </html> }}} }}} {{{#!td {{{#!html+jinja </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. || |-----------------------------------------------------------------------------