Advanced Zope Page Templates

In the chapter entitled Using Zope Page Templates you learned the basics features of Page Templates. In this chapter you'll learn about advanced techniques including new types of expressions and macros.

Advanced TAL

In this section we'll go over all TAL statements and their various options in depth. This material is covered more concisely in Appendix C, Zope Page Templates Reference.

In this chapter, the terms tag and element are used in the sense laid out by the XHTML spec. "<p>" is a tag, while the entire block "<p>stuff</p>" from opening tag through the closing tag is an element.

Advanced Content Insertion

You've already seen how tal:content and tal:replace work in the chapter entitled Using Zope Page Templates. In this section you'll learn some advanced tricks for inserting content.

Inserting Structure

Normally, the tal:replace and tal:content statements convert HTML tags and entities in the text that they insert into an "escaped" form that appears in the resulting document as plain text rather than HTML markup. For instance, the < character is "escaped" to &amp;lt;. If you want to insert text as part of the HTML structure of your document, avoiding this conversion , you need to precede the expression with the structure keyword.

This feature is useful when you are inserting a fragment of HTML or XML 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="newsItem here/topNews"             tal:content="structure newsItem">            A news item with<code>HTML</code> markup.          </p>

 

This will insert the news items' HTML into a series of paragraphs. The built-in variable here refers to the folder in which the template is rendered; See the "Expressions" section further below in this chapter for more information on here. In this case, we use here as the starting point for finding the Zope object topNews, which is presumably a list of news items or a Script which fetches such a list.

The structure keyword prevents the text of each newsItem value from being escaped. It doesn't matter whether the text actually contains any HTML markup, since structure really means "leave this text alone". This behavior is not the default because most of the text that you insert into a template will not contain HTML, but may contain characters that would interfere with the structure of your page.

Dummy Elements

You can include page elements that are visible in the template but not in generated text by using the built-in 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 be populated with dynamic content. 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.

Default Content

You can leave the contents of an element alone by using the default expression with tal:content or tal:replace. For example:

          <p tal:content="default">Spam</p>

 

This renders to:

          <p>Spam</p>

 

Most often you will want to selectively include default content, rather than always including it. For example:

          <p tal:content="python:here.getFood() or default">Spam</p>

 

Note: Python expressions are explained later in the chapter. If the getFood method returns a true value then its result will be inserted into the paragraph, otherwise it's Spam for dinner.

Advanced Repetition

You've already seen most of what you can do with the tal:repeat statement in the chapter entitled Using Zope Page Templates. This section covers a few advanced features of the tal:repeat statement.

Repeat Variables

One topic that bears more explanation are repeat variables. Repeat variables provide information about the current repetition. The following attributes are available on repeat variables:

  • index - repetition number, starting from zero.
  • number - repetition number, starting from one.
  • even - true for even-indexed repetitions (0, 2, 4, ...).
  • odd - true for odd-indexed repetitions (1, 3, 5, ...).
  • start - true for the starting repetition (index 0).
  • end - true for the ending, or final, repetition.
  • length - length of the sequence, which will be the total number of repetitions.
  • letter - count reps with lower-case letters: "a" - "z", "aa" - "az", "ba" - "bz", ..., "za" - "zz", "aaa" - "aaz", and so forth.
  • Letter - upper-case version of letter.

 

You can access the contents of a repeat variable using path expressions or Python expressions. In path expressions, you write a three-part path consisting of the name repeat, the statement variable's name, and the name of the information you want, for example, repeat/item/start. In Python expressions, you use normal dictionary notation to get the repeat variable, then attribute access to get the information, for example, 'python:repeat['item'].start'. The reason that you can't simply write repeat/start is that tal:repeat statements can be nested, so you need to be able to specify which one you want information about.

Repetition Tips

Here are a couple practical tips that you may find useful. Sometimes you'd like to repeat part of your template, but there is no naturally enclosing element. In this case, you must add an enclosing element, but you want to prevent it from appearing in the rendered page. You can do this with the tal:omit-tag statement:

          <div tal:repeat="section here/getSections"               tal:omit-tag="">            <h4 tal:content="section/title">Title</h4>            <p tal:content="section/text">quotation</p>          </div>

 

This is not just a matter of saving a few characters in the rendered output. Including the div tags in the output could affect the page layout, especially if it has stylesheets. We use the tal omit-tag statement to exclude the div tag (and its pair closing tag) while leaving its contents unmolested. The tal:omit-tag statement is described in more detail later in this chapter.

