Skip to content

Neanderthal-Sprint: Plug in your favourite templating system

October 6, 2007

When it comes to templating languages there are a big choice out there, even if you limit yourself to the ones written in Python. They range widely in philosophy and even syntax, although the attribute-language type of syntax that Zope Page Templates pioneered seems to have taken over a lot nowadays.

The major debate today is how powerful the languages should be. It’s generally seen as a bad idea to put logic and programming in templates. The logical conclusion of that is something like Guido Wesdorps Templess, where you pretty much push in a dictionary of strings or lists of strings to the template, and it renders it with string replacements. This makes for a small (and possibly fast) templating language with simple and neat templates. It also can be a lot of work, since any information that should be displayed must be converted to a string and put into the dictionary. With Zope 3s view methodology, the idea is rather to have attributes and methods on the view provide the information, and with templess you would have to both create the method, and explicitly call it when you create the dictionary of variables. That seems to be two indirections aiming at the same thing.

The other philosophy is to put as much power as possible into the hands of the programmer and blame himself if he shoots himself in the foot. The extreme here is Genshi, which even lets you escape out into pure python code, PHP style. Although you don’t have to use that power, the idea of putting all that power into the hands of inexperienced programmers scares many people, including me. I would not like to work for a company that standardizes on Genshi templates, and then have to maintain other programmers Genshi-templates. ZPT meanwhile, has adopted a sort of middle ground, where the combination of trying to be easy to use for programmers and at the same time restricting what you can do, created a template language where you in fact can do almost anything, if you just apply some ugly trickery. That wasn’t good either.

So what to do? Well, that’s easy. The answer has been the same, even since Novell answered the war between ethernet and token ring with “Use whatever you want” and released a network OS that ran on both, the famous NetWare. Raise yourself over the fight, and let the different templating languages battle it out by themselves.

And so, me and Guido Wesdorp has used the main part of the Neanderthal sprint to let you easily plug in the template language of your choice into Grok. The code is in a branch, waiting for review, and will hopefully be merged sometime within a week or two.

Update 2007-11-81: It has now been merged and is a part of Grok 0.11 and later

You can find the branch at http://svn.zope.org/grok/branches/regebro-guido-templates/

Here is the first attempt on documentation of how to do this:

Update 2007-11-81: This documentation is now totally outdated. You can find the updated docs at http://grok.zope.org/minitutorials/template-languages.html

Plugging in new template languages

Introduction

Grok uses Zope Page Templates as default templating language, since this is
the default of Zope 3. Of course you can use whatever templating language you
want in Grok, but to get the automatic association between template
objects/files and views you need to do a little bit of extra work.

Inline templates

"Inline" templates are templates where the template code is written inside the
python class. To get the automatic association to a view class, you need to
write a class that subclasses from grok.components.GrokPageTemplate, so it
will be picked up by the template grokkers. It also needs to implement
grok.interfaces.ITemplateFile. Here is an example:

class MyPageTemplate(grok.components.GrokPageTemplate):

    def __init__(self, html):
        self._template = MyTemplate(html)
        self.__grok_module__ = martian.util.caller_module()

    def _factory_init(self, factory):
        pass

    def default_namespace(self):
        return {}

    def render_template(self, view):
        namespace = self.getDefaultVariables()
        namespace.update(view.getTemplateVariables())
        return self._template.render(**namespace)

In the __init__ method of a typical template you pass in what should be
rendered. This is usually HTML, but can equally well be XML or text, or
whatever your template renders. Note the line setting self.__grok_module__.
This is necessary to support inline templates.

The _factory_init method is a call made when setting up views (when the server
starts). Basically, you can here initialize the view in ways needed to your
template language. For example, Zope Page Templates set the macro attribute on
the view class, so that you can access ZPT macros with the standard syntax of
"context/@@viewname/macros/themacro". Most likely your class doesn’t need to
do anything here.

The default_namespace method should return a dictionary with variables that
should be included in the namespace that is specific for your template
language. By default ‘context’, ‘view’, ‘request’ and ‘static’ is made
available by the view. Your language might want to use some more.

Lastly, the render_template is the method normally used to render a template
attached to a view. Here you do whatever necessary to render your template.
This is usually to call view.default_namespace() and then update the namespace
with the result of the view-specific "extra_namespace", to let the view add
more variables to the namespace, and also override the defaults. The above
example is a reasonable startingpoint for most cases.

With this class finished you can create an inline template, like this:

class AView(grok.View):
    pass

aview = MyPageTemplate('<html><body>Some text</body></html>')

File templates

Mostly you want your templates to reside on disk. To do this you need a
file template class. It looks and works as the template class above, except
that it loads the template from a file instead:

class MyPageTemplateFile(grok.components.GrokPageTemplate):

    def __init__(self, filename, _prefix=None):
        file = open(os.path.join(_prefix, filename)
        self._template = MyTemplate(file.read())
        self.__grok_module__ = martian.util.caller_module()

    def render_template(self, view):
        namespace = self.getDefaultVariables()
        namespace.update(view.getTemplateVariables())
        return self._template.render(**namespace)

Here _factory_init and default_namespace is left out, as GrokPaeTemplate
alredy has them as defaults. The __init__ now takes two parameters, filename
and _prefix, which is the directory in which the file resides. Although there
is no requirement that these are the parameters used it is a good idea, since
that makes the next step easier.

Now you can use this filebase template:

class AView(grok.View):
    pass

aview = MyTemplateFile('lasceuax.html', '.')

Templates in the _templates directory

The most common usecase is however to place the templates in the views
template directory. To do that, a global utility that generates
MyPageTemplates from the filenames found is needed. That utility needs to
implement the ITemplateFileFactory interface. The easiest way of doing this is
to let your template file class implement it directly, by having the above
filename and _prefix parameters in the __init__ call and tell the component
architecture that the class provides the interface with a classProvides call.
You also need to tell Grok that the class should be a direct global utility by
subclassing from grok.GlobalUtility, and annotating it with a grok.direct()
annotation. Lastly you need to choose an extension for your template files,
and set the grok.name to that extension:

class MyPageTemplateFile(grok.components.GrokPageTemplate):

    zope.interface.implements(grok.interfaces.ITemplateFile)
    zope.interface.classProvides(grok.interfaces.ITemplateFileFactory)
    grok.name('mtl')
    grok.direct()

    def __init__(self, filename, _prefix=None):
        file = open(os.path.join(_prefix, filename)
        self._template = MyTemplate(file.read())
        self.__grok_module__ = martian.util.caller_module()

    def render_template(self, view):
        namespace = self.getDefaultVariables()
        namespace.update(view.getTemplateVariables())
        return self._template.render(**namespace)

When your module gets grokked, Grok will now pick up on the MyPageTemplateFile
class, register it as a global utility for templates with the ‘.mtl’ extension
and you can start creating .mtl files in the template directory for your class.

Have fun!

From → grok, python, zope, zope3

Leave a Comment

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s