Templates and the Templating Language

« Return to page index

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:

  1. Play nicely with editing tools.

  2. What you see is very similar to what you get.

  3. 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:

  1. 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:define on the same tag.

  2. Both of tal:content and tal:replace cannot be used on the same tag, since their functions conflict.

  3. 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 &quot;. 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 &quot;$answer&quot;."/>
      <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:replace or tal:content to erase a tag or its contents. If you set an attribute to nothing, the attribute is removed from the tag (or not inserted), unlike a blank string. Equivalent to None in Python.
default
A special value that doesn't change anything when used in tal:replace, tal:content, or tal: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 &lt;, 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 (None if 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, and footer macros

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 the view tab shows for folderish objects. Folderish objects use both the body macro and the folderlisting macro.
footer
This is where AT puts the byline.

This image shows the areas generated by the header, body, and folderlisting macros

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:

  • header
  • body
  • folderlisting
  • footer

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, and search. 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.

As we've already said, widgets are what Archetypes uses to display individual fields. Archetypes' built-in templates, base_view,base, and widgets/field use each field and the field's associated widget (specified in the schema) to determine which widget template to use. However, you can override a widget's template, as we will show below. Furthermore, you can create a whole new widget class, which will have data and operations specific to the display of your custom data type. Read the next section to determine how much widget hacking you need to do.

How to Determine If You Need to Create A Custom Widget Class

If you cannot find a widget in Archetypes or in a readily-available third-party product that does what you need, use the following set of questions to determine if you can just customize the template versus creating a new widget class. If you answer "no" to the following questions, a custom template is all you need:

  • Does the display of your field require helper functions to do conversions or formatting that would be difficult or cumbersome in TALES?
  • Do you have multiple AT-based classes where some fields in those classes share all of the the following characteristics?
    • The same data type
    • Similar, needs for display
    • One or more attributes that are class-specific that apply to the display (i.e. the most appropriate place to set these attributes is in the schema definition)
  • Does your custom data type need some super-specific marshalling when edited or searched that you can't get from any standard AT widget class?
  • Do you need to override or change the way that Archetypes handles the processing of the edit form for a particular field?

If you answered yes to most of the questions, then you might need to create a Widget class. If the questions aren't clear, take a look at the RichDocument tutorial . If you've got a specific enough use-case (like RichDocument) that you need custom widget classes, you're probably able to make it happen just by the sheer fact that you know you need them. [1]

Customizing Widget Templates

Creating custom widget templates is not hard, so don't be afraid. I assume if you're reading this far, you've already determined that you don't need a custom widget class, and just need to tinker a little bit with how things are done by default.

First, you should understand what you have control over by customizing a widget template itself. You're controlling the display of the widget's data, but not its label. For a StringField called myfield, the default display is something like:

myfield: some value