While it's been mentioned before, it's worth saying again: you can nest tal:repeat statements inside each other. Each tal:repeat statement must have a different repeat variable name. Here's an example that shows a math times-table:

          <table border="1">            <tr tal:repeat="x python:range(1, 13)">              <td tal:repeat="y python:range(1, 13)"                  tal:content="python:'%d x %d = %d' % (x, y, x*y)">                  X x Y = Z              </td>            </tr>          </table>

 

This example uses Python expressions, which are covered later in this chapter.

If you've done much work with the dtml-in DTML repetition statement, you will have encountered batching. Batching is the process of chopping up a large list into smaller lists. You typically use it to display a small number of items from a large list on a web page. Think of how a search engine batches its search results. The tal:repeat statement does not support batching, but Zope comes with a batching utility. See the section, "Batching" later in this chapter.

Another useful feature that isn't supplied by tal:repeat is sorting. If you want to sort a list you can either write your own sorting script (which is quite easy in Python) or you can use the sequence.sort utility function. Here's an example of how to sort a list of objects by title, and then by modification date:

          <table tal:define="objects here/objectValues;                             sort_on python:(('title', 'nocase', 'asc'),                                             ('bobobase_modification_time', 'cmp', 'desc'));                             sorted_objects python:sequence.sort(objects, sort_on)">            <tr tal:repeat="item sorted_objects">              <td tal:content="item/title">title</td>              <td tal:content="item/bobobase_modification_time">                modification date</td>              </tr>          </table>

 

This example tries to make things clearer by defining the sort arguments outside the sort function. The sequence.sort function takes a sequence and a description of how to sort it. In this example the description of how to sort the sequence is defined in the sort_on variable. See Appendix B, API Reference for more information on the powerful sequence.sort function.

Advanced Attribute Control

You've already met the tal:attributes statement. You can use it to dynamically replace tag attributes, for example, the href attribute on an a element. You can replace more than one attribute on a tag by separating attributes with semicolons. For example, the code below will generate an "href" and a "class" attribute:

        <a href="link"           tal:attributes="href here/getLink;                           class here/getClass">link</a>

 

You can also define attributes with XML namespaces. For example:

        <Description             dc:Creator="creator name"            tal:attributes="dc:Creator here/owner/getUserName">          Description</Description>

 

Simply put the XML namespace prefix before the attribute name and you can create attributes with XML namespaces.

Defining Variables

You can define your own variable using the tal:define attribute. There are several reasons that you might want to do this. One reason is to avoid having to write long expressions repeatedly in a template. Another is to avoid having to call expensive methods repeatedly. You can define a variable once within an element on a tag and then use it many times within elements which are enclosed by this tag. For example, here's a list that defines a variable and later tests it and repeats over it:

        <ul tal:define="items container/objectIds"            tal:condition="items">          <li tal:repeat="item items">            <p tal:content="item">id</p>          </li>        </ul>

 

The tal:define statement creates the variable items, which you can use anywhere in the ul element. Notice also how you can have two TAL statements on the same ul tag. See the section "Interactions Between TAL Statements" later in this chapter for more information about using more than one statement on a tag. In this case the first statement assigns the variable items and the second uses items in a condition to see whether it is false (in this case, an empty sequence) or true. If the items variable is false, then the ul element is not shown.

Now, suppose that instead of simply removing the list when there are no items, you want to show a message. To do this, place the following before the list:

        <h4 tal:condition="not:container/objectIds">There        Are No Items</h4>

 

The expression, not:container/objectIds is true when container/objectIds is false, and vice versa. See the section, "Not Expressions" later in this chapter for more information.

You can't use your items variable here, because it isn't defined yet. If you move the definition of items to the h4 element, then you can't use it in the ul element any more, because it becomes a local variable of the h4 element. You could place the definition on some element that enclosed both the h4 and the ul, 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 span tag to the bottom of the template:

        <span tal:define="global items container/objectIds"></span>        <h4 tal:condition="not:items">There Are No Items</h4>

 

You can define more than one variable using tal:define by separating them with semicolons. For example:

        <p tal:define="ids container/objectIds;                        title container/title">

 

You can define as many variables as you wish. Each variable can have its own global or local scope. You can also refer to earlier defined variables in later definitions. For example:

        <p tal:define="title template/title;                       global untitled not:title;                       tlen python:len(title);">

 

In this case, both title and tlen are local to the paragraph, but untitled is global. With judicious use of tal:define you can improve the efficiency and readability of your templates.

Omitting Tags

You can remove tags with the tal:omit-tag statement. You will seldom need to use this TAL statement, but occasionally it's useful. The omit-tag attribute removes opening and closing tags, but does not affect the contents of the element. For example:

        <b tal:omit-tag=""><i>this</i> stays</b>

 

Renders to:

        <i>this</i> stays

 

