Using a Before Render Event to Expose an h Helper Object

Pylons 1.X exposed a module conventionally named helpers.py as an h object in the top-level namespace of each Mako/Genshi/Jinja2 template which it rendered. You can emulate the same behavior in Pyramid by using a BeforeRender event subscriber.

First, create a module named helpers.py in your Pyramid package at the top level (next to __init__.py). We'll import the Python standard library string module to use later in a template:

# helpers.py

import string

In the top of the main __init__ module of your Pyramid application package, import the new helpers module you created, as well as the BeforeRender event type. Underneath the imports create a function that will act as an event subscriber:

1# __init__.py
2
3from pyramid.events import BeforeRender
4from myapp import helpers
5
6def add_renderer_globals(event):
7    event['h'] = helpers

Within the main function in the same __init__, wire the subscriber up so that it is called when the BeforeRender event is emitted:

1def main(global_settings, **settings):
2    config = Configurator(....) # existing code
3    # .. existing config statements ... #
4    config.add_subscriber(add_renderer_globals, BeforeRender)
5    # .. other existing config statements and eventual config.make_app()

At this point, with in any view that uses any templating system as a Pyramid renderer, you will have an omnipresent h top-level name that is a reference to the helpers module you created. For example, if you have a view like this:

@view_config(renderer='foo.pt')
def aview(request):
    return {}

In the foo.pt Chameleon template, you can do this:

1 ${h.string.uppercase}

The value inserted into the template as the result of this statement will be ABCDEFGHIJKLMNOPQRSTUVWXYZ (at least if you are using an English system).

You can add more imports and functions to helpers.py as necessary to make features available in your templates.

Using a BeforeRender Event to Expose a Mako base Template

If you wanted to change templates using %inherit based on if a user was logged in you could do the following:

1@subscriber(BeforeRender)
2def add_base_template(event):
3    request = event.get('request')
4    if request.user:
5        base = 'myapp:templates/logged_in_layout.mako'
6        event.update({'base': base})
7    else:
8        base = 'myapp:templates/layout.mako'
9        event.update({'base': base})

And then in your mako file you can call %inherit like so:

<%inherit file="${context['base']}" />

You must call the variable this way because of the way Mako works. It will not know about any other variable other than context until after %inherit is called. Be aware that context here is not the Pyramid context in the traversal sense (which is stored in request.context) but rather the Mako rendering context.

Using a BeforeRender Event to Expose Chameleon base Template

To avoid defining the same basic things in each template in your application, you can define one base template, and inherit from it in other templates.

注釈

Pyramid example application - shootout using this approach.

First, add subscriber within your Pyramid project's __init__.py:

config.add_subscriber('YOURPROJECT.subscribers.add_base_template',
                      'pyramid.events.BeforeRender')

Then add the subscribers.py module to your project's directory:

1from pyramid.renderers import get_renderer
2
3def add_base_template(event):
4    base = get_renderer('templates/base.pt').implementation()
5    event.update({'base': base})

After this has been done, you can use your base template to extend other templates. For example, the base template looks like this:

 1<html xmlns="http://www.w3.org/1999/xhtml"
 2      xmlns:tal="http://xml.zope.org/namespaces/tal"
 3      xmlns:metal="http://xml.zope.org/namespaces/metal"
 4      metal:define-macro="base">
 5    <head>
 6        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
 7        <title>My page</title>
 8    </head>
 9    <body>
10        <tal:block metal:define-slot="content">
11        </tal:block>
12    </body>
13</html>

Each template using the base template will look like this:

1<html xmlns="http://www.w3.org/1999/xhtml"
2      xmlns:tal="http://xml.zope.org/namespaces/tal"
3      xmlns:metal="http://xml.zope.org/namespaces/metal"
4      metal:use-macro="base.macros['base']">
5    <tal:block metal:fill-slot="content">
6        My awesome content.
7    </tal:block>
8</html>

The metal:use-macro="base.macros['base']" statement is essential here. Content inside <tal:block metal:fill-slot="content"></tal:block> tags will replace corresponding block in base template. You can define as many slots in as you want. For more information please see Macro Expansion Template Attribute Language documentation.

Using Building Blocks with Chameleon

If you understood the base template chapter, using building blocks is very simple and straight forward. In the subscribers.py module extend the add_base_template function like this:

 1from pyramid.events import subscriber
 2from pyramid.events import BeforeRender
 3from pyramid.renderers import get_renderer
 4
 5@subscriber(BeforeRender)
 6def add_base_template(event):
 7    base = get_renderer('templates/base.pt').implementation()
 8    blocks = get_renderer('templates/blocks.pt').implementation()
 9    event.update({'base': base,
10                  'blocks': blocks,
11                  })

Make Pyramid scan the module so that it finds the BeforeRender event:

1def main(global_settings, **settings):
2    config = Configurator(....) # existing code
3    # .. existing config statements ... #
4    config.scan('subscriber')
5    # .. other existing config statements and eventual config.make_app()

Now, define your building blocks in templates/blocks.pt. For example:

 1<html xmlns="http://www.w3.org/1999/xhtml"
 2      xmlns:tal="http://xml.zope.org/namespaces/tal"
 3      xmlns:metal="http://xml.zope.org/namespaces/metal">
 4  <tal:block metal:define-macro="base-paragraph">
 5    <p class="foo bar">
 6      <tal:block metal:define-slot="body">
 7      </tal:block>
 8    </p>
 9  </tal:block>
10
11  <tal:block metal:define-macro="bold-paragraph"
12             metal:extend-macro="macros['base-paragraph']">
13    <tal:block metal:fill-slot="body">
14      <b class="strong-class">
15        <tal:block metal:define-slot="body"></tal:block>
16      </b>
17    </tal:block>
18  </tal:block>
19</html>

You can now use these building blocks like this:

 1<html xmlns="http://www.w3.org/1999/xhtml"
 2      xmlns:tal="http://xml.zope.org/namespaces/tal"
 3      xmlns:metal="http://xml.zope.org/namespaces/metal"
 4      metal:use-macro="base.macros['base']">
 5  <tal:block metal:fill-slot="content">
 6    <tal:block metal:use-macro="blocks.macros['base-paragraph']">
 7      <tal:block metal:fill-slot="body">
 8        My awesome paragraph.
 9      </tal:block>
10    </tal:block>
11
12    <tal:block metal:use-macro="blocks.macros['bold-paragraph']">
13      <tal:block metal:fill-slot="body">
14        My awesome paragraph in bold.
15      </tal:block>
16    </tal:block>
17
18  </tal:block>
19</html>

Rendering None as the Empty String in Mako Templates

For the following Mako template:

<p>${nunn}</p>

By default, Pyramid will render:

<p>None</p>

Some folks prefer the value None to be rendered as the empty string in a Mako template. In other words, they'd rather the output be:

<p></p>

Use the following settings in your Pyramid configuration file to obtain this behavior:

[app:myapp]
mako.imports = from markupsafe import escape_silent
mako.default_filters = escape_silent