Edgewall Software

web2py allows for almost seemless integration with Genshi.

The first thing to do, after you've installed web2py and Genshi, is to save the code below into a module named Genshi4web2py.py and place it somewhere in your PYTHONPATH (site-packages or in the root of web2py).

#!/usr/bin/env python

'''
The Genshi4web2py module provides Genshi markup templating functionality to the
web2py framework.  It does not provide Genshi's text-based templating since
web2py's built-in template is so similar, it would be redundant.

To enable Genshi templating on a controller basis, put the following code in
your controller::

    from Genshi4web2py import render
    response.postprocessing.append(lambda x: render(x, request, response))

If you would prefer to enable Genshi for a complete Application, the above code
can be placed in a model file.  This module can also make use of web2py's
cache system for a large speed boost.  To do so pass cache to render like so::

    response.postprocessing.append(lambda x: render(x, request, response, cache=cache))

Note that using the cache for templates will cause frustration in a development 
environment since changes to a view will only be reflected when the cache
refreshes (the default is every 10 minutes).

@var regex_include: a compiled regular expression used to identify Genshi 
    templates.
@var Genshi: indicates if Genshi support is enabled
@var READLIMIT: the max number of bytes of a template to read when identifying
    a template as a Genshi template
'''
import re
import os
import types

try:
    from genshi import HTML
    from genshi.filters.html import HTMLFormFiller
    from genshi.template import Context, TemplateSyntaxError
    from genshi.template.loader import TemplateLoader, TemplateNotFound
    Genshi = True
    READLIMIT=512
    regex_include=re.compile('(?P<all>(?:\{\{\s*include\s+[\'"]|' +
                             '<xi:include\s+href=[\'"])(?P<name>[^\'"]*)' +
                             '[\'"]\s*(?:\}\}|(?:/>|>\s*</xi:include>)))')
except ImportError, e: Genshi = False

def render(vars, request, response, **kwargs):
    '''Renders the controller output with a Genshi Template

    B{Render Options} - "renderOptions" can be specified as a dictionary in 
    L{vars}.  It is used to specify certain options the rendering engine 
    should use.  Specifying any of these is optional.  Below are the specific 
    valid rendering options::

        - B{viewdirs}: a list of paths to template folders in which to look 
            for views
        - B{view}: the name of the view to use for the controller calling 
            L{render}
        - B{Content-Type}: the Content-Type header as it should be sent to 
            the browser
        - B{HTMLFormFill}: a dictionary of values used to fill elements of 
            forms in side the template after template parsing
        - B{GenshiDoctype}: the doctype that Genshi will apply to the resulting 
            rendered output
        - B{GenshiRenderEngine}: the engine Genshi will use to render the view

    @type vars: dict
    @param vars: a dictionary of variables to pass to the template
    @param request: the web2py request instance.  Used to determine the current
        application folder
    @param response: the web2py response instance.  Used to set headers if
        needed.
    @type kwargs: dict
    @param kwargs: any additional keyword arguments will be made available for
        the run environment of the view.

    @returns: If the template exists and is valid, a normal string will be
        returned; otherwise the passed L{vars} will be returned to be parsed by
        a downstream template renderer.

    @raises TemplateSyntaxError: In the event that a template has a syntax
        error, a TemplateSyntaxError will be raised.
    '''

    if type(vars) == types.StringType:
        return vars

    cache = kwargs.get('cache', None)
    appfolder = os.path.dirname(os.path.normpath(request.folder))
    initview = os.path.join(appfolder, 'init', 'views')
    viewdirs = [ os.path.join(request.folder, 'views') ]

    if os.path.exists(initview) and viewdirs[0] != initview:
        viewdirs.append(initview)

    opts = {'viewdirs': viewdirs
            ,'view':response.view
            ,'Content-Type':"text/html; charset=utf-8"
            ,'GenshiDoctype':'html'
            ,'GenshiRenderEngine':'html'
            ,'HTMLFormFill':vars.get('HTMLFormFill')
            }

    if vars.get('renderOptions'):
        opts.update(vars['renderOptions'])

    cachekey = ''.join(opts['viewdirs'])
    if cache:
        loader = cache.ram(cachekey
                           , lambda:TemplateLoader(opts['viewdirs']
                                                   , auto_reload=False))
    else:
        loader = TemplateLoader(opts['viewdirs'], auto_reload=False)

    try:
        if cache:
            output = cache.ram(cachekey+opts['view']
                               , lambda:loader.load(opts['view']))
        else:
            output = loader.load(opts['view'])
    except TemplateNotFound, e:
        return vars
    except TemplateSyntaxError, e:
        f = open(os.path.normpath(e.filename)).read(READLIMIT)
        if not regex_include.search(f):
            return vars
        else:
            raise e

    ctxt = Context(request=request, response=response, HTML=HTML)
    ctxt.update(vars)
    ctxt.update(kwargs)
    output = output.generate(ctxt)
    if opts.get('HTMLFormFill'):
        try:
            output = output.filter(HTMLFormFiller(data=opts['HTMLFormFill']))
        except:
            pass
    output = output.render(opts['GenshiRenderEngine']
                           , doctype=opts['GenshiDoctype'])

    response.headers['Content-Type']=opts['Content-Type']
    return output

(You may also follow the instructions in the docstring of the module for usage instructions.)

Second, create an application in web2py. There are two ways to hook Genshi into web2py, application-wide or controller-wide. In either case, the inclusion code looks like:

    from Genshi4web2py import render
    response.postprocessing.append(lambda x: render(x, request, response))

If you wish to use Genshi throughout that application, then you can place this code in a model file so that it is executed for every controller in the application. If, however, you wish to only use it with one controller, include this code somewhere in the controller.

To invoke Genshi there is nothing more to do, simply make your controllers as you would if using normal web2py templates and make your views as Genshi templates. Genshi4web2py will look for the view corresponding to the running controller and detect if it is a Genshi template. If so, it will render it, otherwise it will pass it on to the internal web2py template renderer.

Last modified 14 years ago Last modified on Apr 12, 2011, 8:34:21 PM