At this level of usage, tal:omit-tag operates almost like tal:replace="default". However, tal:omit-tag can also be used with a true/false expression, in which case it only removes the tags if the expression is true. For example:

        Friends: <span tal:repeat="friend friends">          <b tal:omit-tag="not:friend/best"             tal:content="friend/name">Fred</b>        </span>

 

This will produce a list of friends, with our "best" friend's name in bold.

Error Handling

If an error occurs in your page template, you can catch that error and show a useful error message to your user. For example, suppose your template defines a variable using form data:

        ...        <span tal:define="global prefs request/form/prefs"              tal:omit-tag="" />        ...

 

If Zope encounters a problem, like not being able to find the prefs variable in the form data, the entire page will break; you'll get an error page instead. Happily, you can avoid this kind of thing with limited error handling using the tal:on-error statement:

        ...        <span tal:define="global prefs here/scriptToGetPreferences"              tal:omit-tag=""              tal:on-error="string:An error occurred">        ...

 

When an error is raised while rendering a template, Zope looks for a tal:on-error statement to handle the error. It first looks in the current element, then on its enclosing element, and so on until it reaches the top-level element. When it finds an error handler, it replaces the contents of that element with the error handling expression. In this case, the span element will contain an error message.

Typically you'll define an error handler on an element that encloses a logical page element, for example a table. If an error crops up drawing the table, then the error handler can simply omit the table from the page, or else replace it with an error message of some sort.

For more flexible error handling you can call a script. For example:

        <div tal:on-error="structure here/handleError">        ...        </div>

 

Any error that occurs inside the div will call the handleError script. Note that the structure option allows the script to return HTML. Your error handling script can examine the error and take various actions depending on the error. Your script gets access to the error through the error variable in the namespace. For example:

        ## Script (Python) "handleError"        ##bind namespace=_        ##        error=_['error']        if error.type==ZeroDivisionError:            return "<p>Can't divide by zero.</p>"        else            return """<p>An error occurred.</p>                      <p>Error type: %s</p>                      <p>Error value: %s</p>""" % (error.type,                                                   error.value)

 

Your error handling script can take all kinds of actions, for example, it might log the error by sending email.

The tal:on-error statement is not meant for general purpose exception handling. For example, you shouldn't validate form input with it. You should use a script for that, since scripts allow you to do powerful exception handling. The tal:on-error statement is for dealing with unusual problems that can occur when rendering templates.

Interactions Between TAL Statements

When there is only one TAL statement per element, the order in which they are executed is simple. Starting with the root element, each element's statements are executed, then each of its child elements are visited, in order, and their statements are executed, and so on.

However, it's possible to have more than one TAL statement on the same element. Any combination of statements may appear on the same element, except that the tal:content and tal:replace statements may not appear together.

When an element has multiple statements, they are executed in this order:

  1. define
  2. condition
  3. repeat
  4. content or replace
  5. attributes
  6. omit-tag

 

Since the tal:on-error statement is only invoked when an error occurs, it does not appear in the list.

The reasoning behind this ordering goes like this: you often want to set up variables for use in other statements, so define comes first. The very next thing to do is decide whether this element will be included at all, so condition is next; since the condition may depend on variables you just set, it comes after define. It is valuable to be able to replace various parts of an element with different values on each iteration of a repeat, so repeat comes before content, replace and attributes. Content and replace can't both be used on the same element so they occur at the same place. Omit-tag comes last since no other statements are likely to depend on it and since it should come after define and repeat.

Here's an example element that includes several TAL statements:

        <p tal:define="x /root/a/long/path/x | nothing"           tal:condition="x"           tal:content="x/txt"           tal:attributes="class x/class">Ex Text</p>

 

Notice how the tal:define statement is executed first, and the other statements rely on its results.

There are three limits you should be aware of when combining TAL statements on elements:

  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. For example, 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 fixed order described earlier.

If you want to override the ordering of TAL statements, you must do so by enclosing the element in another element and placing some of the statements on this new element. For example suppose you want to loop over a series of items but skip some. Here's an attempt to write a template that loops over the numbers zero to nine and skips three:

        <!-- broken template -->        <ul>          <li tal:repeat="n python:range(10)"              tal:condition="python:n != 3"              tal:content="n">             1          </li>        </ul>

 

This template doesn't work due to TAL statement execution order. Despite the order in which they are written, the condition is always tested before the repeat is executed. This results in a situation in which the n variable is not defined until after it is tested, which ultimately causes an error when you attempt to test or otherwise view the template. Here's a way around this problem:

        <ul>          <div tal:repeat="n python:range(10)"               tal:omit-tag="">            <li tal:condition="python:n != 3"                tal:content="n">               1            </li>          </div>        </ul>

 