The only thing we can control is the display of what comes after "myfield:", which is just the data contained within the field itself (we'll talk about how to customize the display of labels later). However, if we customize the template, we can insert all kinds of nifty HTML into there! So, let's look at StringWidget's template, 'widget/string':

It's pretty straight-forward. As you can see, there are three important macros in a widget template:

  • view
  • edit
  • search

Don't concern yourself with the edit and search macros; remember, we're customizing the view. Let's start by creating a new template called my_string_widget [2]. Start like this:

Notice how we use the same pass-through macro call in the edit macro that the string template originally uses in the search macro. It's important to remember the following concept: Widget templates must define all three macros: view, edit, and search. Also, notice how there is no display code for the label; that's handled elsewhere. If you're wondering where the accessor variable comes from, that's part of the widget display code. The widget class defines the following local variables that are accessible inside widget templates:

accessor
The accessor method for the field. Call it to retrieve the value of the field.
fieldName
The name of the field.
widget
The widget object for the field.
field
The instance of the field class itself.
mode
Will be view or edit, based on the action being taken. For our purposes, it should always be view.

Now, let's modify the way that our StringField displays. For brevity, I'll just show the view macro:

Then, we should tell our type's schema to point at the new template:

    StringField('myfield',
        widget=StringWidget(
            label='Myfield',
            label_msgid='ATViewTutorial_label_myfield',
            description_msgid='ATViewTutorial_help_myfield',
            i18n_domain='ATViewTutorial',
            macro='my_string_widget',
        )
    ),

Be sure to restart Zope and reinstall using portal_quickinstaller. Now, our StringField, when rendered, looks like this:

Custom Widget

Yes, folks, it's just that easy.

[1] Again, this probably only applies to wiggy.

[2] Notice I'm breaking AT's convention here. You don't have to do that, but I find it more convenient and understandable to add a _widget to the names of my widget templates.

 

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.

Okay, so you've hacked up some custom widget templates, and they're basking in the glory of your newfound power, yet you're still not satisfied. You want to control it all! Well, I've got the information for you!

Archetypes and Type-Specific View Templates

Archetypes automatically recognizes templates with specific names, and uses the code within those templates to display your AT-based object. All of this magic happens within the base_view template. To create a custom view template, convert your type name to lowercase (the name that's listed in portal_types, or what's returned from myObject.portal_type). Now, replace spaces with underscores ( _ ). Finally, add _view to the end of the name, and you've almost got a custom view template.

See below for examples of type names and their corresponding view templates.

Type Name

View Template Name

My Type

my_type_view

SomeTypeV2

sometypev2_view

Now, to remedy that "almost" part of the above paragraph, define one or more of the following macros in your template:

  • header
  • body
  • folderlisting
  • footer

Voila! You've got a custom view template. To see how this works, create a simple template (named appropriately, of course) that contains the following code:

          Foo

          Foo

          Foo

          Foo

And, just like magic, you should see, rendered in your content area:

The Infamous "Foo" View

But Wait! Where Are All My Fields?

So now you want your data back. You said you wanted total control, and now you don't want total control. But the point of this tutorial isn't control, it's shine. So, let's explore how to mix and match existing AT templates with your custom code to make a shiny template that renders exactly how you want it.

First, keep the above "Foo" template around. It's very useful when you aren't quite sure which of the four macros is generating a portion of the content area. Simply comment out one or more of the macros, and you'll see which macro generates what portion.

Now, do you remember when I talked about using the base template as a starting point for creating custom templates? Well, that's what we'll do. Let's start by customizing the footer. The footer macro in the following template is copied directly from 'base':

Get the byline - contains details about author and modification date.

Now, let's add something below the byline, say, some important information that applies to every instance of your custom type::

Get the byline - contains details about author and modification date.

Important information that applies to every instance of my custom type.

Notice that all we had to do was copy the macro from base , and add the <p> tag with some text in it. Notice that, for example, we could have used tal:content="here/getCustomFooterData" in the </p> <p> tag if we had defined a getCustomFooterData() method in our class.

Now, let's apply this concept to the body macro, and play around with displaying fields. First, we'll start by coping the body from base into our template.

Now, we'll change up some things by adding a little bit of code into the macro. First, notice that the tal:repeat is repeating over all the fields that are not metadata. Therefore, if you want to do something for every field, put it inside this macro. You could (conceivably) rearrange the macro so that the tal:repeat loop is inside another containing block, and put TAL code before and after the display of the fields, or make use of the first and last repeat variables to achieve the same thing. So, let's do two things to customize our body macro:

  • Surround all the fields with a </p> <div> that has a custom CSS class, my-custom-at-body
  • Surround each field with a <div> that has a different custom CSS class, my-custom-at-field
 

These changes, as I'm sure you've figured out, aren't going to make much of a difference (if any) in the look of the rendered page without actually writing some custom CSS code. We now introduce the css macro:

<link href="#" type="text/css" rel="stylesheet" />
<div class="my-custom-at-body">
<div class="my-custom-at-field">&nbsp;</div>
</div>

Now, we can define a CSS stylesheet called my_custom_css.css that contains our custom CSS code:

    .my-custom-at-body {
        border: thin dashed;
        background-color: #cccccc;
        padding-top: 1em;
    }

    .my-custom-at-field {
        background-color: #ffffff;
    }

Archetypes inserts the css macro into the '' tag of the rendered page, making our custom CSS code, linked files, and includes available within the page. Our end-result would look something like this:

Custom Body Macro

If we had created custom widget templates, those would be applied to the rendered page as well.

Customizing Labels

There's still one element of control that we're missing: we still can't override the display of a field label. By customizing the display of the label, we can insert images, links, etc. into the page instead of the default label.

The macro included in our custom view template below will do that magic for us:

<link href="#" type="text/css" rel="stylesheet" />
<div class="my-custom-at-body">
<div class="my-custom-at-field">&nbsp;</div>
</div>
<label>Now presenting... Field1!</label>

Notice that I've only overridden the default label for fields labeled "myfield". The label macro in widgets/field is where the default behavior can be found. The final result looks like this:

Customized Label

Also, don't forget that you have the power to omit head,body,folderlisting, and footer by simply writing in do-nothing macros into your view template. Furthermore, you can reach into your object and retrieve field values without using the widget framework.

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, and footer macros
  • How to create a custom view template that overrides one or more of the header,body,folderlisting, and footer macros
  • How to create a custom widget template that works in the Archetypes framework
  • How to create a custom body template 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:

  • header macro
    • Title (or id if no title is present)
    • Document actions (e.g. print, send to)
  • body macro
    • List of fields
      • Field label (from the label macro in the view template, if one exists)
      • Field value (from the widget template's view macro)
  • folderlisting macro
    • List of links to each sub-object
  • footer macro
    • 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:

  1. Change the portal_type to lowercase.
  2. Replace all spaces with underscores (_).
  3. Append _view to 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:define the variable field as the current field; the widget templates depend on this variable being set.
folderlisting
This is the folder listing display when viewing the view tab of a folderish object. This is not the same as the contents view.
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:

  • view
  • edit
  • search

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 view for 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.

Reasons/Use Cases

I usually like to customise as little as possible so that more of my page templates are just like the plone default templates. I find this helps when I move to a new version and also makes doing styling using CSS easier.

Another use case is if I want to generate a form using the schema but I need it to do different things based on which button is pushed, you can accomplish this with putting named buttons on the form in combination by using portal_formcontroller to override what happens on a submit. E.g. importing data from CSV, in a seperate schemata I have a form.button.Import button and on this schemata I only show this button and the cancel button (instead of save, nex previous etc.) and then I customise the portal_formcontroller action (and validation) so content_edit (the script that does the saving) goes to a script that does the importing before going back to the view action.

Archetypes base_view and base_edit

Both of these templates have several macros which are gotten by from other page templates. They are setup in such a way that they will look for a template named with the content type for these macros and then default to the generic archetypes macros. I.e. say you have a content type 'Newsletter' base_view looks for a template named 'newsletter_view' if it finds it and it contains the right macros it will use those instead of the default 'view_macros' (found in 'portal_skins/archetypes' skin folder.

Below is a skeleton example of a custom view template showing the different things you can customise. See base.pt

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"

      lang="en"

      metal:use-macro="here/main_template/macros/master"

      i18n:domain="plone">

<body>



<metal:main fill-slot="main">

        <!-- header, H1 with title in it -->

        <metal:header define-macro="header">

        

        </metal:header>

        

        <!-- body macro where all the fields are -->

        <metal:body define-macro="body">

                

        </metal:body>

        

        <!-- folderlisting that shows sub-objects if there are any -->

        <metal:folderlisting define-macro="folderlisting">

                

                

        </metal:folderlisting>

        

        <!-- footer, by line created date etc. -->

        <metal:footer define-macro="footer">

                

        </metal:footer>

        

</metal:main>

</body>

</html>

Below is an skeleton of a custom edit template:

<html xmlns="http://www.w3.org/1999/xhtml"

      xml:lang="en"

      lang="en"

      xmlns:tal="http://xml.zope.org/namespaces/tal"

      xmlns:metal="http://xml.zope.org/namespaces/metal"

      xmlns:i18n="http://xml.zope.org/namespaces/i18n"

      i18n:domain="plone">



  <metal:head define-macro="topslot">

  </metal:head>

  

  <metal:head define-macro="javascript_head">

  </metal:head>



  <body>

        <!-- header, h1 of Edit <Type>, schemata links and webdav lock message -->

        <metal:header define-macro="header">



        </metal:header>

        

        <!-- typedesription, typeDescription from the content type -->

        <metal:typedescription define-macro="typedescription">



        </metal:typedescription>



        <!-- body, editform , fields, buttons, the default macro 

             contains a number of slots which usually provide enough

             ways to customise so often I use that macro and just 

             fill the slots

        -->

        <metal:body define-macro="body">

            <metal:default_body use-macro="here/edit_macros/macros/body">

              <!-- inside the fieldset but above all the fields -->

              <metal:block fill-slot="extra_top">

              </metal:block>

              

              <!-- listing of the fields, usually I won't customise this

              <metal:block fill-slot="widgets">

              </metal:block>

              -->



              <!-- below the fields above the formControls (hidden fields for refernce stuff is above buttons) -->

              <metal:block fill-slot="extra_bottom">

              </metal:block>



              <!-- within the formControls these are the default previous, next, save, cancel buttons -->

              <metal:block fill-slot="buttons">

              </metal:block>



              <!-- within the formControls a slot for extra buttons -->

              <metal:block fill-slot="extra_buttons">

              </metal:block>



            </metal:default_body>

        </metal:body>



        

        <!-- footer, by line created date etc. -->

        <metal:footer define-macro="footer">

        

        </metal:footer>



  </body>



</html>



See the templates into Products.Archetypes:skins/archetypes for examples about how does Archetypes work by default: get the field lists, hook up translation, handle form processing and more. Using them as a base and customizing only the neccessary bits can make the job much easier than starting from scratch.

How to do it

Lets say your content type is 'Newsletter'

Steps for View

  1. Create a page template (either file system of in ZMI) called 'newsletter_view'
  2. Use the skeleton and comment out the macros you wish to keep the same. I.e. the ones you want to use from view_macros template (in portal_skins/archetypes)
  3. Put your code into the relevant macros/slots.
  4. Test and you are done.

Steps for Edit

  1. Create a page template called 'newsletter_edit'
  2. Use the skeleton and then comment out the macros you wish to use the default for. (from edit_macros).
  3. Put your code into the relevant macros/slots.
  4. Test and you are done.

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.