Developer's Guide

  • Docs Home
  • Community Home

4. Zope 2 Page Templates, TAL and METAL and Zenoss

Templates live in layers which, due to Zope magic (aka acquisition), are available anywhere in the object tree. As is the case with most templating languages, Zope templates are context-agnostic, meaning that they may be used as views on any object. When the name of a template is called against a particular context, the skins tool (/zport/portal_skins in Zenoss) will supply the appropriate template object, determined by the priority of the layers -- given two templates with the same name, that in the higher priority layer will prevail. This allows Zope products to override the templates of other products to provide different functionality. It can also result in total confusion as to the source of a template as this process is in no way transparent.

Templates may be created in the ZODB, or they may live on the filesystem; the latter is preferable for all but the most ad hoc situations. Typically, a Zope product that provides templates will register a skins directory, which will include one or more layers. When the product is initialized, the layers it provides will be added to the skins tool under whatever skin is specified. Zenoss has a single skin, so only the order of the layers determines template inheritance.

The Zenoss UI comprises several layers, mostly for the purposes of organization. The ZenModel and ZenEvents products each have a folder (named zenmodel and zenevents, respectively), the ZenUtils product has one (inexplicably located at ZenUtils/js), and the ZenWidgets product has two (zentablemanager and zenui). zenmodel and zenevents generally contain templates applicable to classes provided by their respective products. The zenui folder contains most of the dialog templates, nearly all of the CSS, JavaScript (including the YUI library), image files and other templates that don't necessarily belong to a single product. The zentablemanager layer provides resources related to ZenTableManager. The ZenUtils/js layer provides the MochiKit library and a few JavaScript utilities. Both the zentablemanager folder and the ZenUtils/js layer are legacies and shouldn't be modified. All new templates should go in one of the other three, and all static browser resources should go in zenui.

Table 13.1. Zenoss portal_skins directories and their Descriptions

DirectoryNotes
zenmodelContains the majority of the templates.
zeneventsEvent-specific templates.
zentablemanagerDeprecated.
zenuiMost of the dialog templates, nearly all of the CSS, JavaScript (including the YUI library), image files and other templates that don't necessarily belong to a single product.
ZenUtils/jsDeprecated. This layer is actually not under portal_skins. The MochiKit library and a few JavaScript utilities

Zope page templates are a combination of METAL, TAL and TALES, each of which is summarized more succinctly than one familiar with them might expect here.

In short, METAL allows templates to define macros (which are essentially sub-templates that may be called by other templates) and slots (which may be filled by other templates). For example, one wishing to have a title on all pages might create the following base.pt:

<html metal:define-macro="base_template">
<head>
 <title>Zenoss: <tal:block metal:define-slot="subtitle">
 Default Subtitle</tal:block>
        </title>
</head>
<body>
 <tal:block metal:define-slot="content">Default Content</tal:block>
</body>
</html>

Then on a template that might be used to view an object, one could:

<tal:block metal:use-macro="here/base/macros/base_template">
<tal:block metal:fill-slot="subtitle">My Subtitle</tal:block>
  <tal:block metal:fill-slot="content">My Content</tal:block>
</tal:block>

This allows for relatively complex abstraction.

Zenoss has a base template providing several basic page types that include global CSS and JavaScript resources, the basic page structure, and optionally the tab pane. This template is located at ZenModel/skins/zenmodel/templates.pt. When creating a new template, find another like it and copy the templates.pt macro reference used there.

TAL comprises a set of attributes for page elements allowing for iteration loops, dynamic attribute mutation, and other dynamic content. The above resource will summarize these more fully.

TALES allows access to the template's namespace. Some useful properties available on all templates:

Commonly-used Zope Properties in ZPT

here

the context object

container

the folder containing the context object

template

the template object

root

the portal object (zport)

user

the current authenticated user object

request

the current HttpRequest object

portal_url

the base URL of the portal (eg http://localhost:8080/zport)

TALES accepts paths (e.g. here/id) which it resolves into object properties. It will attempt to resolve the final path element as a key index, a key name, an attribute, or a callable. For example, if mydict is a dictionary on the context, here/mydict/mykey will return mydict[mykey]. If getSomething() is a method on the context, here/getSomething will return the result of that method. However, if python:here.getSomething() returns a dictionary, one cannot do here/getSomething/mykey.

The path resolution is fairly limited -- for example, one cannot pass arguments to methods. In case something more complex is needed, one can use python: followed by arbitrary Python code. For example, python:here.mydict[mykey] will return the same thing as here/mydict/mykey, while python:here.getSomething(template.id) is not possible using a path. The previous paragraph's impossible here/getSomething/mykey can be resolved this way: python:here.getSomething()[mykey].

Finally, if one wishes to generate a string, one may prepend the argument with string:. Everything after that will be treated as a string, unless contained within ${}, in which case it will be evaluated as a TALES path. For example:

  <span tal:content='string:The name of this
    template is ${template/id}'/>

4.1. Tips

  • ZPT ignores everything inside a script element, although it does not ignore TAL defined on the element itself. This can make dynamic JavaScript problematic. One way around this, however, is like this:

           <script tal:content="string:
            var templateId = '${template/id}';
           "></script>

    This is obviously unwieldy, especially in the case of several levels of nested quotes, but it at least allows JavaScript access to the template's namespace.

  • Slots on macros are not inherited unless specifically defined. For example, if one has a template base.pt:

            <tal:block metal:define-macro="base">
                My Base Template
                <span metal:define-slot="content">Default Content</span>
            </tal:block>

    from which one wishes to create a more specific base template, plaintext.pt:

            <tal:block metal:define-macro="plaintext">
                <style>body{font-family:Courier,monospace}</style>
                <tal:block metal:use-macro="here/base/macros/base"/>
            </tal:block>

    templates calling here/plaintext/macros/plaintext will not be able to fill here/base/macros/base's 'content' slot. One must chain the slots, defining a plaintext content slot inside the fill of base's content slot:

            <tal:block metal:define-macro="plaintext">
                <style>body{font-family:Courier,monospace}</style>
                <tal:block metal:use-macro="here/base/macros/base">
    
                    <tal:block metal:fill-slot="content">
                        <tal:block metal:define-slot="content">
                        </tal:block>
                    </tal:block>
    
                </tal:block>
            </tal:block>
  • Thanks to Zope's magical acquisition, templates can be treated as methods on objects. If an object may be viewed at /zport/dmd/object/mytemplate, then calling object.mytemplate() in a Python file will return the HTML that template generates. In this case, however, there's no request object, so templates that ask for one will throw an error. This is both a blessing and a curse; many man-hours have been wasted searching for methods that do not exist.

  • Generally, unless a specific tag is required, use <tal:block> for purely logical structures, as it will produce no side effects (whereas using <div> could easily do so).