This template solves the problem by defining the n variable on an enclosing div element. Notice that the div tag will not appear in the output due to its tal:omit-tag statement.

Although span and div are natural choices for this in HTML, there is, in general, no equivalent natural element in XML. In this case, you can use TAL's namespace in a new way: while TAL does not define any tags, it doesn't prohibit any either. You can make up any tag name you like within the TAL namespace, and use it to make an element, like so:

        <tal:series define="items here/getItems">          <tal:items repeat="item items">          <tal:parts repeat="part item">            <part tal:content="part">Part</part>          </tal:parts>          </tal:items>          <noparts tal:condition="not:items" />        </tal:series>

 

The tal:series, tal:items, and tal:parts tags in this example should be acceptable to tools that handle XML namespaces properly, and to many HTML tools. This method has two additional advantages over a div. First, TAL tags are omitted just like TAL attributes, so no tal:omit-tag is necessary. Second, TAL attributes in these tags don't require their own tal: prefix, since they inherit the namespace of the tag. The METAL namespace can be used in exactly the same fashion.

Form Processing

You can process forms in DTML using a common pattern called the "form/action pair". A form/action pair consists of two DTML methods or documents: one that contains a form that collects input from the user, and one that contains an action that is taken on that input and returns the user a response. The form calls the action. See the chapter entitled Dynamic Content with DTML for more information on the form/action pattern.

Zope Page Templates don't work particularly well with the form/action pattern since it assumes that input processing and response presentation are handled by the same object (the action). Instead of the form/action pattern you should use form/action/response pattern with Page Templates. The form and response should be Page Templates and the action should be a script. The form template gathers the input and calls the action script. The action script should process the input and return a response template. This pattern is more flexible than the form/action pattern since it allows the script to return any of a number of different response objects.

For example here's a part of a form template:

        ...        <form action="action">          <input type="text" name="name">          <input type="text" name="age:int">          <input type="submit">        </form>        ...

 

This form could be processed by this script:

        ## Script (Python) "action"        ##parameters=name, age        ##        container.addPerson(name, age)        return container.responseTemplate()

 

This script calls a method to process the input and then returns another template, the response. You can render a Page Template from Python by calling it. The response template typically contains an acknowledgment that the form has been correctly processed.

The action script can do all kinds of things. It can validate input, handle errors, send email, or whatever it needs to do to "get the job done". Here's a sketch of how to validate input with a script:

        ## Script (Python) "action"        ##        if not context.validateData(request):            # if there's a problem return the form page template            # along with an error message            return context.formTemplate(error_message='Invalid data')        # otherwise return the thanks page        return context.responseTemplate()

 

This script validates the form input and returns the form template with an error message if there's a problem. The Script's context variable is equivalent to here in TALES. You can pass Page Templates extra information with keyword arguments. The keyword arguments are available to the template via the options built-in variable. So the form template in this example might include a section like this:

        <span tal:condition="options/error_message | nothing">        Error: <b tal:content="options/error_message">          Error message goes here.        </b></span>

 

This example shows how you can display an error message that is passed to the template via keyword arguments. Notice the use of | nothing to handle the case where no error_message argument has been passed to the template.

Depending on your application you may choose to redirect the user to a response Page Template instead of returning it directly. This results in twice as much network activity, but might be useful because it changes the URL displayed in the user's browser to the URL of the Page Template, rather than that of the action script.

If you need to set up a quick-and-dirty form, you can always create a version of the form-action pair using Page Templates alone. You should only do this when you don't care about error handling and when the response will always be the same, no matter what the user submits. Since Page Templates don't have an equivalent of dtml-call, you can use one of any number of hacks to call an input processing method without inserting its results. For example:

        <span tal:define="unused here/processInputs"               tal:omit-tag=""/>

 

This sample calls the processInputs method and assigns the result to the unused variable.

Expressions

You've already encountered Page Template expressions. Expressions provide values to template statements. For example, in the TAL statement <td tal:content="request/form/age">Age</td>, the expression of the statement is request/form/age. request/form/age is an example of a path expression. Path expressions describe objects by giving them paths such as request/form/age, or user/getUserName. Expressions only work in the context of a TAL statement; they do not work in "normal" HTML inserted in your page templates. In this section you'll learn about all the different types of expressions, and variables.

Built-in Page Template Variables

Variables are names that you can use in expressions. You have already seen some examples of the built-in variables such as template, user, repeat, and request. Here is the complete list of the other built-in variables and their uses. Note that these variables are different than the built-in variables that you would use in a Script (Python), they are only effective for Page Templates::

