Templates and the Templating Language
Plone Theme Reference
1. Templates and the Templating Language
The main elements of a skin are page templates, images, Python scripts, CSS files, and JavaScript files.
(Zope) Page Templates
Page templates (.pt files or ZPT) are an essential part of a Plone theme and are probably the easiest aspect of Plone to get to grips with. They are written in an elegant XML-based templating language called TAL, sometimes make use of macros (METAL), and sometimes incorporate Python expressions (small one-line calculations) or Python scripts.
There are several excellent introductions to ZPT, and it doesn't take long to learn TAL. Try these:
TAL is the one language that we really recommend you learn properly. The rest you can pick your way through or familiarise yourself with as you go along.
A Plone web page is delivered via an aggregation of templates, rather than just one, and there a couple of aspects of Zope Page Template that you'll need to be aware of.
1. Slot
A slot is a predefined section of a template. This can be left empty, or given some default content, but it is available to be filled on the fly. A slot is defined in a template in code like this:
<metal:bodytext metal:define-slot="main" tal:content="nothing">
.....
</metal:bodytext>
And filled via another template like this:
<metal:main fill-slot="main">
<h1 class="documentFirstHeading">
......
</h1>
</metal:main>
The ZPT tutorial on plone.org talks you through this in more detail, and the Templates and Components to Page section of this manual gives you an example.
2. Content view templates (_view)
Note: the term "view" also has a more technical application, so in the context of Components (discussed later in this manual) it will mean something different.
From the user's, contributor's, or visitor's perspective, a view is the way in which a content item is presented on the page. There's a useful introduction to this in the Plone User Manual.
Templates that are used to render a content item for a view have _view appended to their name (e.g., document_view.pt) and may have a title such as "Standard View." These templates are, in fact, sets of information ready to drop into slots.
Scripts
These are small stand-alone functions for times when you need a few lines of code to perform your calculation. On the file system, they have a .py extension; you'll find them in the Zope Management Interface as Script (Python).
Here's a snippet from the event_view template (the content view for the event content type) which uses a Python script to format the a time field according to the default format for the site. If you look in CMFPlone/skins/plone_scripts, you'll find toLocalizedTime.py - just a few lines of code.
<span metal:define-slot="inside" class="explain" tal:attributes="title python:here.end()" tal:content="python:here.toLocalizedTime(here.end(), long_format=1)">End Date Time</span>
2. Getting started
Page Templates are a web page generation tool. In this part, we'll go through their basics and show how to use them in your web site to create dynamic web pages easily.
The goal of Page Templates is natural workflow. A designer will use a WYSIWYG HTML editor to create a template, then a programmer will edit it to make it part of an application. If required, the designer can load the template back into his editor and make further changes to its structure and appearance. By taking reasonable steps to preserve the changes made by the programmer, he will not disrupt the application.
Page Templates aim at this goal by adopting three principles:
-
Play nicely with editing tools.
-
What you see is very similar to what you get.
-
Keep code out of templates, except for structural logic.
A Page Template is like a model of the pages that it will generate. In particular, it is a valid HTML/XHTML page. Since HTML is highly structured, and WYSIWYG editors carefully preserve this structure, there are strict limits on the ways in which the programmer can change a page and still respect the first principle.
Although Page Templates are suited for programmers and designers who need to work together to create dynamic web pages, they form the basis for most of Plone's pages, so you should learn them a bit at least, if you need to customize the Plone look or layout. Moreover, they can be simpler to use and understand than the alternative, DTML.
Why Yet Another Template Language?
There are plenty of template systems out there, some of them quite popular, such as ASP, JSP, and PHP. Since the beginning, Zope has come with a template language called DTML. Why invent another?
First, none of these template systems are aimed at HTML designers. Once a page has been converted into a template, it is invalid HTML, making it difficult to work with outside of the application. Each of them violates the first or second principle of Zope Page Templates to one degree or another. Programmers should not "hijack" the work of the designers and turn HTML into software. XMLC, part of the Enhydra project, shares our goal, but requires the programmer to write substantial amounts of Java support code for each template.
Second, all of these systems suffer from failure to separate presentation, logic, and content (data). Their violations of the third principle decrease the scalability of content management and website development efforts that use these systems.
Applying The Principles
Page Templates use the Template Attribute Language (TAL). TAL consists of special tag attributes. For example, a dynamic page title might look like this:
<title tal:content="context/title">Page Title</title>
The tal:content attribute is a TAL statement. Since it
has an XML namespace (the tal: part) most editing tools
will not complain that they don't understand it, and will not
remove it. It will not change the structure or appearance of
the template when loaded into a WYSIWYG editor or a web
browser. The name content indicates that it will set the
content of the title tag, and the value "context/title" is an
expression providing the text to insert into the tag.
To the HTML designer using a WYSIWYG tool, this is perfectly valid HTML, and shows up in the editor looking the way a title should look. The designer, not caring about the application details of TAL, only sees a mockup of the dynamic template, complete with dummy values like "Page Title" for the title of the document.
When this template is saved in Zope and viewed by a user, Zope turns this static content into dynamic content and replaces "Page Title" with whatever "context/title" resolves to. In this case, "context/title" resolves to the title of the object to which to the template is applied. This substitution is done dynamically, when the template is viewed.
This example also demonstrates the second principle. When you view the template in an editor, the title text will act as a placeholder for the dynamic title text. The template provides an example of how generated documents will look.
There are template commands for replacing entire tags, their contents, or just some of their attributes. You can repeat a tag several times or omit it entirely. You can join parts of several templates together, and specify simple error handling. All of these capabilities are used to generate document structures. You can't create subroutines or classes, write loops or multi-way tests, or easily express complex algorithms. For these tasks, you should use Python.
The template language is deliberately not as powerful and general-purpose as it could be. It is meant to be used inside of a framework (such as Zope) in which other objects handle business logic and tasks unrelated to page layout.
For instance, template language would be useful for rendering an invoice page, generating one row for each line item, and inserting the description, quantity, price, and so on into the text for each row. It would not be used to create the invoice record in a database or to interact with a credit card processing facility.
Creating a Page Template
If you design pages, you will probably use FTP or WebDAV instead of the Zope Management Interface (ZMI) to create and edit Page Templates, or you will be developing templates on the filesystem for later installation. If you're not the Zope site owner, ask your Zope administrator for instructions. For the very small examples in this article, it is much easier to use the ZMI. For more information on using FTP or WebDAV with Zope, see The Zope Book or Jeffrey Shell's WebDAV article.
You may also use Emacs, cadaver, or some other client, but if you are a Zope administrator or a programmer, you will probably use the ZMI anyway at least occasionally. See the Zope Book for instructions on setting up Zope to to work with various clients.
Use your web browser to log into the Zope management interface as
you normally would with Zope. Choose a Folder (the root is fine)
and pick "Page Template" from the drop-down add list. Type
"simple_page" in the add form's Id field, then push the "Add and
Edit" button.
You should now see the main editing page for the new Page
Template. The title is blank, the content-type is text/html,
and the default template text is in the editing area.
Now you will create a very simple dynamic page. Type the words "a
Simple Page" in the Title field. Then, edit the template's body text
to look like this:
This is <b tal:replace="template/title">the Title</b>.
Now push the "Save Changes" button. The edit page should show a
message confirming that your changes have been saved. If an error message appears above the code area, or some
text starting with <-- Page Template Diagnostics is added to
the template, then check to make sure you typed the example
correctly and save it again. You don't need to erase the error
comment: once the error is corrected it will go away.
Click on the Test tab. You should see a mostly blank page with
"This is a Simple Page." at the top.
Back up, then click on the "Browse HTML source" link under the content-type field. This will show you the unrendered source of the template. You should see "This is the Title." Back up again, so that you are ready to edit the example further.
Simple Expressions
The text "template/title" in your simple Page Template is a
path expression. This the most commonly used of the
expression types defined by the TAL Expression Syntax
(TALES). It fetches the title property of the template.
Here are some other common path expressions:
-
request/URL: The URL of the current web request.
-
user/getUserName: The authenticated user's login name.
-
container/objectIds: A list of Ids of the objects in the same Folder as the template.
Every path starts with a variable name. If the variable contains
the value you want, you stop there. Otherwise, you add a slash
(/) and the name of a sub-object or property. You may need to
work your way through several sub-objects to get to the value
you're looking for.
There is a small built in set of variables, such as request and
user, that will be listed and described later. You will also
learn how to define your own variables.
Inserting Text
In your "simple_page" template, you used the tal:replace
statement on a bold tag. When you tested it, it replaced the
entire tag with the title of the template. When you browsed the
source, instead, you saw the template text in bold. We used a bold tag in
order to highlight the difference.
In order to place dynamic text inside of other text, you typically
use tal:replace on a span tag. Add the following lines to
your example:
<br>
The URL is <span tal:replace="request/URL">URL</span>.
The span tag is structural, not visual, so this looks like "The
URL is URL." when you view the source in an editor or browser.
When you view the rendered version, it may look something like:
<br>
The URL is http://localhost:8080/simple_page.
Remember to take care when editing not to destroy the span or
place formatting tags such as b or font inside of it, since
they would also be replaced.
If you want to insert text into a tag but leave the tag itself
alone, you use tal:content. To set the title of your example
page to the template's title property, add the following lines
above the other text:
<head>
<title tal:content="template/title">The Title</title>
</head>
If you open the "Test" tab in a new window, the window's title will be "a Simple Page".
Repeating Structures
Now you will add some context to your page, in the form of a list of the objects that are in the same Folder. You will make a table that has a numbered row for each object, and columns for the id, meta-type, and title. Add these lines to the bottom of your example template:
<table border="1" width="100%">
<tr>
<th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th>
</tr>
<tr tal:repeat="item container/objectValues">
<td tal:content="repeat/item/number">#</td>
<td tal:content="item/id">Id</td>
<td tal:content="item/meta_type">Meta-Type</td>
<td tal:content="item/title">Title</td>
</tr>
</table>
The tal:repeat statement on the table row means
"repeat this row for each item in my container's list of
object values". The repeat statement puts the objects from
the list into the item variable one at a time, and
makes a copy of the row using that variable. The value
of "item/id" in each row is the Id of the object for that row.
You can use any name you like for the "item" variable, as long as
it starts with a letter and contains only letters, numbers, and
underscores (_). It only exists in the <tr> tag; If you
tried to use it above or below that tag you would get an error.
You also use the tal:repeat variable name to get
information about the current repetition. By placing it
after the builtin variable repeat in a path, you can access
the repetition count starting from zero (index), from one (number),
from "A" (Letter), and in several other ways. So, the
expression repeat/item/number is 1 in the first row, 2
in the second row, and so on.
Since one tal:repeat loop can be placed inside of another, more
than one can be active at the same time. This is why you
must write repeat/item/number instead of just
repeat/number. You must specify which loop you are interested in by
including the loop name.
Conditional Elements
View the template, and you'll notice that the table is very dull looking. Let's improve it by shading alternate rows. Copy the second row of the table, then edit the code so that it looks like this:
<table border="1" width="100%">
<tr>
<th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th>
</tr>
<tbody tal:repeat="item container/objectValues">
<tr bgcolor="#EEEEEE" tal:condition="repeat/item/even">
<td tal:content="repeat/item/number">#</td>
<td tal:content="item/id">Id</td>
<td tal:content="item/meta_type">Meta-Type</td>
<td tal:content="item/title">Title</td>
</tr>
<tr tal:condition="repeat/item/odd">
<td tal:content="repeat/item/number">#</td>
<td tal:content="item/id">Id</td>
<td tal:content="item/meta_type">Meta-Type</td>
<td tal:content="item/title">Title</td>
</tr>
</tbody>
</table>
The tal:repeat has not changed, you have just moved it onto
the new tbody tag. This is a standard HTML tag meant to
group together the body rows of a table, which is how you are
using it. There are two rows in the body, with identical
columns, and one has a grey background.
View the template's source, and you see both rows. If you
had not added the tal:condition statements to the rows,
then the template would generate both rows for every item,
which is not what you want. The tal:condition statement
on the first row ensures that it is only included on even-indexed repetitions, while the second row's condition only
lets it appear in odd-indexed repetitions.
A tal:condition statement does nothing if its expression
has a true value, but removes the entire statement tag,
including its contents, if the value is false. The odd and
even properties of repeat/item are either zero or one.
The number zero, a blank string, an empty list, and the
builtin variable nothing are all false values. Nearly
every other value is true, including non-zero numbers, and
strings with anything in them (even spaces!).
Defining Variables
Note: In Plone 4 or newer, use container/values instead of container/objectValues below.
Your template will always show at least one row, since
the template itself is one of the objects listed. In other
circumstances, you might want to account for the possibility
that the table will be empty. Suppose you want to simply
omit the entire table in this case. You can do this by
adding a tal:condition to the table:
<table border="1" width="100%"
tal:condition="container/objectValues">
Now, when there are no objects, no part of the table will be included in the output. When there are objects, though, the expression "container/objectValues" will be evaluated twice, which is mildly inefficient. Also, if you wanted to change the expression, you would have to change it in both places.
To avoid these problems, you can define a variable to hold
the list, and then use it in both the tal:condition and the
tal:repeat. Change the first few lines of the table to
look like this:
<table border="1" width="100%"
tal:define="items container/objectValues"
tal:condition="items">
<tr>
<th>#</th><th>Id</th><th>Meta-Type</th><th>Title</th>
</tr>
<tbody tal:repeat="item items">
The tal:define statement creates the variable items, and you
can use it anywhere in the table tag. Notice also how you can
have two TAL attributes on the same table tag. You can, in
fact, have as many as you want. In this case, they are evaluated
in order. The first assigns the variable items and the second
uses items in a condition to see whether or not it is false (in
this case, an empty sequence) or true.
Now, suppose that instead of simply leaving the table out when there are no items, you want to show a message. To do this, you place the following above the table:
<h4 tal:condition="not:container/objectValues">There
Are No Items</h4>
You can't use your items variable here, because it isn't defined
yet. If you move the definition to the h4 tag, you can't use it
in the table tag any more, because it becomes a local variable
of the h4 tag. You could place the definition on some tag that
enclosed both the h4 and the table, but there is a simpler
solution. By placing the keyword global in front of the
variable name, you can make the definition last from the h4 tag
to the bottom of the template:
<h4 tal:define="global items container/objectValues"
tal:condition="not:items">There Are No Items</h4>
<table border="1" width="100%"
tal:condition="items">
The not: in the first tal:condition is an expression type
prefix that can be placed in front of any expression. If the
expression is true, not: is false, and vice versa.
Changing Attributes
Most, if not all, of the objects listed by your template have an
icon property, that contains the path to the icon for that kind
of object. In order to show this icon in the meta-type column,
you will need to insert this path into the src attribute of an
img tag, by editing the meta-type column in both rows to look
like this:
<td>
<img src="/misc_/OFSP/Folder_icon.gif"
tal:attributes="src item/icon">
<span tal:replace="item/meta_type">Meta-Type</span>
</td>
The tal:attributes statement replaces the src attribute of the
image with the value of item/icon. The value of src in the
template acts as a placeholder, so that the image is not broken,
and is the correct size.
Since the tal:content attribute on the table cell would have
replaced the entire contents of the cell, including the image,
with the meta-type text, it had to be removed. Instead, you
insert the meta-type inline in the same fashion as the URL at the
top of the page.
Based on the Zope Book, © Zope Corporation
3. Macros and Slots
Macros
So far, you've seen how Page Templates can be used to add dynamic behavior to individual web pages. Another feature of page templates is the ability to reuse look and feel elements across many pages.
For example, with Page Templates, you can have a site that has a standard look and feel. No matter what the "content" of a page, it will have a standard header, side-bar, footer, and/or other page elements. This is a very common requirement for web sites, and this is exactly how Plone works.
You can reuse presentation elements across pages with macros. Macros define a section of a page that can be reused in other pages. A macro can be an entire page, or just a chunk of a page such as a header or footer. After you define one or more macros in one Page Template, you can use them in other Page Templates.
Using Macros
You can define macros with tag attributes similar to TAL statements. Macro tag attributes are called Macro Expansion Tag Attribute Language (METAL) statements. Here's an example macro definition:
<p metal:define-macro="copyright"> Copyright 2008, <em>Foo, Bar, and Associates</em> Inc. </p>
This metal:define-macro statement defines a macro named "copyright". The macro consists of the p element (including all contained elements, ending with the closing p tag).
Macros defined in a Page Template are stored in the template's macros attribute. You can use macros from other Page Templates by referring to them through the macros attribute of the Page Template in which they are defined. For example, suppose the copyright macro is in a Page Template called "master_page". Here's how to use the copyright macro from another Page Template:
<hr /> <b metal:use-macro="container/master_page/macros/copyright"> Macro goes here </b>
In this Page Template, the b element will be completely replaced by the macro when Zope renders the page:
<hr /> <p> Copyright 2008, <em>Foo, Bar, and Associates</em> Inc. </p>
If you change the macro (for example, if the copyright holder changes) then all Page Templates that use the macro will automatically reflect the change.
Notice how the macro is identified by a path expression using the metal:use-macro statement. The metal:use-macro statement replaces the statement element with the named macro.
Macro Details
The metal:define-macro and metal:use-macro statements are pretty simple. However there are a few subtleties to using them which are worth mentioning.
A macro's name must be unique within the Page Template in which it is defined. You can define more than one macro in a template, but they all need to have different names.
It should also be noted that, despite the define-macro attribute, the macro is anyway a regular section of the template; so, when you call the whole template, the macro section is rendered in the output page just like any other section in the template. By using the define-macro attribute you are simply adding some sort of "anchor" to that section, so that you can call it from outside; but you are not changing anything regarding the behaviour of that same section in the template itself.
Normally you'll refer to a macro in a metal:use-macro statement with a path expression. However, you can use any expression type you wish so long as it returns a macro. For example:
<p metal:use-macro="python:context.getMacro()"> Replaced with a dynamically determined macro, which is located by the getMacro script. </p>
In this case the path expression returns a macro defined dynamically by the getMacro script. Using Python expressions to locate macros lets you dynamically vary which macro your template uses.
You can use the default variable with the metal:use-macro statement:
<p metal:use-macro="default"> This content remains - no macro is used </p>
The result is the same as using default with tal:content and tal:replace. The "default" content in the tag doesn't change when it is rendered. This can be handy if you need to conditionally use a macro or fall back on the default content if it doesn't exist.
If you try to use the nothing variable with metal:use-macro you will get an error, since nothing is not a macro. If you want to use nothing to conditionally include a macro, you should instead enclose the metal:use-macro statement with a tal:condition statement.
Zope handles macros first when rendering your templates. Then Zope evaluates TAL expressions. For example, consider this macro:
<p metal:define-macro="title" tal:content="template/title"> template's title </p>
When you use this macro it will insert the title of the template in which the macro is used, not the title of the template in which the macro is defined. In other words, when you use a macro, it's like copying the text of a macro into your template and then rendering your template.
Using Slots
Macros are much more useful if you can override parts of them when you use them. You can do this by defining slots in the macro that you can fill in when you use the template. For example, consider a side bar macro:
<div metal:define-macro="sidebar"> Links <ul> <li><a href="/">Home</a></li> <li><a href="/products">Products</a></li> <li><a href="/support">Support</a></li> <li><a href="/contact">Contact Us</a></li> </ul> </div>
This macro is fine, but suppose you'd like to include some additional information in the sidebar on some pages. One way to accomplish this is with slots:
<div metal:define-macro="sidebar"> Links <ul> <li><a href="/">Home</a></li> <li><a href="/products">Products</a></li> <li><a href="/support">Support</a></li> <li><a href="/contact">Contact Us</a></li> </ul> <span metal:define-slot="additional_info"></span> </div>
When you use this macro you can choose to fill the slot like so:
<p metal:use-macro="container/master_page/macros/sidebar"> <b metal:fill-slot="additional_info"> Make sure to check out our <a href="/specials">specials</a>. </b> </p>
When you render this template the side bar will include the extra information that you provided in the slot:
<div> Links <ul> <li><a href="/">Home</a></li> <li><a href="/products">Products</a></li> <li><a href="/support">Support</a></li> <li><a href="/contact">Contact Us</a></li> </ul> <b> Make sure to check out our <a href="/specials">specials</a>. </b> </div>
Notice how the span element that defines the slot is replaced with the b element that fills the slot.
Customizing Default Presentation
A common use of slot is to provide default presentation which you can customize. In the slot example in the last section, the slot definition was just an empty span element. However, you can provide default presentation in a slot definition. For example, consider this revised sidebar macro:
<div metal:define-macro="sidebar"> <div metal:define-slot="links"> Links <ul> <li><a href="/">Home</a></li> <li><a href="/products">Products</a></li> <li><a href="/support">Support</a></li> <li><a href="/contact">Contact Us</a></li> </ul> </div> <span metal:define-slot="additional_info"></span> </div>
Now the sidebar is fully customizable. You can fill the links slot to redefine the sidebar links. However, if you choose not to fill the links slot then you'll get the default links, which appear inside the slot definition.
You can even take this technique further by defining slots inside of slots. This allows you to override default presentation with a fine degree of precision. Here's a sidebar macro that defines slots within slots:
<div metal:define-macro="sidebar"> <div metal:define-slot="links"> Links <ul> <li><a href="/">Home</a></li> <li><a href="/products">Products</a></li> <li><a href="/support">Support</a></li> <li><a href="/contact">Contact Us</a></li> <span metal:define-slot="additional_links"></span> </ul> </div> <span metal:define-slot="additional_info"></span> </div>
If you wish to customize the sidebar links you can either fill the links slot to completely override the links, or you can fill the additional_links slot to insert some extra links after the default links. You can nest slots as deeply as you wish.
Combining METAL and TAL
You can use both METAL and TAL statements on the same elements. For example:
<ul metal:define-macro="links" tal:repeat="link context/getLinks"> <li> <a href="link_url" tal:attributes="href link/url" tal:content="link/name">link name</a> </li> </ul>
In this case, getLinks is a (imaginary) Script that assembles a list of link objects, possibly using a Catalog query.
Since METAL statements are evaluated before TAL statements, there are no conflicts. This example is also interesting since it customizes a macro without using slots. The macro calls the getLinks Script to determine the links. You can thus customize your site's links by redefining the getLinks Script at different locations within your site.
It's not always easy to figure out the best way to customize look and feel in different parts of your site. In general you should use slots to override presentation elements, and you should use Scripts to provide content dynamically. In the case of the links example, it's arguable whether links are content or presentation. Scripts probably provide a more flexible solution, especially if your site includes link content objects.
Whole Page Macros
Rather than using macros for chunks of presentation shared between pages, you can use macros to define entire pages. Slots make this possible. Here's an example macro that defines an entire page:
<html metal:define-macro="page"> <head> <title tal:content="context/title">The title</title> </head> <body> <h1 metal:define-slot="headline" tal:content="context/title">title</h1> <p metal:define-slot="body"> This is the body. </p> <span metal:define-slot="footer"> <p>Copyright 2008 Fluffy Enterprises</p> </span> </body> </html>
This macro defines a page with three slots: headline, body, and footer. Notice how the headline slot includes a TAL statement to dynamically determine the headline content.
You can then use this macro in templates for different types of content, or different parts of your site. For example here's how a template for news items might use this macro:
<html metal:use-macro="container/master_page/macros/page"> <h1 metal:fill-slot="headline"> Press Release: <span tal:replace="context/getHeadline">Headline</span> </h1> <p metal:fill-slot="body" tal:content="context/getBody"> News item body goes here </p> </html>
This template redefines the headline slot to include the words "Press Release" and call the getHeadline method on the current object. It also redefines the body slot to call the getBody method on the current object.
The powerful thing about this approach is that you can now change the page macro and the press release template will be automatically updated. For example you could put the body of the page in a table and add a sidebar on the left and the press release template would automatically use these new presentation elements.
Based on the Zope Book, © Zope Corporation
4. Advanced Usage
In this part we'll look at some more advanced features of the Template Attribute Language, including a more in-depth look at the TAL Expression Syntax (TALES).
Mixing and Matching Statements
As you have seen in the example template, you can put more than one TAL statement on the same tag. There are three limits you should be aware of, however:
-
Only one of each kind of statement can be used on a single tag. Since HTML does not allow multiple attributes with the same name, you can't have two
tal:defineon the same tag. -
Both of
tal:contentandtal:replacecannot be used on the same tag, since their functions conflict. -
The order in which you write TAL attributes on a tag does not affect the order in which they execute. No matter how you arrange them, the TAL statements on a tag always execute in the following order:
define,condition,repeat,content/replace,attributes.
To get around these limits, you can add another tag and
split up the statements between the tags. If there is no
obvious tag type that would fit, use span or div.
For example, if you want to define a variable for each
repetition of a paragraph, you can't place the tal:define
on the same tag as the tal:repeat, since the definition
would happen before all of the repetitions. Instead, you
would write either of the following:
<div tal:repeat="ph phrases">
<p tal:define="n repeat/ph/number">
Phrase number <span tal:replace="n">1</span> is
<b tal:content="ph">Phrase</b>.</p>
</div>
<p tal:repeat="ph phrases">
<span tal:define="n repeat/ph/number">
Phrase number <span tal:replace="n">1</span> is
<b tal:content="ph">Phrase</b>".</span>
</p>
Note: the definition of "n" is actually not much useful in this example because we could have directly used "repeat/ph/number" in the replace attribute which only occurs once, but it serves our purpose.
Statements with Multiple Parts
If you need to set multiple attributes on a tag, you
can't do it by placing multiple tal:attributes statements
on the tag, and splitting them across tags is useless.
Both the tal:attributes and tal:define statements can
have multiple parts in a single statement. You separate the
parts with semicolons (;), so any semicolon appearing in an
expression in one of these statements must be escaped by
doubling it (;;). Here is an example of setting both the
src and alt attributes of an image:
<img src="default.jpg"
tal:attributes="src item/icon; alt item/id">
Here is a mixture of variable definitions:
<span tal:define="global logo context/logo.gif; ids context/objectIds">
Note: in Plone 4 or newer you can use context/items instead of context/objectIds.
String Expressions
String expressions allow you to easily mix path expressions
with text. All of the text after the leading string: is
taken and searched for path expressions. Each path
expression must be preceded by a dollar sign ($). If it
has more than one part, or needs to be separated from the
text that follows it, it must be surrounded by braces ({}).
Since the text is inside of an attribute value, you can only
include a double quote by using the entity syntax ". Since dollar signs are used to signal path
expressions, a literal dollar sign must be written as two
dollar signs ($$). For example:
<span tal:replace="string:Just text."/>
<span tal:replace="string:© $year, by Me."/>
<span tal:replace="string:Three ${vegetable}s, please."/>
<span tal:replace="string:Your name is ${user/getUserName}!"/>
<span tal:replace="string:She answered "$answer"."/>
<span tal:replace="string:This product costs $price $$."/>
Nocall Path Expressions
An ordinary path expression tries to render the object that it fetches. This means that if the object is a function, Script, Method, or some other kind of executable thing, then the expression will evaluate to the result of calling the object. This is usually what you want, but not always. For example, if you want to put a DTML Document into a variable so that you can refer to its properties, you can't use a normal path expression because it will render the Document into a string.
If you put the nocall: expression type prefix in front of a
path, it prevents the rendering and simply gives you the
object. For example:
<span tal:define="doc nocall:context/aDoc"
tal:content="string:${doc/id}: ${doc/title}">
Id: Title</span>
This expression type is also valuable when you want to define a variable to hold a function or class from a module, for use in a Python expression.
Other Builtin Variables
You have already seen some examples of the builtin variables
template, user, repeat, and request.
Here is a more complete list of the other builtin variables and
their uses:
- nothing
- A false value, similar to a blank string, that
you can use in
tal:replaceortal:contentto erase a tag or its contents. If you set an attribute tonothing, the attribute is removed from the tag (or not inserted), unlike a blank string. Equivalent toNonein Python. - default
- A special value that doesn't change anything
when used in
tal:replace,tal:content, ortal:attributes. It leaves the template text in place. - options
- The keyword arguments, if any, that were passed to the template.
- attrs
- A dictionary of attributes of the current tag in the template. The keys are the attributes names, and the values are the original values of the attributes in the template.
- root
- The root Zope object. Use this to get Zope objects from fixed locations, no matter where your template is placed or called.
- context
- The object on which the template is being called. This is often the same as the container, but can be different if you are using acquisition. Use this to get Zope objects that you expect to find in different places depending on how the template is called.
- here
- An (older) alias for context.
- container
- The container (usually a Folder) in which the template is kept. Use this to get Zope objects from locations relative to the template's permanent home.
- request
- Contains the complete information about the current HTTP request that Zope is processing. See this page in the zope.org wiki for further info about the request object.
- modules
- The collection of Python modules available to templates. See the section on writing Python expressions.
- view
- For templates called from a Zope 3-style view only, this variable refers to the associated view class. This may then contain functions and variables prepared explicitly for the template to output
Alternate Paths
The path template/title is guaranteed to exist every time
the template is used, although it may be a blank string.
Some paths, such as request/form/x, may not exist during
some renderings of the template. This normally causes an
error when the path is evaluated.
When a path doesn't exist, you often have a fallback path or
value that you would like to use instead. For instance, if
request/form/x doesn't exist, you might want to use context/x
instead. You can do this by listing the paths in order of
preference, separated by vertical bar characters (|):
<h4 tal:content="request/form/x | context/x">Header</h4>
Two variables that are very useful as the last path in a list
of alternates are nothing and default. Use nothing to
blank the target if none of the paths is found, or default
to leave the example text in place.
You can also test the existence of a path directly with the
exists: expression type prefix. A path expression with
exists: in front of it is true if the path exists, false
otherwise. These examples both display an error message only
if it is passed in the request:
<h4 tal:define="err request/form/errmsg | nothing"
tal:condition="err" tal:content="err">Error!</h4>
<h4 tal:condition="exists:request/form/errmsg"
tal:content="request/form/errmsg">Error!</h4>
Dummy Elements
You can include page elements that are visible in the
template but not in generated text by using the builtin
variable nothing, like this:
<tr tal:replace="nothing">
<td>10213</td><td>Example Item</td><td>$15.34</td>
</tr>
This can be useful for filling out parts of the page that will take up more of the generated page than of the template. For instance, a table that usually has ten rows will only have one row in the template. By adding nine dummy rows, the template's layout will look more like the final result.
Inserting Structure
Normally, the tal:replace and tal:content statements
quote the text that they insert, converting < to <,
for instance. If you actually want to insert the unquoted
text, you need to precede the expression with the structure
keyword. Given a variable copyright with a string value of "© 2008 By <b>Me</b>", the following two
lines:
<span tal:replace="copyright">Copyright 2008</span>
<span tal:replace="structure copyright">Copyright 2008</span>
...will generate "© 2001 By <b>Me</b>" and "© 2001 By Me", respectively.
This feature is especially useful when you are inserting a fragment of HTML that is stored in a property or generated by another Zope object. For instance, you may have news items that contain simple HTML markup such as bold and italic text when they are rendered, and you want to preserve this when inserting them into a "Top News" page. In this case, you might write:
<p tal:repeat="article topnewsitems"
tal:content="structure article">A News Article</p>
Basic Python Expressions
A Python expression starts with python:, followed by an
expression written in the Python language. Python is a simple and expressive programming language. If you
have never encountered it before, you should read one of the
excellent tutorials or introductions available at the official website
http://www.python.org.
A Page Template Python expression can contain anything that
the Python language considers an expression. You can't use
statements such as if and while, and Zope's security
restrictions are applied.
Comparisons
One place where Python expressions are practically
necessary is in tal:condition statements. You usually
want to compare two strings or numbers, and there isn't any
other way to do that. You can use the comparison operators
< (less than), > (greater than), == (equal to), and
!= (not equal to). You can also use the boolean
operators and, not, and or. For example:
<p tal:repeat="widget widgets">
<span tal:condition="python:widget.type == 'gear'">
Gear #<span tal:replace="repeat/widget/number">1</span>:
<span tal:replace="widget/name">Name</span>
</span>
</p>
Sometimes you want to choose different values inside a
single statement based on one or more conditions. You can
do this with the test function, like this:
You <span tal:define="name user/getUserName"
tal:replace="python:test(name=='Anonymous User', 'need to log in', default)">
are logged in as
<span tal:replace="name">Name</span>
</span>
<tr tal:define="oddrow repeat/item/odd"
tal:attributes="class python:test(oddrow, 'oddclass', 'evenclass')">
Using other Expression Types
You can use other expression types inside of a Python
expression. Each type has a corresponding function with
the same name, including path(), string(), exists(),
and nocall(). This allows you to write expressions such
as:
"python:path('context/%s/thing' % foldername)"
"python:path(string('context/$foldername/thing'))"
"python:path('request/form/x') or default"
The final example has a slightly different meaning than the path expression "request/form/x | default", since it will use the default text if "request/form/x" doesn't exists or if it is false.
Getting at Zope Objects
Much of the power of Zope involves tying together specialized objects. Your Page Templates can use Scripts, SQL Methods, Catalogs, and custom content objects. In order to use them, you have to know how to get access to them.
Object properties are usually attributes, so you can get a template's title with the expression "template.title". Most Zope objects support acquisition, which allows you to get attributes from "parent" objects. This means that the Python expression "context.Control_Panel" will acquire the Control Panel object from the root folder. Object methods are attributes, as in "context.objectIds" and "request.set". Objects contained in a folder can be accessed as attributes of the folder, but since they often have Ids that are not valid Python identifiers, you can't use the normal notation. For example, instead of writing "context.penguin.gif", you must write "getattr(context, 'penguin.gif')".
Some objects, such as request, modules, and Zope Folders
support item access. Some examples of this are:
request['URL'], modules['math'], and context['thing']
When you use item access on a Folder, it doesn't try to acquire the name, so it will only succeed if there is actually an object with that Id contained in the folder.
Path expressions allow you to ignore details of how you get from one object to the next. Zope tries attribute access, then item access. You can write "context/images/penguin.gif" instead of "python:getattr(context.images, 'penguin.gif')", and "request/form/x" instead of "python:request.form['x']".
The tradeoff is that path expressions don't allow you to specify those details. For instance, if you have a form variable named "get", you must write "python:request.form['get']", since "request/form/get" will evaluate to the "get" method of the form dictionary.
Using Scripts
Script objects are often used to encapsulate business logic and complex data manipulation. Any time that you find yourself writing lots of TAL statements with complicated expressions in them, you should consider whether you could do the work better in a script.
Each script has a list of parameters that it expects to be given when it is called. If this list is empty, then you can use the script by writing a path expression. Otherwise, you will need to use a Python expression, like this:
"python:context.myscript(1, 2)"
"python:context.myscript('arg', foo=request.form['x'])"
If you want to return more than a single bit of data from a script to a page template, it is a good idea to return it in
a dictionary. That way, you can define a variable to hold
all the data, and use path expressions to refer to each bit.
For example, supposing we have a getPerson script which returns a dictionary like {'name':'Fred', 'age':25}:
<span tal:define="person context/getPerson"
tal:replace="string:${person/name} is ${person/age}">
Name is 30</span> years old.
Calling DTML
DTML is another templating language made available by Zope, mostly replaced by ZPT nowadays, but still in use. You can read more about it in the relevant chapter of the Zope Book.
Unlike Scripts, DTML Methods don't have an explicit parameter list. Instead, they expect to be passed a client, a mapping, and keyword arguments. They use these to construct a namespace.
When Zope's ZPublisher publishes a DTML object, it passes the context of the object as the client, and the REQUEST as the mapping. When one DTML object calls another, it passes its own namespace as the mapping, and no client.
If you use a path expression to render a DTML object, it will
pass a namespace with request, context, and the template's
variables already on it. This means that the DTML object
will be able to use the same names as if it were being
published in the same context as the template, plus the
variable names defined in the template.
Python Modules
The Python language comes with a large number of modules, which provide a wide variety of capabilities to Python programs. Each module is a collection of Python functions, data, and classes related to a single purpose, such as mathematical calculations or regular expressions.
Several modules, including "math" and "string", are available
in Python Expressions by default. For example, you can get
the value of pi from the math module by writing "python:math.pi".
To access it from a path expression, however, you need to use
the modules variable. In this case, you would use "modules/math/pi".
Please refer to the Zope Book or a DTML reference guide for more
information about these modules.
The "string" module is hidden in Python expressions by the "string"
expression type function, so you need to access it through
the modules variable. You can do this directly in an expression
in which you use it, or define a global variable for it, like this:
tal:define="global mstring modules/string"
tal:replace="python:mstring.join(slist, ':')"
Modules can be grouped into packages, which are simply a way of organizing and naming related modules. For instance, Zope's Python-based Scripts are provided by a collection of modules in the "PythonScripts" subpackage of the Zope "Products" package. In particular, the "standard" module in this package provides a number of useful formatting functions that are standard in the DTML "Var" tag. The full name of this module is "Products.PythonScripts.standard", so you could get access to it using either of the following statements:
tal:define="pps modules/Products.PythonScripts.standard"
tal:define="pps python:modules['Products.PythonScripts.standard']"
Most Python modules cannot be accessed from Page Templates, DTML, or Scripts unless you add Zope security assertions to them. That's outside the scope of this document, and is covered by the Zope Security Guide.
Special HTML attributes
The HTML boolean attributes checked, selected, nowrap, compact, ismap, declare, noshade, disabled, readonly, multiple, selected and noresize are treated differently by tal:attributes. The value is treated as true or false (as defined by tal:condition). The attribute is set to attr=âattrâ in the true case and omitted otherwise. If the value is default, then it is treated as true if the attribute already exists, and false if it does not. For example, each of the following lines:
<input type="checkbox" checked tal:attributes="checked default">
<input type="checkbox" tal:attributes="checked string:yes">
<input type="checkbox" tal:attributes="checked python:42">
will render as:
<input type="checkbox" checked="checked">
while each of these:
<input type="checkbox" tal:attributes="checked default">
<input type="checkbox" tal:attributes="checked string:">
<input type="checkbox" tal:attributes="checked nothing">
will render as:
<input type="checkbox">
This article contains information and examples from the Zope Book, © Zope Developers Community.
5. Global Template Variables
Plone defines a few useful global variables to use them in your templates
While writing templates for Plone, you will notice a set of variables you use more often, like the URL of the portal or the currently authenticated member.
For your convenience, Plone defines a few global template variables that are pulled into main_template via global_defines. Some of the most useful ones are:
- portal
- The portal object.
- portal_url
- The url of the portal.
- member
- The current user (
Noneif user is anonymous) - checkPermission
- A function to check if the current user has a certain permission in the current context, e.g.
checkPermission('View portal content', context). - isAnon
- True if the current user is not logged in.
- is_editable
- True if the current user has edit permissions in the context.
- default_language
- The default language of the portal.
- here_url
- The URL of the current object.
To see the full list list of these variables, see the docstring for globalize() in the interface Products.CMFPlone.browser.interfaces.IPlone.
6. Customizing AT Templates
This tutorial describes the steps to produce a fully-customized view of an Archetypes object. This is applicable to tweaking little details of the default AT behavior as well as gutting and re-building the view from scratch. (contributed by Floyd May)
6.1. Introduction
Goals, Pre-Requisites, and Tools
So you think Archetypes' way of automagically generating HTML to view your object isn't pretty enough? Well, you've come to the right place! I'm going to teach you how to dress up those boring, drab views and make them shine!
Goals: What will I learn?
- How Archetypes generates views for content objects
- How much control Archetypes gives you
- How to change the HTML output for a particular field by creating a custom widget template
- How to use the Archetypes template framework to make minor changes to the default AT-generated view
- How to customize the HTML output for the entire view of an Archetypes object by using the
title,body,folderlisting, andfootermacros
Pre-Requisites: What do I need to know?
- How to read and write Python code
- How to read and write Zope Page Templates (ZPT)
- How to create Archetypes-based products (ArchGenXML is acceptable)
Tools: What do I need to have installed?
- Plone 2.0 or 2.1
- Archetypes (included by default in Plone 2.1)
- The ATViewTutorial product - this product has examples of the concepts in this tutorial
6.2. What Makes It Tick?
This page describes how Archetypes uses different templates to generate HTML, and how AT-based template customization can be applied.
Archetypes has a very clever system for generating HTML pages for AT-based objects. The same set of templates generates all of the content areas for all AT-based objects. This buys you the following benefit, useful for site consistency:
- All the pages look the same.
However, it also has the following drawback:
- All the pages look the same.
Different types of content need to be displayed differently. Let's dig into how Archetypes does things, so we can figure out how to make your content types shine!
The base_view Template
The base_view template (found in the archetypes skin) handles selecting the appropriate
macros from the appropriate templates, and using those macros to display content objects.
If you look at this piece of code from 'base_view':
<tal:block define="portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
view_template python:'%s_view' % portal_type;
view_macros python:path('here/%s/macros|nothing' % view_template);
macro view_macros/css | nothing"
condition="macro">
You can see that it defines the variable view_template as the object's Portal Type Name converted to lowercase, with spaces replaced with underscores (_), followed by _view. So, MyType's view template, for instance, would be called mytype_view.
Now, before we move on, I must warn you: don't edit base_view. Seriously. Don't.[1]
No, really. Don't customize base_view. If you think you need to customize base_view, first, well... don't. Keep reading the tutorial. If you're certain, after reading the tutorial, that you need to customize base_view, again, don't! Write a clear, concise example indicating why, after reading this tutorial, you believe you should customize base_view, and send it to the archetypes-users mailing list. If you really do need to customize base_view, you've found a shortcoming in Archetypes, and the people on the list will inform you if that's the case. So, repeat after me: "Don't customize base_view." Good!
Now, there are six important macros to be aware of. These six macros give you the power to insert template code that is customized for your class. These macros are:
js- A macro to insert javascript into the
<head>tag of the generated HTML page css- A macro to insert CSS includes and style code into the
<head>tag of the page header- The macro that defines the topmost portion of the content area. By default, this macro has an
<h1>tag that contains the title, and links for printing, emailing, etc. on the right. body- The macro that defines the "body" area of the content. This is where the fields and their values are displayed.
folderlisting- This macro shows a list of the child content for the object. Don't confuse this with
folder_contents, this is what theviewtab shows for folderish objects. Folderish objects use both thebodymacro and thefolderlistingmacro. footer- This is where AT puts the byline.

