Sunday, March 25, 2007

Genshi Templates in Nevow

Update: When done reading through this example and code, please see the most recent blog post about this that addresses the dynamic rendering of Genshi templates in Nevow.

I have been exploring the extensibility of Nevow, and I'm finding it surprisingly easy to integrate other tools. I've only used Genshi for recent Trac hacking, but I hear more and more about it and it seems to be gaining a little momentum as one of the preferred template systems for python web application developers.

Using the code from the blog post Object Publishing with Nevow, I have created an example of how to integrate Genshi with Nevow. I will provide a link to the source code at the end of the post.

Integrating another templating system into Nevow is actually a no-brainer: you just need to have the "alien" template parsed by its own parsers prior to Nevow performing its parsing. This impacts only one module: nevow.loaders.

Before we dig any deeper, how about some eye-candy? Then a quick review, and finally the code. Here are some screenshots of the Genshi templates running along-side native Nevow templates:


434358390 65710Bc3A0 O
434358446 Df7816377A O

The second image is the page in the little demo that showcases all the functionality of Genshi as documented on this page.

Traditionally with Nevow, Page classes contain data_*() and render_*() methods. These Page classes are "web resources" whose locateChild() method is called after an HTTP GET. Each page class that will have an HTML view needs to set a docFactory attribute; this is the loader instance I referred to above.

Here is a toy example of a page class from a previous blog post:
from nevow import rend
from nevow import loaders

class SiteRoot(rend.Page):

docFactory = loaders.xmlstr('''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"
xmlns:nevow="http://nevow.com/ns/nevow/0.1">
</html>''')
What we need is to be able to do something link this:
from nevow import rend

import myloaders

class GenshiStuffResource(rend.Page):

docFactory = myloaders.genshistr('''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"
xmlns:nevow="http://nevow.com/ns/nevow/0.1"
xmlns:genshi="http://genshi.edgewall.org/">
<h1>Genshi Test</h1>
<div>
<span genshi:with="y=7; x=y**2; z=x+10">$x $y $z</span>
</div>
<div>
<span>Your name is ${username}</span>
</div>
</html>''')
To make this work, all we have to do is create myloaders.py with the following content:

from genshi.template import MarkupTemplate

from nevow import inevow
from nevow.loaders import xmlstr

class genshistr(xmlstr):

def load(self, ctx=None, preprocessors=()):
attrs = inevow.IRenderer(ctx).__dict__
tmpl = MarkupTemplate(self.template)
xmlStr = tmpl.generate(**attrs).render('xhtml')
self.template = xmlStr
return super(genshistr, self).load(ctx=ctx,
preprocessors=preprocessors)
There is a minimal amount of magic happening here: MarkupTemplate.generate() takes keywords (or a dict, with extended call syntax) and those key/value pairs are available to the template that is rendered. We are using our class's __dict__ for this; so if you want data in your template, you need to make it a class attribute. There are many ways to do this, and you can choose the one that is safest, cleanest, and least consumptive of resources for your application.

This is an example using a string as a template. The source code link below includes a folder where the templates are broken out into files and with a myloaders.genshifile class defined. The screenshots above were taken from a running instance of that example.

Browse the example source code here: nevow/templating/genshi.

Technorati Tags: , , , ,

4 comments:

Karl Bartel said...

This is really interesting. I prefer Genshi templates to the nevow XML templates. But I'm using Genshi directly on top of twisted.web .
Which nevow benefits are left, when you don't use its templating system? Anything except the possibility to use athena?

Duncan McGreggor said...

Well, since I do lots of non-web Twisted and much of it can integrate with the web, I like to use a web framework that can understand deferreds. I have also slowly been moving away from HTML forms and towards Athena (eventually my stuff will be 100% Athena), so your point about Athena is correct (and for me, a big motivation to stay with Nevow). I also use Axiom for all my data needs whenever possible.

What's more is that there is a very elegant abstraction with Nevow, similar to what one does with Zope's view adaptation: adapting a page Fragment/Element to an Axiom item (or any other data store one writes support for) provides the page (or part of the page) represented by the Fragment/Element with the data that it needs. This is very powerful, concise, and an elegant way to both keep the data and the page logic separate while providing clean access to the data.

Um, so I guess that last paragraph means "I use Nevow for style/aesthetics, too." ;-)

Karl Bartel said...

The genshi template is rendered on template load time, which makes all mostly static during run time (unless you waste resources and reload it continuously). This means all dynamic page parts have to be rendered with nevow.
Is this observation correct? If yes, doesn't it make using genshi with rather useless for highly dynamic pages?

Duncan McGreggor said...

Karl, a belated thanks for your comment. Yes, you are most correct -- the examples I posted only work at load time. Hopefully I will have time to dive back into this at some point and provide actually useful code...