nothing
A false value, similar to a blank string, that you can use in tal:replace or tal:content to erase an element or its contents. If you set an attribute to nothing, the attribute is removed from the tag (or not inserted). A blank string, on the other hand, would insert the tag with an empty value, as in alt="".
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. When a template is rendered from the web, no options are present. Options are only available when a template is called from Python or by similarly complex means. For example, when the template t is called by the Python expression t(foo=1), the path options/foo equals 1.
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. This variable is rarely needed.
root
The root Zope object. Use this to get Zope objects from fixed locations, no matter where your template is placed or called.
here
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. The here variable is analogous to the context variable in Python-based scripts.
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. The container and here variables refer to the same object when a template is called from its normal location. However, when a template is applied to another object (for example, a ZSQL Method) the container and here will not refer to the same object.
modules
The collection of Python modules available to templates. See the section on writing Python expressions.

You'll find examples of how to use these variables throughout this chapter.

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 ('$'). Here are some examples:

        "string:Just text. There's no path here."        "string:copyright $year by Fred Flintstone."

 

If the path expression has more than one part (if it contains a slash), or needs to be separated from the text that follows it, it must be surrounded by braces ('{}'). For example:

        "string:Three ${vegetable}s, please."        "string:Your name is ${user/getUserName}!"

 

Notice how in the example above, you need to surround the vegetable path with braces so that Zope doesn't mistake it for vegetables.

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:

        "string:Please pay $$$dollars_owed"        "string:She said, &quot;Hello world.&quot;"

 

Some complex string formatting operations (such as search and replace or changing capitalization) can't easily be done with string expressions. For these cases, you should use Python expressions or Scripts.

Path Expressions

Path expressions refer to objects with a path that resembles a URL path. A path describes a traversal from object to object. All paths begin with a known object (such as a built-in variable, a repeat variable, or a user defined variable) and depart from there to the desired object. Here are some example paths expressions:

        template/title        container/files/objectValues        user/getUserName        container/master.html/macros/header        request/form/address        root/standard_look_and_feel.html

 

With path expressions you can traverse from an object to its sub-objects including properties and methods. You can also use acquisition in path expressions. See the section entitled "Calling Scripts from the Web" in the chapter entitled Advanced Zope Scripting for more information on acquisition and path traversal.

Zope restricts object traversal in path expressions in the same way that it restricts object access via URLs. You must have adequate permissions to access an object in order to refer to it with a path expression. See the chapter entitled Users and Security for more information about object access controls.

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 Zope evaluates the path expression.

When a path doesn't exist, you may 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 here/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 | here/x">Header</h4>

 

Two variables that are very useful as the last path in a list of alternates are nothing and default. For example, default tells tal:content to leave the dummy content. Different TAL statements interpret default and nothing differently. See Appendix C, "Zope Page Templates Reference" for more information.

You can also use a non-path expression as the final part in an alternate-path expression. For example:

          <p tal:content="request/form/age|python:18">age</p>

 

In this example, if the request/form/age path doesn't exist, then the value is the number 18. This form allows you to specify default values to use which can't be expressed as paths. Note, you can only use a non-path expression as the last alternative.

 

The "here/?myvar" expression is evaluated by traversing from "here" to the name given by the value of "myvar". For example, if "myvar" is set to "title", "here/?myvar" is equivalent to "here/title".

 

You can also test the existence of a path directly with the exists expression type prefix. See the section "Exists Expressions" below for more information on exists expressions.

Not Expressions

Not expressions let you negate the value of other expressions. For example:

        <p tal:condition="not:here/objectIds">          There are no contained objects.        </p>

 

Not expressions return true when the expression they are applied to is false, and vice versa. In Zope, zero, empty strings, empty sequences, nothing, and None are considered false, while everything else is true. Non-existent paths are neither true nor false, and applying a not: to such a path will fail.

There isn't much reason to use not expressions with Python expressions since you can use the Python not keyword instead.