As you can see, the header macro generates what's outlined in the area marked ''header'' in red, the body macro generates the content just beneath it, and the folderlisting macro generates the listing of the objects within the folderish object.
The base_view template automatically pulls the appropriate macro from the custom view template (mytype_view, from our earlier ad-hoc example), or from the next template that we are going to explore: base.
The base Template
The base template contains four of the six macros that base_view looks for:
headerbodyfolderlistingfooter
The only reason why I mention base is so that you know where AT's default behavior comes from. This is important if you only want to change a little bit of a type's view. It's often helpful to copy the macro from base into your custom view template, and then start tweaking and customizing.
Widgets
Widgets are what Archetypes uses to display fields. Widgets have two parts:
- The widget class
- This class defines data and behavior for the widget. In most cases, you'll never need to create a custom 'Widget'-derived class. See Archetypes/Widget.py for examples.
- The widget template
- This is a ZPT that provides three macros:
view,edit, andsearch. These macros display the field. Some of the macros depend on certain variables being defined in the calling template, so pay close attention. Most often, you'll only need to provide a custom widget template, and not a custom widget class.
There are all kinds of widgets out there to do all kinds of things. The Archetypes Quick Reference Manual covers the details for the various widgets in Archetypes.
[1] Unless you're wiggy.
6.3. Customizing Widgets
This page shows you how to customize widgets, and gives some examples of what kinds of neat tricks can be done with widget customization.
6.4. Total Control: The View Template
This page describes how you can control every portion of the HTML output in the content area by creating a custom view template.
Customizing Labels
6.5. Conclusion
Some final notes about customizing Archetypes view templates
So, now you should know all of the following information:
- How to identify which parts of an Archetypes view template are generated by the
header,body,folderlisting, andfootermacros - How to create a custom view template that overrides one or more of the
header,body,folderlisting, andfootermacros - How to create a custom widget template that works in the Archetypes framework
- How to create a custom
bodytemplate that uses Archetypes' widget rendering templates - How to inject custom CSS code and links to custom CSS files into your view template
Some Final Notes
I want to cover a few details about how to apply all of these tools. Some wise guy somewhere said something like, "To a man whose only tool is a hammer, every problem tends to look like a nail." Your success with Archetypes is very contingent upon selecting the appropriate tool for your specific problem. So, use the following outline of the basic AT page layout as a guideline to determine what should be customized:
headermacro- Title (or id if no title is present)
- Document actions (e.g. print, send to)
bodymacro- List of fields
- Field label (from the
labelmacro in the view template, if one exists) - Field value (from the widget template's
viewmacro)
- Field label (from the
- List of fields
folderlistingmacro- List of links to each sub-object
footermacro- byline
So, based on which parts of this standard layout you need to customize, use the appropriate macro. Keep the infamous "Foo" template around to help you with debugging. See the next page for a reference on customizing Archetypes view templates.
6.6. Reference
A reference for customizing Archetypes view templates
View Templates
View templates are named according to the portal_type of the class. To create the name for a view template, follow these steps to create the name of the template:
- Change the
portal_typeto lowercase. - Replace all spaces with underscores (
_). - Append
_viewto the end of the name.
Archetypes will automatically locate templates with names created according to the above steps, and will make use of the macros defined within the template. View templates can define one or more of the following macros:
css- A macro for inserting type-specific CSS code, including
<link>tags pointing to custom CSS files. There is no default macro for this within Archetypes; Archetypes uses the existing CSS styles in Plone to render AT-based objects. header- This macro, by default, generates the
<h1>tag containing the object's title and the document actions (print, rss, e.g.) body- The location where the fields and values are displayed by default. When rendering fields using the existing widget mechanism, be sure to
tal:definethe variablefieldas the current field; the widget templates depend on this variable being set. folderlisting- This is the folder listing display when viewing the
viewtab of a folderish object. This is not the same as thecontentsview. footer- By default, this is where Archetypes puts the byline.
label- This template generates field labels.
For any of these macros that is not defined in the custom view template, Archetypes will use the default behavior in its place, taken from base or widgets/field.
Widget Templates
Use custom widget templates by naming them in the schema - insert a macro parameter into the widget's constructor in the schema, and set the value to the name of the template. For example, macro="my_widget_template". Widget templates must have the following three macros:
vieweditsearch
Widget templates have the following local variables available within TALES expressions:
accessor- The accessor method for the current field. The code
<p tal:content="accessor" />will cause the field's value to be written within the<p>tag. fieldName- The name of the field.
widget- The widget object for the field.
mode- Will always be
viewfor view templates, but is useful for, say, error checking.
7. How to customise view or edit on archetypes content items
Explains one way to customise the view or edit templates without having to change the action of an object.
8. Create an Alternative Edit Template
Suppose you've a content type and you want to keep the default "edit" template, but you want also to create another edit template suited for editing only some particular metadata.
Think of the standard edit as a "full" edit, while your new edit is a custom version. They co-exist together. This method also works if you've already a mycustomtype_edit edit template, and you want to have another one.
Limiting the Fields Available to Edit
Step 1: Copy the base_edit and the edit_macros (you can do it by simply customizing them) and change their id (rename) to mynewedit and mynewmacros
Step 2: Modify mwynewedit to point to mynewmacros:
- edit_template python:'newemacros';
- edit_macros python:path('here/newemacros/macros | nothing');
so this edit template will pick up the macros from your custom version.
Step 3: Modify mynewmacros as you need. For example, if you want to display only some fields, you can (in<metal:block define-slot="widgets"> which manages widgets display) change the template as below:
<tal:fields repeat="field
python:schematas[fieldset].editableFields(here, visible_only=True)">
<div tal:omit-tag=""
tal:condition="python:field.getName()
in ['title','myfield1','myfield2','myfield3','myfield4']">
<metal:fieldMacro use-macro="python:here.widget(field.getName(),
mode='edit')" />
</div>
</tal:fields>
So only 'title','myfield1','myfield2','myfield3','myfield4' will be displayed and you will avoid people editing unwanted fields (even if they can).
Step 4: If there's a failure, the user should go back to your form, so in mynewedit -> actions, change failure from string:edit to string:mynewedit
Dealing with Validation
Suppose you've some validated fields, or you want to run custom code when the user saves the form. The validation will kick in when you'll press "save". If you've some required fields, but they're not in the fields listed above, you'll get the red warning and your data will not be saved. So you have to bypass it, and we need to do it in two places:
Step 1:
- edit mwynewedit -> validation -> delete the validation step.
- edit mwynewedit -> actions -> change string:content_edit to string:mycontent_edit (a custom content edit)
Step 2:
- Find content_edit in portal_skins/archetypes and customize it. Then rename it to mycontent_edit.
Here you can add custom code, sending mail and so on.
- Edit it -> actions -> delete the validate_integrity step
- Edit it -> actions -> change any string:edit to string:mynewedit , so after saving you'll go to your edit form if there are any failures (should not if you remove the validation)
Now your form can edit your content type without any restrictions. If you need some restrictions, just don't delete the validation steps above and customize the validation scripts validate_base and validate_integrity (renaming them and pointing to them in the validation steps above).
This is enough to perform your "very" own custom edit, with custom saving and custom validation, leaving the default one untouched.





