using evoque with pylons

Pylons encourages a good separation
between the model, the controllers, and the views. It also is very
flexible about using different template engines for different views.
This note discusses a way for how to set and use
Evoque Templating as the default template engine in
a Pylons (minimum version 0.9.7) application.

You may also wish to explore the
QuickWikiEvoque
tutorial application, that offers a complete Pylons 0.9.7 + SQLAlchemy 0.5
application that you can downlaod and play with.

Let’s assume we create a site from scratch, let’s call it evoque_site.
When asked, enter the template_engine as follows:

$ paster create –template=pylons evoque_site
Selected and implied templates:
Pylons#pylons Pylons application template

Variables:
egg: evoque_site
package: evoque_site
project: evoque_site
Enter template_engine (mako/genshi/jinja/etc: Template language) [‘mako’]: evoque
Enter sqlalchemy (True/False: Include SQLAlchemy 0.4 configuration) [False]:

adjust application files

We will then need to add the following codes to the files/functions indicated:

evoque_site.config.environment.load_environment(global_conf, app_conf)

    # Evoque Templating: 
    # http://evoque.gizmojo.org/ext/pylons/
    # http://evoque.gizmojo.org/usage/api/
    
    import logging
    from evoque.domain import Domain
    from webhelpers.html import literal
    
    evoque_domain = Domain(
        # root folder for the default template collection, must be abspath;
        # as default use pylon's first templates folder
        os.path.join(root, 
                app_conf.get("evoque.default_dir") or paths['templates'][0]),
        
        # whether evaluation namespace is restricted or not 
        restricted = app_conf.get("evoque.restricted")=="true",    
        
        # how should any evaluation errors be rendered
        # int 0 to 4, for: [silent, zero, name, render, raise]
        errors = int(app_conf.get("evoque.errors", 3)),
        
        # evoque logger; additional setings should be specified via the pylons
        # config ini file, just as for any other logger in a pylons application
        log = logging.getLogger("evoque"), 
        
        # [collections] int, max loaded templates in a collection
        cache_size = int(app_conf.get("evoque.cache_size", 0)), 
        
        # [collections] int, min seconds to wait between checks for
        # whether a template needs reloading
        auto_reload = int(app_conf.get("evoque.auto_reload", 60)), 
        
        # [collections] bool, consume all whitespace trailing a directive
        slurpy_directives = app_conf.get("evoque.slurpy_directives")=="true",
        
        # [collections/templates] str or class, to specify the *escaped* 
        # string class that should be used i.e. if any str input is not of 
        # this type, then cast it to this type). 
        # Builtin str key values are: "xml" -> qpy.xml, "str" -> unicode
        # In a Pylons context we use the webhelpers.html.literal class
        quoting = app_conf.get("evoque.quoting", literal),
        
        # [collections/templates] str, preferred encoding to be tried 
        # first when decoding template source. Evoque decodes templates 
        # strings heuristically, i.e. guesses the input encoding.
        input_encoding = app_conf.get("evoque.input_encoding", "utf-8"),
        
        # [collections/templates] list of filter functions, each having 
        # the following signature: filter_func(s:basestring) -> basestring
        # The functions will be called, in a left-to-right order, after 
        # template is rendered. NOTE: not settable from the conf ini.
        filters=[]
    )
    
    # default template (optional) i.e. what to receive
    # when calling domain.get_template() with no params
    if app_conf.get("evoque.default_template"):
        evoque_domain.get_collection().get_template("", 
                src=app_conf.get("evoque.default_template"))
    
    # attach evoque domain to app_globals
    config['pylons.app_globals'].evoque_domain = evoque_domain

development.ini

The above config load_environment() code may be
customized on a per-deployment basis, by specifying any of the
following parameters under the [app:main]
section of the the deployment’s .ini file:

# Evoque Domain : http://evoque.gizmojo.org/ext/pylons/
evoque.default_dir = 
evoque.default_template = 
evoque.restricted = false
evoque.errors = 3 
evoque.cache_size = 0 
evoque.auto_reload = 2
evoque.slurpy_directives = true
evoque.quoting = 
evoque.input_encoding = utf-8

If evoque.default_dir is not set,
then Pylon’s first templates folder is used.

The filters parameter (on Domain it only serves as site-wide default)
is not settable in the ini.
For a representative usage example of filters see the
Sub-templates in markdown howto.

logging.getLogger(“evoque”)

Just like any other logger used in a Pylons application, the
evoque logger may be adjusted via a deployment’s
conf ini file.

evoque_site/lib/base.py

from pylons.templating import pylons_globals, cached_template

def render_evoque(template_name, extra_vars=None, cache_key=None,
                    cache_type=None, cache_expire=None,
                    collection=None, raw=False, quoting=None):
    """ Render a template with Evoque : http://evoque.gizmojo.org/ext/pylons/
	
    Accepts the cache options ``cache_key``, ``cache_type``, and
    ``cache_expire``. 
    
    The remaining options affect how template is located, loaded and rendered:
    - template_name: normally this is the collection-root-relative locator for
          the template; if template had been previously with location specified
          via the src option, then may be any arbitrary string identifier.
    - collection: either(None, str, Collection), an existing collection, 
          None implies the default collection
    - raw: bool, render the raw template source 
    - quoting: either(str, type), sets the Quoted-No-More string class to use, 
          None uses the collection's default, 
          "xml" uses qpy.xml, 
          "str" uses unicode
    """
    # Create a render callable for the cache function
    def render_template():
        # Get the globals
        # Note: when running in restricted mode which of these globals are
        # to be exposed should be a lot more selective. 
        pg = pylons_globals()
        
        # Update any extra vars if needed
        if extra_vars:
            pg.update(extra_vars)
        
        # Grab a template reference
        template = pg['app_globals'].evoque_domain.get_template(
            template_name, collection=collection, raw=raw, quoting=quoting)
        return template.evoque(pg, raw=raw, quoting=quoting)
    
    return cached_template(template_name, render_template, cache_key=cache_key,
                    cache_type=cache_type, cache_expire=cache_expire)

render = render_evoque

evoque_site/setup.py

install_requires=[
    "Pylons>=0.9.7",
    ...,
    "evoque>=${folder.dist["version"]}"
]

add a controller and template

Let’s first add and edit a simple controller
and then add a template just to test:

$ paster controller hello

evoque_site/controllers/hello.py

class HelloController(BaseController):

    def index(self):
        # Return a rendered template with evoque 
        c.title = "Hello from evoque!"
        return render("template.html")

evoque_site/templates/template.html

<html>
<head><title>evoque + pylons : ${c.title}</title></head>
<style type="text/css">
div.code {
  white-space: pre; font-family: monospace; line-height: 1.1em;
  padding: 0.6em; margin-bottom: 2em;
  border: 1px solid #ccc; background-color: #fafafe;
}
</style>
<body>
<h2>${c.title}</h2>

<h3>template evaluation context</h3>

<div class="code">${inspect()}</div>

</body>
</html>

Define a route for the hello.index() view, and load it
in your web browser.
You should see a simple page that dumps the template evaluation context.