Nocall 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:here/aDoc"              tal:content="string:${doc/getId}: ${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.

Nocall expressions can also be used on functions, rather than objects:

        <p tal:define="join nocall:modules/string/join">

 

This expression defines the join variable as a function ('string.join'), rather than the result of calling a function.

Exists Expressions

An exists expression is true if its path exists, and otherwise is false. For example here's one way to 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>

 

You can do the same thing more easily with an exists expression:

        <h4 tal:condition="exists:request/form/errmsg"            tal:content="request/form/errmsg">Error!</h4>

 

You can combine exists expressions with not expressions, for example:

        <p tal:condition="not:exists:request/form/number">Please enter        a number between 0 and 5</p>

 

Note that in this example you can't use the expression, "not:request/form/number", since that expression will be true if the number variable exists and is zero.

Python Expressions

The Python programming language is a simple and expressive one. If you have never encountered it before, you should read one of the excellent tutorials or introductions available at the Python website.

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. In addition, Zope imposes some security restrictions to keep you from accessing protected information, changing secured data, and creating problems such as infinite loops. See the chapter entitled Advanced Zope Scripting for more information on Python security restrictions.

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 is no support in TAL to do this without Python expressions. In Python expressions, 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>

 

This example loops over a collection of objects, printing information about widgets which are of type gear.

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>

 

If the user is Anonymous, then the span element is replaced with the text need to log in. Otherwise, the default content is used, which is in this case 'are logged in as ...'.

The test function works like an if/then/else statement. See Appendix A, DTML Reference for more information on the test function. Here's another example of how you can use the test function:

          <tr tal:define="oddrow repeat/item/odd"              tal:attributes="class python:test(oddrow, 'oddclass',                                                'evenclass')">

 

This assigns oddclass and evenclass class attributes to alternate rows of the table, allowing them to be styled differently in HTML output, for example.

Without the test function you'd have to write two tr elements with different conditions, one for even rows, and the other for odd rows.

Using other Expression Types

You can use other expression types inside of a Python expression. Each expression 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('here/%s/thing' % foldername)"          "python:path(string('here/$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 these objects you have to know how to get access to them within Page Templates.

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 "here.Control_Panel" will acquire the Control Panel object from the root Folder. Object methods are attributes, as in "here.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, you cannot access the penguin.gif object with the following Python expression:

          "python:here.penguin.gif"

 

Instead, you must write:

          "python:getattr(here, 'penguin.gif')"

 

since Python doesn't support attribute names with periods.

Some objects, such as request, modules, and Zope Folders support Python item access, for example:

          request['URL']          modules['math']          here['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.

As shown in previous chapters, 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:

          "here/images/penguin.gif"

 

instead of:

          "python:getattr(here.images, 'penguin.gif')"

 

and:

          "request/form/x" 

 

instead of:

          "python:request.form['x']"

 

The trade-off 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 this path expression:

          "request/form/get" 

 

will evaluate to the "get" method of the form dictionary.

If you prefer you can use path expressions inside Python expressions using the path() function, as described above.

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. If you have trouble understanding your template statements and expressions, then it's better to simplify your Page Template and use Scripts for the complex stuff.

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 in order to supply the argument, like this:

          "python:here.myscript(1, 2)"          "python:here.myscript('arg', foo=request.form['x'])"

 

If you want to return more than one item 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 item. For example, suppose the getPerson script returns a dictionary with name and age keys:

          <span tal:define="person here/getPerson"                tal:replace="string:${person/name} is ${person/age}">          Name is 30</span> years old.

 

Of course, it's fine to return Zope objects and Python lists as well.

Calling DTML

Unlike Scripts, DTML Methods and Documents don't have an explicit parameter list. Instead, they expect to be passed a client, a mapping, and keyword arguments. They use these parameters to construct a namespace. See the chapter entitled Variables and Advanced DTML for more information on explicitly calling DTML.

When Zope publishes a DTML object through the web, 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, here, 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. For example, here is a template that uses a DTML Method to generate JavaScript:

          <head tal:define="items here/getItems.sql">            <title tal:content="template/title">Title</title>            <!-- Changed by: Peter Sabaini, 30-Jan-2004 -->            <script tal:content="structure here/jsItems"></script>          </head>          ...etc...

 

...and here is the DTML Method jsItems:

          <dtml-let prefix="template.id">          <dtml-in items>          &dtml-prefix;_&dtml-name; = &dtml-value; ;          </dtml-in>          </dtml-let>

 

The DTML uses the template's id, and the items variable that it defined just before the call.

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, "modules/math/pi".

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, ':')"

 

In practice you'll rarely need to do this since you can use string methods most of the time rather than having to rely on functions in the string module.

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="global pps modules/Products/PythonScripts/standard"          tal:define="global pps python:modules['Products.PythonScripts.standard']"

 

Many Python modules cannot be accessed from Page Templates, DTML, or Scripts unless you add Zope security assertions to them.

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, much as you can with DTML (e.g. by inserting standard_html_header and standard_html_footer into your DTML), but in a slightly different way.

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.

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 2001, <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).

Macros defined in a Page Template are stored in the template's macros attribute. You can use macros from other page template 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 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 2001, <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.

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

If you check the Expand macros when editing option on the Page Template Edit view, then any macros that you use will be expanded in your template's source. When you're editing in the ZMI, rather than using a WYSIWYG editing tool, it's more convenient not to expand macros when editing. This is the default for newly created templates. When using WYSIWYG tools, however, it is often desirable to have the macros expanded so you are editing a complete page. In this case, check the Expand macros.. checkbox before editing the page.

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.html/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.

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 here/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 an (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="here/title">The title</title>          </head>          <body>            <h1 metal:define-slot="headline"                tal:content="here/title">title</h1>            <p metal:define-slot="body">              This is the body.              </p>            <span metal:define-slot="footer">              <p>Copyright 2001 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.html/macros/page">         <h1 metal:fill-slot="headline">           Press Release:            <span tal:replace="here/getHeadline">Headline</span>         </h1>         <p metal:fill-slot="body"            tal:content="here/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.

This is a much more flexible solution to control page look and feel then the DTML standard_html_header and standard_html_footer solution. In fact, Zope comes with a stock page template in the root folder named standard_template.pt that includes a whole page macro with a head and body slot. Here's how you might use this macro in a template:

        <html metal:use-macro="here/standard_template.pt/macros/page">          <div metal:fill-slot="body">            <h1 tal:content="here/title">Title</h1>            <p tal:content="here/getBody">Body text goes here</p>          </div>        </html>

 

Using the standard_template.pt macro is very similar to using other whole page macros. The only subtlety worth pointing out is the path used to locate the macro. In this example the path begins with here. This means that Zope will search for the standard_template.pt object using acquisition starting at the object that the template is applied to. This allows you to customize the look and feel of templates by creating custom standard_template.pt objects in various locations. This is exactly the same trick that you can use to customize look and feel by overriding standard_html_header and standard_html_footer in site locations. However, with standard_template.pt you have more choices. You can choose to start the path to the macro with root or with container, as well as with here. If the path begins with root then you will always get the standard template which is located in the root folder. If the path begins with container then Zope will search for a standard template using acquisition starting in the folder where the template is defined. This allows you to customize look and feel of templates, but does not allow you to customize the look and feel of different objects based on their location in the site.

Caching Templates

While rendering Page Templates normally is quite fast, sometimes it's not fast enough. For frequently accessed pages, or pages that take a long time to render, you may want to trade some dynamic behavior for speed. Caching lets you do this. For more information on caching see the "Cache Manager" section of the chapter entitled Basic Objects.

You can cache Page Templates using a cache manager in the same way that you cache other objects. To cache a Page Template, you must associate it with a cache manager. You can either do this by going to the Cache view of your Page Template and selecting the cache manager (there must be one in the acquisition path of the template for the Cache view to appear), or by going to the Associate view of your cache manager and locating your Page Template.

Here's an example of how to cache a Page Template. First create a Python-based script name long.py with these contents:

      ## Script (Python) "long.py"      ##      for i in range(500):        for j in range(500):          for k in range(5):            pass      return 'Done'

 

The purpose of this script is to take up a noticeable amount of execution time. Now create a Page Template that uses this script, for example:

      <html>        <body>          <p tal:content="here/long.py">results</p>        </body>      </html>

 

Now view this page. Notice how it takes a while to render. Now let's radically improve its rendering time with caching. Create a Ram Cache Manager if you don't already have one. Make sure to create it within the same folder as your Page Template, or in a higher level. Now visit the Cache view of your Page Template. Choose the Ram Cache Manager you just created and click Save Changes. Click the Cache Settings link to see how your Ram Cache Manager is configured. By default, your cache stores objects for one hour (3600 seconds). You may want to adjust this number depending on your application. Now return to your Page Template and view it again. It should take a while for it to render. Now reload the page, and watch it render immediately. You can reload the page again and again, and it will always render immediately since the page is now cached.

If you change your Page Template, then it will be removed from the cache. So the next time you view it, it will take a while to render. But after that it will render quickly since it will be cached again.

Caching is a simple but very powerful technique for improving performance. You don't have to be a wizard to use caching, and it can provide great speed-ups. It's well worth your time to use caching for performance-critical applications.

For more information on caching in the context of Zope, see the chapter entitled Zope Services.

Page Template Utilities

Zope Page Templates are powerful but simple. Unlike DTML, Page Templates don't give you a lot of convenience features for things like batching, drawing trees, sorting, etc. The creators of Page Templates wanted to keep them simple. However, you may miss some of the built-in features that DTML provides. To address these needs, Zope comes with utilities designed to enhance Page Templates.

Batching Large Sets of Information

When a user queries a database and gets hundreds of results, it's often better to show them several pages with only twenty results per page, rather than putting all the results on one page. Breaking up large lists into smaller lists is called batching.

Unlike DTML, which provides batching built into the language, Page Templates support batching by using a special Batch object that comes from the ZTUtils utility module. See Appendix B, API Reference, for more information on the ZTUtils Python module.

Here's a simple example, showing how to create a Batch object:

        <ul tal:define="lots python:range(100);                        batch python:modules['ZTUtils'].Batch(lots,                                                               size=10,                                                              start=0)">          <li tal:repeat="num batch"              tal:content="num">0          </li>        </ul>

 

This example renders a list with 10 items (in this case, the numbers 0 through 9). The Batch object chops a long list up into groups or batches. In this case it broke a one hundred item list up into batches of ten items.

You can display a different batch of ten items by passing a different start number:

        <ul tal:define="lots python:range(100);                        batch python:modules['ZTUtils'].Batch(lots,                                                               size=10,                                                              start=13)">

 

This batch starts with the fourteenth item and ends with the twenty third item. In other words, it displays the numbers 13 through 22. It's important to notice that the batch start argument is the index of the first item. Indexes count from zero, rather than from one. So index 13 points to the fourteenth item in the sequence. Python uses indexes to refer to list items.

Normally when you use batches you'll want to include navigation elements on the page to allow users to go from batch to batch. Here's a full-blow batching example that shows how to navigate between batches:

        <html>          <head>            <title tal:content="template/title">The title</title>          </head>          <body tal:define="employees here/getEmployees;                 start python:int(path('request/start | nothing') or 0);                 batch python:modules['ZTUtils'].Batch(employees,                                                        size=3,                                                        start=start);                 previous python:batch.previous;                 next python:batch.next">          <p>            <a tal:condition="previous"               tal:attributes="href string:${request/URL0}?start:int=${previous/first}"               href="previous_url">previous</a>            <a tal:condition="next"               tal:attributes="href string:${request/URL0}?start:int=${next/first}"               href="next_url">next</a>          </p>          <ul tal:repeat="employee batch" >            <li>              <span tal:replace="employee/name">Bob Jones</span>              makes $<span tal:replace="employee/salary">100,000</span>              a year.            </li>          </ul>          </body>        </html>

 

Define a Script (Python) with the name getEmployees in the same folder with the following body (no parameters are necessary):

        return [  {'name': 'Chris McDonough', 'salary':'5'},                  {'name': 'Guido van Rossum', 'salary': '10'},                  {'name': 'Casey Duncan', 'salary':'20' },                  {'name': 'Andrew Sawyers', 'salary':'30' },                  {'name': 'Evan Simpson', 'salary':'35' },                   {'name': 'Stephanie Hand', 'salary':'40' }, ]

 

This example iterates over batches of results from the getEmployees method. It draws a previous and a next link as necessary to allow you to page through all the results a batch at a time. The batch size in this case is 3.

Take a look at the tal:define statement on the body element. It defines a bunch of batching variables. The employees variable is a list of employee objects returned by the getEmployees Script. It is not very big now, but it could grow fairly large (especially if it were a call into a SQL Method of real employees). The second variable, start, is either set to the value of request/start or to zero if there is no start variable in the request. The start variable keeps track of where you are in the list of employees. The batch variable is a batch of ten items from the lists of employees. The batch starts at the location specified by the start variable. The previous and next variables refer to the previous and next batches (if any). Since all these variables are defined on the body element, they are available to all elements inside the body.

Next let's look at the navigation links. They create hyper links to browse previous and next batches. The tal:condition statement first tests to see if there is a previous and next batch. If there is a previous or next batch, then the link is rendered, otherwise there is no link. The tal:attributes statement creates a link to the previous and next batches. The link is simply the URL or the current page ('request/URL0') along with a query string indicating the start index of the batch. For example, if the current batch starts with index 10, then the previous batch will start with an index of 0. The first variable of a batch gives its starting index, so in this case, previous.start would be 0.

It's not important to fully understand the workings of this example. Simply copy it, or use a batching example created by the Z Search Interface. Later when you want to do more complex batching you can experiment by changing the example code. Don't forget to consult Appendix B, API Reference for more information on the ZTUtils module and Batch objects.

Miscellaneous Utilities

Zope provides a couple Python modules which may come in handy when using Page Templates. The string, math, and random modules can be used in Python expressions for string formatting, math function, and pseudo-random number generation. These same modules are available from DTML and Python-based scripts.

The Products.PythonScripts.standard module is designed to provide utilities to Python-based scripts, but it's also useful for Page Templates. It includes various string and number formatting functions.

As mentioned earlier in the chapter, the sequence module provides a handy sort function.

Finally the AccessControl module includes a function and a class which you'll need if you want to test access and to get the authenticated user.

See Appendix B, API Reference for more information on these utilities.

Conclusion

This chapter covers some useful and some obscure nooks and crannies of Page Templates, and after reading it you may feel a little overwhelmed. Don't worry, you don't need to know everything in this chapter to effectively use Page Templates. You should understand the different path types and macros, but you can come back to the rest of the material when you need it. The advanced features that you've learned about in this chapter are there for you if and when you need them.