Edgewall Software

Changes between Version 17 and Version 18 of TracDev/Proposals/Jinja


Ignore:
Timestamp:
Feb 25, 2016, 1:08:34 AM (8 years ago)
Author:
Christian Boos
Comment:

move the theme presentation to HtmlTemplates

Legend:

Unmodified
Added
Removed
Modified
  • TracDev/Proposals/Jinja

    v17 v18  
    1111   - 127/898 plugins (14.1%) on trac-hacks.org use `filter_stream()`
    1212   - -> [PortingFromGenshiToJinja#ReplacingITemplateStreamFilter replacing ITemplateStreamFilter]
    13  * how to handle themeing? -> see [#Themeing] below
     13 * how to handle themeing? -> see HtmlTemplates#Jinjaarchitecture
    1414 * should we rewrite tag builders or use lightweight string templates? -> [PortingFromGenshiToJinja#tag tag] `Fragment`/`Element` builder has been reimplemented
    1515 * others?
     
    6161In summary, this means that for the big problematic pages, we can easily have a 10x speedup and more, by migrating to Jinja2, and this with a much lighter memory footprint.
    6262For smaller pages, the speed-up is between 5x to 10x as well.
    63 
    64 
    65 == Implementation details
    66 
    67 === Themeing
    68 
    69 We summarize the current template page architecture we use since the adoption of the Genshi template engine (i.e. since Trac 0.11), before describing the new but similar template page architecture that we'll use with the Jinja2 template engine.
    70 
    71 ==== Genshi theme
    72 I've never really tried the TH:ThemeEnginePlugin plugin, or alternatives, so I can't be sure if I got it right, but from what I can see in Trac's code base itself, the idea with Genshi-based themeing (and page architecture in general) was to have a dynamically loaded "theme" template page that would primarily be in charge of the main structure of all HTML pages.
    73 
    74 Let's take the example of a simple "end user" page, the search.html page.
    75 What happens is that:
    76  - search.html includes
    77    - layout.html, which includes
    78      - $chrome.theme (typically "theme.html", the default theme page that ships with Trac)
    79 
    80 In more details, the
    81 [source:tags/trac-1.0.9/trac/search/templates/search.html search.html] is structured like this:
    82 {{{#!html+genshi
    83 <html>
    84   <xi:include href="layout.html" />
    85   <head>
    86     <title>Search</title>
    87     ...
    88   </head>
    89   <body>
    90     <div id="content" class="search">
    91       <h1>Search</h1>
    92       ...
    93     </div>
    94   </body>
    95 </html>
    96 }}}
    97  - it starts by including the layout.html page (`<xi:include href="layout.html" />`)
    98  - it then provides the `<head>` and `<body>` elements specific to that search page;
    99    these elements will be processed by the `<py:match>`es filters defined so far,
    100    in the order in which they have been included
    101 
    102 The [source:tags/trac-1.0.9/trac/templates/layout.html  layout.html]
    103 page in turn is structured like this:
    104 {{{#!html+genshi
    105 <html>
    106   <py:match path="head"><head>
    107     <title py:with="title = list(select('title/text()'))">
    108       ${title} – ${project.name or 'Trac'}  <!-- e.g.  "Search - Trac" -->
    109     </title>
    110     ...
    111     ${select("*[local-name() != 'title']|text()|comment()")}
    112   </head></py:match>
    113 
    114   <py:match path="body"><body>
    115 
    116     ${select('*|text()|comment()')}         <!-- e.g. "<h1>Search</h1>"
    117                                                        ... -->
    118     ...
    119     <div id="altlinks" py:if="'alternate' in chrome.links">
    120       ...
    121     </div>
    122   </body></py:match>
    123 
    124   <xi:include href="$chrome.theme"><xi:fallback /></xi:include>
    125 </html>     
    126 }}}
    127  - it defines `<py:match>` filters for transforming the content provided
    128    in `<head>` and `<body>` elements in the including template (search.html);
    129    the head filter adds some content to the `<title>` and prepends some content
    130    in the <head>, the body filter appends some content in  the <body>
    131  - it then **dynamically** includes some "theme" page, `$chrome.theme`
    132 
    133 By default, this theme page will be our
    134 [source:tags/trac-1.0.9/trac/templates/theme.html theme.html] template:
    135 {{{#!xml
    136 <html>
    137   <body>
    138     <div id="banner">
    139       ...
    140     </div>
    141     <div id="main">
    142       ...
    143       ${select('*|text()|comment()')}       <!-- e.g. "<h1>Search</h1>"
    144                                                        ...
    145                                                        <div id="altlinks" ... </div> -->
    146     </div>
    147     <div id="footer">
    148       ...
    149     </div>
    150   </body>
    151 </html>
    152 }}}
    153  - it defines a `<py:match>` filter on the `<body>` tag
    154    (the one that will be produced by the previously applied filters,
    155    i.e. the output of the `<py:match>` from the layout.html)
    156    and it will **embed** that element into some predefined HTML
    157    structure (inside a div element), and
    158    it will prepend and append other divs around it:
    159 
    160 So this dynamic theme template has the last say, and can theoretically re-order
    161 the content generated by the previous filters any way it likes, although in practice
    162 it simply inserts the body content produced by previous steps inside a predefined
    163 structure (the `<div id="main">`).
    164 
    165 An example of another theme page: [https://github.com/chevah/trac-bootstrap-theme/blob/master/templates/theme.html trac-bootstrap-theme's theme.html].
    166 
    167 
    168 
    169 Note that this scheme can be extended to additional intermediate levels,
    170 for example see what we do with the admin panels.
    171 
    172 One such panel is
    173 [source:tags/trac-1.0.9/trac/admin/templates/admin_basics.html admin_basics.html]:
    174 {{{#!html+genshi
    175 <html>
    176   <xi:include href="admin.html" />
    177   <head>
    178     <title>Basics</title>
    179   </head>
    180 
    181   <body>
    182     ...
    183   </body>
    184 </html>
    185 }}}
    186  - it starts by including the admin.html page
    187  - then it provides the `<head>` and `<body>` elements specific to that panel
    188    
    189 The [source:tags/trac-1.0.9/trac/admin/templates/admin.html admin.html] page
    190 is similar to the search.html page in that it includes the layout.html, but
    191 it also first contains its own `<py:match>` templates to organize the content of
    192 the admin panel which included it:
    193 {{{#!html+genshi
    194 <html>
    195   <py:match path="head" once="true"><head>
    196     <title>Administration: ${select('title/text()')}</title>
    197     ${select("*[local-name() != 'title']")}
    198   </head></py:match>
    199 
    200   <py:match path="body" once="true" buffer="false"><body>
    201     <div id="content" class="admin">
    202       <h1>Administration</h1>
    203       <div id="tabs">
    204       <div id="tabcontent">
    205         ${select("*|text()")}
    206         <br style="clear: right" />
    207       </div>
    208     </div>
    209 
    210   </body></py:match>
    211 
    212   <xi:include href="layout.html" />
    213 </html>
    214 }}}
    215  - defines `<py:match>` filters for `<head>` and `<body>` (of the including panel page),
    216    which in turn will produce modified `<head>` and `<body>` elements
    217  - it then includes the layout.html page (see above)
    218 
    219 Feel free to brush up your Genshi craft by reading ([G:GenshiTutorial#AddingaLayoutTemplate]),
    220 as I just did ;-)
    221 
    222 
    223 ==== Jinja2 theme
    224 
    225 Fortunately Jinja2 can do dynamic includes as well, or more precisely, dynamic ''extends''.
    226 Therefore the same idea can be transposed: have the end user page extend the layout page,
    227 then have the layout page extend whatever has been defined to be the theme page.
    228 
    229 The differences with Genshi are subtle: while in both case the control of the
    230 output is delegated to the more generic page, with Jinja2 the parent only controls
    231 what it puts around //blocks//. It can put some default content in these blocks,
    232 but the end user page has the final say about what to do with this default content,
    233 as it can reuse it inside its block (by calling `super()`) or not.
    234 
    235 Let's transpose the previous example of the search.html template.
    236 For as long as we'll have both template engines coexisting,
    237 we'll prefix the new Jinja2 templates with a `j`.
    238 So we're now discussing:
    239  - jsearch.html, which extends
    240    - jlayout.html, which extends
    241      - jtheme.html (as this is our default theme page)
    242 
    243 In more details, we describe what happens with the
    244 [source:cboos.git/trac/search/templates/jsearch.html jsearch.html] page:
    245 {{{#!html+jinja
    246 # extends 'jlayout.html'
    247 
    248 <!DOCTYPE html>
    249 <html>
    250   <head>
    251     <title>
    252     #   block title
    253     ${_("Search")}    ${ super() }
    254     #   endblock title
    255     </title>
    256 
    257     # block head
    258     ${ super() }
    259     ...
    260     # endblock head
    261   </head>
    262 
    263   <body>
    264     # block content
    265     <div id="content" class="search">
    266       <h1>${_("Search")}</h1>
    267       ...
    268     </div>
    269   </body>
    270 </html>
    271 }}}
    272  - it starts by //extending// the jlayout.html page (`# extends 'jlayout.html')
    273  - then it redefines the ''title'', ''head'' and ''content'' blocks,
    274    and has to place a `${ super() }` expression in order to insert the default
    275    content proposed by the extended template at the right place;
    276    note that the presence of the `<html>`, `<head>` and `<body>` tags here
    277    is strictly "decorative", it will be ignored in the final output. As we're
    278    in a template extending another, only what's in the redefined blocks matters.
    279 
    280 These blocks are first defined in the extended template, jlayout.html.
    281 
    282 The [source:cboos.git/trac/templates/jlayout.html jlayout.html] page looks like this:
    283 {{{#!html+jinja
    284 # extends ('j' + chrome.theme)
    285 
    286 <!DOCTYPE html>
    287 <html>
    288   <head>
    289     # block head
    290 
    291     <title>
    292     #  block title
    293     – ${project.name or 'Trac'}
    294     #  endblock title
    295     </title>
    296 
    297     ...
    298     # endblock head
    299   </head>
    300 
    301   <body>
    302     # block content
    303     # endblock content
    304 
    305     ...
    306     <div id="altlinks">
    307       ...
    308     </div>
    309   </body>
    310 </html>
    311 }}}
    312  - first **dynamically** //extends// in turn some "theme" page (`# extends ('j' + chrome.theme)`)
    313  - then the jlayout.html template defines a few blocks:
    314     - the ''head'' block and its ''title'' sub-block; here we understand why we've put
    315       the ''title'' block **outside** of the ''head'' block in the jsearch.html template:
    316       the jlayout.html's ''head'' block contains among other things a `<title>` element,
    317       and we're reusing  that default content in the inheriting ''head'' block; if in
    318       jsearch.html we had defined the <title> element in the ''head'' block as well,
    319       we would have had two of these <title> elements in that block
    320     - the ''content'' block which is filled with some predefined, generic content,
    321       mostly the same stuff that could be found in the corresponding layout.html,
    322       in `<py:match>` filters
    323 
    324 By default, this theme template will be our
    325 [source:cboos.git/trac/templates/jtheme.html jtheme.html] page:
    326 {{{#!html+jinja
    327 
    328 <!DOCTYPE html>
    329 <html>
    330   <head>
    331     # block head
    332     # endblock head
    333   </head>
    334 
    335   <body>
    336     # block body
    337     <div id="banner">
    338       ...
    339     </div>
    340     <div id="main">
    341       ...
    342       # block content
    343       (here goes the content of the content block produced by layout.html)
    344       # endblock content
    345     </div>
    346     <div id="footer">
    347       ...
    348     </div>
    349     # block body
    350   </body>
    351 </html>
    352 }}}
    353  - it defines a ''head'' block inside an otherwise empty `<head>` element;
    354    this means this is simply a "slot" that will be filled by the content
    355    of the `head` block in the extending templates (in this case, jlayout.html)
    356  - it contains a `<body>` element;
    357    as we want to replicate what the original theme.html did,
    358    what we want to achieve here is to provide a ''slot''
    359    at the place where we want to insert the content
    360    produced by the jlayout.html template;
    361    I didn't name that inner block "body", as this could be confusing:
    362    we're not in control of the `<body>` element there, just of
    363    a fraction of it, the bottom part of the main div.
    364 
    365 Note that //if// we wanted to be in full control of the body in the extending
    366 template, we could (as opposed to what you can do in Genshi): we would simply
    367 have to redefine the ''body'' block which contains all of the default structure
    368 (possibly reusing the content of that block by a call to `${ super() }`).
    369 In our case, neither jsearch.html nor jlayout.html redefine the `body` block,
    370 as they're happy with what jtheme does with it.
    371 
    372 
    373 Depending how one looks at it, it seems this approach is even more flexible than
    374 what we had in Genshi, as the end user template can decide which bits of the
    375 parent template it wants or not ("bottom-up" control), something that was not
    376 readily doable with Genshi ("top-down" control).
    377 
    378 
    379 Like what we did with Genshi, this scheme can be extended to support additional intermediate levels. We did that for the admin and the preference panels.
    380 
    381 Let's take for example the Logging admin panel, [source:cboos.git/trac/admin/templates/jadmin_logging.html jadmin_logging.html]:
    382 {{{#!html+jinja
    383 # extends "jadmin.html"
    384 <html>
    385   <head>
    386     <title>
    387       # block admintitle
    388       Logging
    389       # endblock admintitle
    390     </title>
    391   </head>
    392 
    393   <body>
    394     # block adminpanel
    395     ...
    396     # block adminpanel
    397   </body>
    398 </html>
    399 }}}
    400  - it starts by extending the jadmin.html page
    401  - then it provides the `<head>` and `<body>` elements specific to that panel,
    402    more precisely the //admintitle// and //adminpanel// blocks (if some JavaScript
    403    or other resources are needed, the //head// could be redefined as well)
    404    
    405 The [source:cboos.git/trac/admin/templates/jadmin.html@jinja2 jadmin.html] page
    406 is similar to the jsearch.html page in that it extends the jlayout.html:
    407 {{{#!html+jinja
    408 # extends "jlayout.html"
    409 <html>
    410   <head>
    411     <title>
    412       # block title
    413       Administration:
    414       #   block admintitle
    415       #   endblock admintitle
    416       ${ super() }
    417       # endblock
    418     </title>
    419   </head>
    420 
    421   <body>
    422     # block content
    423     <div id="content" class="admin">
    424       <h1>Administration</h1>
    425       <div id="tabs">
    426       <div id="tabcontent">
    427         # block adminpanel
    428         # endblock adminpanel
    429         <br style="clear: right" />
    430       </div>
    431     </div>
    432     # endblock content
    433   </body>
    434 </html>
    435 }}}
    436 
    437 //See [1df4e05c/cboos.git] for the full conversion of admin.html -> jadmin.html and admin_logging.html -> jadmin_logging.html.//
    438 
    439 
    440 I omitted the discussion of the
    441 replacement for the `<xi:include href="site.html>` template
    442 (`# include site_head.html` and `# include site_body.html`).
    44363
    44464