DTML is the kind of language that "does what you mean." That is good, when it does what you actually want it to do, but when it does something you don't want to do, it's bad. This chapter tells you how to make DTML do what you really mean.
It's no lie that DTML has reputation for complexity. And it's true, DTML is really simple if you all you want to do is simple layout, like you've seen so far. However, if you want to use DTML for more advanced tasks, you have to understand where DTML variables come from.
Here's a very tricky error that almost all newbies encounter. Imagine you have a DTML Document named called zooName. This document contains an HTML form like the following:
<dtml-var standard_html_header> <dtml-if zooName> <p><dtml-var zooName></p> <dtml-else> <form action="<dtml-var URL>" method="GET"> <input name="zooName"> <input type="submit" value="What is zooName?"> </form> </dtml-if> <dtml-var standard_html_footer>
This looks simple enough, the idea is, this is an HTML page that calls itself. This is because the HTML action is the URL variable, which will become the URL of the DTML Document.
If there is a
zooName variable, then the page will print it, if there
isn't, it shows a form that asks for it. When you click submit, the data
you enter will make the "if" evaluate to true, and this code should print
what entered in the form.
But unfortunately, this is one of those instances where DTML will not do what you mean, because the name of the DTML Document that contains this DTML is also named zooName, and it doesn't use the variable out of the request, it uses itself, which causes it call itself and call itself, ad infinitum, until you get an "excessive recursion" error. So instead of doing what you really meant, you got an error. This is what confuses beginners. In the next couple sections, we'll show you how to fix this example to do what you mean.
There's are actually two ways to fix the DTML error in the zooName document. The first is that you can rename the document to something like zopeNameFormOrReply and always remember this special exception and never do it; never knowning why it happens. The second is to understand how names are looked up, and to be explicit about where you want the name to come from in the namespace.
The DTML namespace is a collection of objects arranged in a stack. A stack is a list of objects that can manipulated by pushing and popping objects on to and off of the stack.
When a DTML Document or DTML Method is executed, Zope creates a DTML namespace to resolve DTML variable names. It's important to understand the workings of the DTML namespace so that you can accurately predict how Zope will locate variables. Some of the trickiest problems you will run into with DTML can be resolved by understanding the DTML namespace.
When Zope looks for names in the DTML namespace stack it first looks at the very top most object in the stack. If the name can't be found there, then the next item down is looked in. Zope will work its way down the stack, checking each object in turn until it finds the name that it is looking for.
If Zope gets all the way down to the bottom of the stack and can't find what it is looking for, then an error is generated. For example, try looking for the non-existent name, unicorn:
As long as there is no variable named unicorn viewing this DTML will return an error, as shown in Figure 7-1.
Figure 7-1 DTML error message indicating that it cannot find a variable.
But the DTML stack is not all there is to names because DTML doesn't start with an empty stack, before you even begin executing DTML in Zope there are already a number of objects pushed on the namespace stack.
DTML namespaces are built dynamically for every request in Zope. When you call a DTML Method or DTML Document through the web, the DTML namespace starts with the same first two stack elements the client object and the request as shown in Figure 7-2
Figure 7-2 Initial DTML namespace stack.
The client object is the first object on the top of the DTML namespace stack. What the client object is depends on whether or not you are executing a DTML Method or a DTML Document. In our example above, this means that the client object is named zooName. Which is why it breaks. The form input that we really wanted comes from the web request, but the client is looked at first.
The request namespace is always on the bottom of the DTML namespace
stack, and is therefore the last namespace to be looked in for names.
This means that we must be explicit in our example about which
namespace we want. We can do this with the DTML
<dtml-var standard_html_header> <dtml-with REQUEST only> <dtml-if zooName> <p><dtml-var zooName></p> <dtml-else> <form action="<dtml-var URL>" method="GET"> <input name="zooName"> <input type="submit" value="What is zooName?"> </form> </dtml-if> </dtml-with> <dtml-var standard_html_footer>
Here, the with tag says to look in the
REQUEST namespace, and only
REQUEST namespace, for the name "zooName".
The client object in DTML depends on whether or not you are executing a DTML Method or a DTML Document. In the case of a Document, the client object is always the document itself, or in other words, a DTML Document is its own client object.
A DTML Method however can have different kinds of client objects depending on how it is called. For example, if you had a DTML Method that displayed all of the contents of a folder then the client object would be the folder that is being displayed. This client object can change depending on which folder the method in question is displaying. For example, consider the following DTML Method named list in the root folder:
<dtml-var standard_html_header> <ul> <dtml-in objectValues> <li><dtml-var title_or_id></li> </dtml-in> </ul> <dtml-var standard_html_footer>
Now, what this method displays depends upon how it is used. If
you apply this method to the Reptiles folder with the URL
http://localhost:8080/Reptiles/list, then you will get
something that looks like Figure 7-3.
Figure 7-3 Applying the list method to the Reptiles folder.
But if you were to apply the method to the Birds folder with the URL http://localhost:8080/Birds/list then you would get something different, only two items in the list, Parrot and Raptors.
Same DTML Method, different results. In the first example, the client object of the list method was the Reptiles folder. In the second example, the client object was the Birds folder. When Zope looked up the objectValues variable, in the first case it called the objectValues method of the Reptiles folder, in the second case it called the objectValues method of the Birds folder.
In other words, the client object is where variables such as methods, and properties are looked up first.
As you saw in Chapter 4, "Dynamic Content with DTML", if Zope cannot find a variable in the client object, it searches through the object's containers. Zope uses acquisition to automatically inherit variables from the client object's containers. So when Zope walks up the object hierarchy looking for variables it always starts at the client object, and works its way up from there.
The request object is the very bottom most object on the DTML namespace stack. The request contains all of the information specific to the current web request.
Just as the client object uses acquisition to look in a number of places for variables, so too the request looks up variables in a number of places. When the request looks for a variable it consults these sources in order:
The request namespace is very useful in Zope since it is the primary way that clients (in this case, web browsers) communicate with Zope by providing form data, cookies and other information about themselves. For more information about the request object, see Appendix B.
A very simple and enlightening example is to simply print the REQUEST out in an HTML page:
<dtml-var standard_html_header> <dtml-var REQUEST> <dtml-var standard_html_footer>
Try this yourself, you should get something that looks like Figure 7-4.
Figure 7-4 Displaying the request.
Since the request comes after the client object, if there are names that exist in both the request and the client object, DTML will always find them first in the client object. This can be a problem. Next, let's look at some ways to get around this problem by controlling more directly how DTML looks up variables.
When you insert a variable using the var tag, Zope first looks up the variable using the DTML namespace, it then renders it and inserts the results. Rendering means turning an object or value into a string suitable for inserting into the output. Zope renders simple variables by using Python's standard method for coercing objects to strings. For complex objects such as DTML Methods and SQL Methods, Zope will call the object instead of just trying to turn it into a string. This allows you to insert DTML Methods into other DTML Methods.
In general Zope renders variables in the way you would
expect. It's only when you start doing more advanced tricks that
you become aware of the rendering process. Later in this chapter
we'll look at some examples of how to control rendering using
getitem DTML utility function.
Now that you have seen that the DTML namespace is a stack, you may be wondering how, or even why, new objects get pushed onto it.
Some DTML tags modify the DTML namespace while they are executing. A tag may push some object onto the namespace stack during the course of execution. These tags include the in tag, the with tag, and the let tag.
When the in tag iterates over a sequence it pushes the current item in the sequence onto the top of the namespace stack:
<dtml-var getId> <!-- This is the id of the client object --> <dtml-in objectValues> <dtml-var getId> <!-- this is the id of the current item in the objectValues sequence --> </dtml-in>
You've seen this many times throughout the examples in this book. While the in tag is iterating over a sequence, each item is pushed onto the namespace stack for the duration of the contents of the in tag block. When the block is finished executing, the current item in the sequence is popped off the DTML namespace stack and the next item in the sequence is pushed on.
The with tag pushes an object that you specify onto the top of the namespace stack for the duration of the with block. This allows you to specify where variables should be looked up first. When the with block closes, the object is popped off the namespace stack.
Consider a folder that contains a bunch of methods and properties that you are interested in. You could access those names with Python expressions like this:
<dtml-var standard_html_header> <dtml-var expr="Reptiles.getReptileInfo()"> <dtml-var expr="Reptiles.reptileHouseMaintainer"> <dtml-in expr="Reptiles.getReptiles()"> <dtml-var species> </dtml-in> <dtml-var standard_html_footer>
Notice that a lot of complexity is added to the code just to get things out of the Reptiles folder. Using the with tag you can make this example much easier to read:
<dtml-var standard_html_header> <dtml-with Reptiles> <dtml-var getReptileInfo> <dtml-var reptileHouseMaintainer> <dtml-in getReptiles> <dtml-var species> </dtml-in> </dtml-with> <dtml-var standard_html_footer>
Another reason you might want to use the with tag is to put the request, or some part of the request on top of the namespace stack. For example suppose you have a form that includes an input named id. If you try to process this form by looking up the id variable like so:
You will not get your form's id variable, but the client object's id. One solution is to push the web request's form on to the top of the DTML namespace stack using the with tag:
<dtml-with expr="REQUEST.form"> <dtml-var id> </dtml-with>
This will ensure that you get the form's id first. See Appendix B for complete API documentation of the request object.
If you submit your form without supplying a value for the id input, the form on top of the namespace stack will do you no good, since the form doesn't contain an id variable. You'll still get the client object's id since DTML will search the client object after failing to find the id variable in the form. The with tag has an attribute that lets you trim the DTML namespace to only include the object you specify:
<dtml-with expr="REQUEST.form" only> <dtml-if id> <dtml-var id> <dtml-else> <p>The form didn't contain an "id" variable.</p> </dtml-if> </dtml-with>
Using the only attribute allows you to be sure about where your variables are being looked up.
The let tag lets you push a new namespace onto the namespace stack. This namespace is defined by the tag attributes to the let tag:
<dtml-let person="'Bob'" relation="'uncle'"> <p><dtml-var person>'s your <dtml-var relation>.</p> </dtml-let>
This would display:
<p>Bob's your uncle.</p>
The let tag accomplishes much of the same goals as the with tag. The main advantage of the let tag is that you can use it to define multiple variables to be used in a block. The let tag creates one or more new variables and their values and pushes a namespace object containing those variables and their values on to the top of the DTML namespace stack. In general the with tag is more useful to push existing objects onto the namespace stack, while the let tag is better suited for defining new variables for a block.
When you find yourself writing complex DTML that requires things like new variables, there's a good chance that you could do the same thing better with Python or Perl. Advanced scripting is covered in Chapter 10, "Advanced Zope Scripting".
The DTML namespace is a complex place, and this complexity evolved
over a lot of time. Although it helps to understand where names come
from, it is much more helpful to always be specific about where you
are looking for a name. The
let tags let you control
the namespace to look exactly in the right place for the name you are
Like all things in Zope, the DTML namespace is an object, and it can can be accessed directly in DTML with the _ (underscore) object. The _ namespace is often referred to as as "the under namespace".
The under namespace provides you with many useful methods for certain programming tasks. Let's look at a few of them.
Say you wanted to print your name three times. This can be done with the in tag, but how do you explicitly tell the in tag to loop three times? Just pass it a sequence with three items:
<dtml-var standard_html_header> <ul> <dtml-in expr="_.range(3)"> <li><dtml-var sequence-item>: My name is Bob.</li> </dtml-in> </ul> <dtml-var standard_html_footer>
_.range(3) Python expression will return a sequence of the
first three integers, 0, 1, and 2. The range function is a
standard Python built-in and many of Python's built-in functions
can be accessed through the _ namespace, including:
range([start,], stop, [step])
stepintegers at a time.
startdefaults to 0 and
stepdefaults to 1. For example:
'_.range(3,9,2)' -- gives '[3,5,7,9]'. 'len(sequence)' -- 'len' returns the size of *sequence* as an integer.
Many of these names come from the Python language, which contains a set
of special functions called
built-ins. The Python philosophy is to
have a small, set number of built-in names. The Zope philosphy can be
thought of as having a large, complex array of built-in names.
The under namespace can also be used to explicitly control variable look up. There is a very common usage of this syntax. You've seen that the in tag defines a number of special variables, like sequence-item and sequence-key that you can use inside a loop to help you display and control it. What if you wanted to use one of these variables inside a Python expression?:
<dtml-var standard_html_header> <h1>The squares of the first three integers:</h1> <ul> <dtml-in expr="_.range(3)"> <li>The square of <dtml-var sequence-item> is: <dtml-var expr="sequence-item * sequence-item"> </li> </dtml-in> </ul> <dtml-var standard_html_footer>
Try this, does it work? No! Why not? The problem lies in this var tag:
<dtml-var expr="sequence-item * sequence-item">
Remember, everything inside a Python expression attribute must be a valid Python expression. In DTML, sequence-item is the name of a variable, but in Python this means "The object sequence minus the object item". This is not what you want.
What you really want is to look up the variable sequence-item. One way to solve this problem is to use the in tag prefix attribute. For example:
<dtml-var standard_html_header> <h1>The squares of the first three integers:</h1> <ul> <dtml-in prefix="loop" expr="_.range(3)"> <li>The square of <dtml-var loop_item> is: <dtml-var expr="loop_item * loop_item"> </li> </dtml-in> </ul> <dtml-var standard_html_footer>
The prefix attribute causes in tag variables to be renamed using the specified prefix and underscores, rather than using "sequence" and dashes. So in this example, "sequence-item" becomes "loop_item". See Appendix A for more information on the prefix attribute.
Another way to look up the variable sequence-item in a DTML expression is to use the getitem utility function to explicitly look up a variable:
The square of <dtml-var sequence-item> is: <dtml-var expr="_.getitem('sequence-item') * _.getitem('sequence-item')">
The getitem function takes the name to look up as its first argument. Now, the DTML Method will correctly display the sum of the first three integers. The getitem method takes an optional second argument which specifies whether or not to render the variable. Recall that rendering a DTML variable means turning it into a string. By default the getitem function does not render a variable.
Here's how to insert a rendered variable named myDoc:
<dtml-var expr="_.getitem('myDoc', 1)">
This example is in some ways rather pointless, since it's the functional equivalent to:
However, suppose you had a form in which a user got to select which document they wanted to see from a list of choices. Suppose the form had an input named selectedDoc which contained the name of the document. You could then display the rendered document like so:
<dtml-var expr="_.getitem(selectedDoc, 1)">
Notice in the above example that selectedDoc is not in quotes. We don't want to insert the variable named selectedDoc we want to insert the variable named by selectedDoc. For example, the value of selectedDoc might be chapterOne. Using indirect variable insertion you can insert the chapterOne variable. This way you can insert a variable whose name you don't know when you are authoring the DTML.
If you a python programmer and you begin using the more complex aspects of DTML, consider doing a lot of your work in Python scripts that you call from DTML. This is explained more in Chapter 10, "Advanced Zope Scripting". Using Python sidesteps many of the issues in DTML.
Zope can be used by many different kinds of users. For example, the Zope site, Zope.org, has over 11,000 community members at the time of this writing. Each member can log into Zope, add objects and news items, and manage their own personal area.
Because DTML is a scripting language, it is very flexible about working with objects and their properties. If there were no security system that constrained DTML then a user could potentially create malicious or privacy-invading DTML code.
DTML is restricted by standard Zope security settings. So if you don't have permission to access an object by going to its URL you also don't have permission to access it via DTML. You can't use DTML to trick the Zope security system.
For example, suppose you have a DTML Document named Diary which is private. Anonymous users can't access your diary via the web. If an anonymous user views DTML that tries to access your diary they will be denied:
DTML verifies that the current user is authorized to access all DTML variables. If the user does not have authorization, than the security system will raise an Unauthorized error and the user will be asked to present more privileged authentication credentials.
In Chapter 7, "Users and Security" you read about security rules for executable content. There are ways to tailor the roles of a DTML Document or Method to allow it to access restricted variables regardless of the viewer's roles.
DTML will not let you gobble up memory or execute infinite loops and recursions the restrictions on looping and memory are pretty tight, which makes DTML not the right language for complex, expensive programming logic. For example, you cannot create huge lists with the _.range utility function. You also have no way to access the filesystem directly in DTML.
Keep in mind however that these safety limits are simple and can be outsmarted by a determined user. It's generally not a good idea to let anyone you don't trust write DTML code on your site.
In the rest of this chapter we'll look at the many advanced DTML tags. These tags are summarized in Appendix A. DTML has a set of built-in tags, as documented in this book, which can be counted on to be present in all Zope installations and perform the most common kinds of things. However, it is also possible to add new tags to a Zope installation. Instructions for doing this are provided at the Zope.org web site, along with an interesting set of contributed DTML tags.
This section covers what could be referred to as Zope miscellaneous tags. These tags don't really fit into any broad categories except for one group of tags, the exception handling DTML tags which are discussed at the end of this chapter.
The var tag can call methods, but it also inserts the return value. Using the call tag you can call methods without inserting their return value into the output. This is useful if you are more interested in the effect of calling a method rather than its return value.
For example, when you want to change the value of a property, animalName, you are more interested in the effect of calling the manage_changeProperties method than the return value the method gives you. Here's an example:
<dtml-if expr="REQUEST.has_key('animalName')"> <dtml-call expr="manage_changeProperties(animalName=REQUEST['animalName'])"> <h1>The property 'animalName' has changed</h1> <dtml-else> <h1>No properties were changed</h1> </dtml-if>
In this example, the page will change a property depending on whether a certain name exists. The result of the manage_changeProperties method is not important and does not need to be shown to the user.
Another common usage of the call tag is calling methods that affect
client behavior, like the
RESPONSE.redirect method. In this
example, you make the client redirect to a different page, to
change the page that gets redirected, change the value for the
"target" variable defined in the let tag:
<dtml-var standard_html_header> <dtml-let target="'http://example.com/new_location.html'"> <h1>This page has moved, you will now be redirected to the correct location. If your browser does not redirect, click <a href="<dtml-var target>"><dtml-var target></a>.</h1> <dtml-call expr="RESPONSE.redirect(target)"> </dtml-let> <dtml-var standard_html_footer>
In short, the call tag works exactly like the var tag with the exception that it doesn't insert the results of calling the variable.
DTML can be documented with comments using the comment tag:
<dtml-var standard_html_header> <dtml-comment> This is a DTML comment and will be removed from the DTML code before it is returned to the client. This is useful for documenting DTML code. Unlike HTML comments, DTML comments are NEVER sent to the client. </dtml-comment> <!-- This is an HTML comment, this is NOT DTML and will be treated as HTML and like any other HTML code will get sent to the client. Although it is customary for an HTML browser to hide these comments from the end user, they still get sent to the client and can be easily seen by 'Viewing the Source' of a document. --> <dtml-var standard_html_footer>
The comment block is removed from DTML output.
In addition to documenting DTML you can use the comment tag to temporarily comment out other DTML tags. Later you can remove the comment tags to re-enable the DTML.
The tree tag lets you easily build dynamic trees in HTML to display hierarchical data. A tree is a graphical representation of data that starts with a "root" object that has objects underneath it often referred to as "branches". Branches can have their own branches, just like a real tree. This concept should be familiar to anyone who has used a file manager program like Microsoft Windows Explorer to navigate a file system. And, in fact, the left hand "navigation" view of the Zope management interface is created using the tree tag.
For example here's a tree that represents a collection of folders and sub-folders.
Figure 7-5 HTML tree generated by the tree tag.
Here's the DTML that generated this tree display:
<dtml-var standard_html_header> <dtml-tree> <dtml-var getId> </dtml-tree> <dtml-var standard_html_footer>
The tree tag queries objects to find their sub-objects and takes care of displaying the results as a tree. The tree tag block works as a template to display nodes of the tree.
Now, since the basic protocol of the web, HTTP, is stateless, you need to somehow remember what state the tree is in every time you look at a page. To do this, Zope stores the state of the tree in a cookie. Because this tree state is stored in a cookie, only one tree can appear on a web page at a time, otherwise they will confusingly use the same cookie.
You can tailor the behavior of the tree tag quite a bit with tree tag attributes and special variables. Here is a sampling of tree tag attributes.
Suppose you want to use the tree tag to create a dynamic site map. You don't want every page to show up in the site map. Let's say that you put a property on folders and documents that you want to show up in the site map.
Let's first define a Script with the id of publicObjects that returns public objects:
## Script (Python) "publicObjects" ## """ Returns sub-folders and DTML documents that have a true 'siteMap' property. """ results= for object in context.objectValues(['Folder', 'DTML Document']): if object.hasProperty('siteMap') and object.siteMap: results.append(object) return results
Now we can create a DTML Method that uses the tree tag and our Scripts to draw a site map:
<dtml-var standard_html_header> <h1>Site Map</h1> <p><a href="&dtml-URL0;?expand_all=1">Expand All</a> | <a href="&dtml-URL0;?collapse_all=1">Collapse All</a> </p> <dtml-tree branches="publicObjects" skip_unauthorized="1"> <a href="&dtml-absolute_url;"><dtml-var title_or_id></a> </dtml-tree> <dtml-var standard_html_footer>
This DTML Method draws a link to all public resources and displays them in a tree. Here's what the resulting site map looks like.
Figure 7-6 Dynamic site map using the tree tag.
For a summary of the tree tag arguments and special variables see Appendix A.
In general DTML creates textual output. You can however, make DTML return other values besides text. Using the return tag you can make a DTML Method return an arbitrary value just like a Python or Perl-based Script.
Here's an example:
<p>This text is ignored.</p> <dtml-return expr="42">
This DTML Method returns the number 42.
Another upshot of using the return tag is that DTML execution will stop after the return tag.
If you find yourself using the return tag, you almost certainly should be using a Script instead. The return tag was developed before Scripts, and is largely useless now that you can easily write scripts in Python and Perl.
The sendmail tag formats and sends a mail messages. You can use the sendmail tag to connect to an existing Mail Host, or you can manually specify your SMTP host.
Here's an example of how to send an email message with the sendmail tag:
<dtml-sendmail> To: <dtml-var recipient> Subject: Make Money Fast!!!! Take advantage of our exciting offer now! Using our exclusive method you can build unimaginable wealth very quickly. Act now! </dtml-sendmail>
Notice that there is an extra blank line separating the mail headers from the body of the message.
A common use of the sendmail tag is to send an email message generated by a feedback form. The sendmail tag can contain any DTML tags you wish, so it's easy to tailor your message with form data.
The mime tag allows you to format data using MIME (Multipurpose Internet Mail Extensions). MIME is an Internet standard for encoding data in email message. Using the mime tag you can use Zope to send emails with attachments.
Suppose you'd like to upload your resume to Zope and then have Zope email this file to a list of potential employers.
Here's the upload form:
<dtml-var standard_html_header> <p>Send you resume to potential employers</p> <form method=post action="sendresume" ENCTYPE="multipart/form-data"> <p>Resume file: <input type="file" name="resume_file"></p> <p>Send to:</p> <p> <input type="checkbox" name="send_to:list" value="email@example.com"> Yahoo<br> <input type="checkbox" name="send_to:list" value="firstname.lastname@example.org"> Microsoft<br> <input type="checkbox" name="send_to:list" value="email@example.com"> McDonalds</p> <input type=submit value="Send Resume"> </form> <dtml-var standard_html_footer>
Create another DTML Method called sendresume to process the form and send the resume file:
<dtml-var standard_html_header> <dtml-if send_to> <dtml-in send_to> <dtml-sendmail smtphost="my.mailserver.com"> To: <dtml-var sequence-item> Subject: Resume <dtml-mime type=text/plain encode=7bit> Hi, please take a look at my resume. <dtml-boundary type=application/octet-stream disposition=attachment encode=base64><dtml-var expr="resume_file.read()"></dtml-mime> </dtml-sendmail> </dtml-in> <p>Your resume was sent.</p> <dtml-else> <p>You didn't select any recipients.</p> </dtml-if> <dtml-var standard_html_footer>
This method iterates over the sendto variable and sends one email for each item.
Notice that there is no blank line between the
To: header and
the starting mime tag. If a blank line is inserted between them
then the message will not be interpreted as a multipart message
by the receiving mail reader.
Also notice that there is no newline between the boundary tag and the var tag, or the end of the var tag and the closing mime tag. This is important, if you break the tags up with newlines then they will be encoded and included in the MIME part, which is probably not what you're after.
As per the MIME spec, mime tags may be nested within mime tags arbitrarily.
The unless tag executes a block of code unless the given condition is true. The unless tag is the opposite of the if tag. The DTML code:
<dtml-if expr="not butter"> I can't believe it's not butter. </dtml-if>
is equivalent to:
<dtml-unless expr="butter"> I can't believe it's not butter. </dtml-unless>
What is the purpose of the unless tag? It is simply a convenience tag. The unless tag is more limited than the if tag, since it cannot contain an else or elif tag.
Like the if tag, calling the unless tag by name does existence checking, so:
<dtml-unless the_easter_bunny> The Easter Bunny does not exist or is not true. </dtml-unless>
Checks for the existence of the_easter_bunny as well as its truth. While this example only checks for the truth of the_easter_bunny:
<dtml-unless expr="the_easter_bunny"> The Easter Bunny is not true. </dtml-unless>
This example will raise an exception if the_easter_bunny does not exist.
Anything that can be done by the unless tag can be done by the if tag. Thus, its use is totally optional and a matter of style.
Often you want to present a large list of information but only show it to the user one screen at a time. For example, if a user queried your database and got 120 results, you will probably only want to show them to the user a small batch, say 10 or 20 results per page. Breaking up large lists into parts is called batching. Batching has a number of benefits.
The in tag provides several variables to facilitate batch processing. Let's look at a complete example that shows how to display 100 items in batches of 10 at a time:
<dtml-var standard_html_header> <dtml-in expr="_.range(100)" size=10 start=query_start> <dtml-if sequence-start> <dtml-if previous-sequence> <a href="<dtml-var URL><dtml-var sequence-query >query_start=<dtml-var previous-sequence-start-number>"> (Previous <dtml-var previous-sequence-size> results) </a> </dtml-if> <h1>These words are displayed at the top of a batch:</h1> <ul> </dtml-if> <li>Iteration number: <dtml-var sequence-item></li> <dtml-if sequence-end> </ul> <h4>These words are displayed at the bottom of a batch.</h4> <dtml-if next-sequence> <a href="<dtml-var URL><dtml-var sequence-query >query_start=<dtml-var next-sequence-start-number>"> (Next <dtml-var next-sequence-size> results) </a> </dtml-if> </dtml-if> </dtml-in> <dtml-var standard_html_footer>
Let's take a look at the DTML to get an idea of what's going on. First we have an in tag that iterates over 100 numbers that are generated by the range utility function. The size attribute tells the in tag to display only 10 items at a time. The start attribute tells the in tag which item number to display first.
Inside the in tag there are two main if tags. The first one
tests special variable
sequence-start. This variable is only
true on the first pass through the in block. So the contents of
this if tag will only be executed once at the beginning of the
loop. The second if tag tests for the special variable
sequence-end. This variable is only true on the last pass
through the in tag. So the second if block will only be
executed once at the end. The paragraph between the if tags is
executed each time through the loop.
Inside each if tag there is another if tag that check for the
variables are true when the current batch has previous or further
batches respectively. In other words
previous-sequence is true
for all batches except the first, and
next-sequence is true for
all batches except the last. So the DTML tests to see if there are
additional batches available, and if so it draws navigation links.
The batch navigation consists of links back to the document with a query_start variable set which indicates where the in tag should start when displaying the batch. To better get a feel for how this works, click the previous and next links a few times and watch how the URLs for the navigation links change.
Finally some statistics about the previous and next batches are
displayed using the
previous-sequence-size special variables. All of this ends up
generating the following HTML code:
<html><head><title>Zope</title></head><body bgcolor="#FFFFFF"> <h1>These words are displayed at the top of a batch:</h1> <ul> <li>Iteration number: 0</li> <li>Iteration number: 1</li> <li>Iteration number: 2</li> <li>Iteration number: 3</li> <li>Iteration number: 4</li> <li>Iteration number: 5</li> <li>Iteration number: 6</li> <li>Iteration number: 7</li> <li>Iteration number: 8</li> <li>Iteration number: 9</li> </ul> <h4>These words are displayed at the bottom of a batch.</h4> <a href="http://pdx:8090/batch?query_start=11"> (Next 10 results) </a> </body></html>
Batch processing can be complex. A good way to work with batches is to use the Searchable Interface object to create a batching search report for you. You can then modify the DTML to fit your needs. This is explained more in Chapter 11, "Searching and Categorizing Content".
Zope has extensive exception handling facilities. You can get access to these facilities with the raise and try tags. For more information on exceptions and how they are raised and handled see a book on Python or you can read the online Python Tutorial.
You can raise exceptions with the raise tag. One reason to raise exceptions is to signal an error. For example you could check for a problem with the if tag, and in case there was something wrong you could report the error with the raise tag.
The raise tag has a type attribute for specifying an error type. The error type is a short descriptive name for the error. In addition, there are some standard error types, like Unauthorized and Redirect that are returned as HTTP errors. Unauthorized errors cause a log-in prompt to be displayed on the user's browser. You can raise HTTP errors to make Zope send an HTTP error. For example:
<dtml-raise type="404">Not Found</dtml-raise>
This raises an HTTP 404 (Not Found) error. Zope responds by sending the HTTP 404 error back to the client's browser.
The raise tag is a block tag. The block enclosed by the raise tag is rendered to create an error message. If the rendered text contains any HTML markup, then Zope will display the text as an error message on the browser, otherwise a generic error message is displayed.
Here is a raise tag example:
<dtml-if expr="balance >= debit_amount"> <dtml-call expr="debitAccount(account, debit_amount)"> <p><dtml-var debit_amount> has been deducted from your account <dtml-var account>.</p> <dtml-else> <dtml-raise type="Insufficient funds"> <p>There is not enough money in account <dtml-account> to cover the requested debit amount.</p> </dtml-raise> </dtml-if>
There is an important side effect to raising an exception, exceptions cause the current transaction to be rolled back. This means any changes made by a web request to be ignored. So in addition to reporting errors, exceptions allow you to back out changes if a problem crops up.
If an exception is raised either manually with the raise tag, or as the result of some error that Zope encounters, you can catch it with the try tag.
Exceptions are unexpected errors that Zope encounters during the execution of a DTML document or method. Once an exception is detected, the normal execution of the DTML stops. Consider the following example:
Cost per unit: <dtml-var expr="_.float(total_cost/total_units)" fmt=dollars-and-cents>
This DTML works fine if total_units is not zero. However, if total_units is zero, a ZeroDivisionError exception is raised indicating an illegal operation. So rather than rendering the DTML, an error message will be returned.
You can use the try tag to handle these kind of problems. With the try tag you can anticipate and handle errors yourself, rather than getting a Zope error message whenever an exception occurs.
The try tag has two functions. First, if an exception is raised, the try tag gains control of execution and handles the exception appropriately, and thus avoids returning a Zope error message. Second, the try tag allows the rendering of any subsequent DTML to continue.
Within the try tag are one or more except tags that identify and handle different exceptions. When an exception is raised, each except tag is checked in turn to see if it matches the exception's type. The first except tag to match handles the exception. If no exceptions are given in an except tag, then the except tag will match all exceptions.
Here's how to use the try tag to avoid errors that could occur in the last example:
<dtml-try> Cost per unit: <dtml-var expr="_.float(total_cost/total_units)" fmt="dollars-and-cents"> <dtml-except ZeroDivisionError> Cost per unit: N/A </dtml-try>
If a ZeroDivisionError is raised, control goes to the except tag, and "Cost per unit: N/A" is rendered. Once the except tag block finishes, execution of DTML continues after the try block.
DTML's except tags work with Python's class-based exceptions. In addition to matching exceptions by name, the except tag will match any subclass of the named exception. For example, if ArithmeticError is named in a except tag, the tag can handle all ArithmeticError subclasses including, ZeroDivisionError. See a Python reference such as the online Python Library Reference for a list of Python exceptions and their subclasses. An except tag can catch multiple exceptions by listing them all in the same tag.
Inside the body of an except tag you can access information about the handled exception through several special variables.
You can use these variables to provide error messages to users or to take different actions such as sending email to the webmaster or logging errors depending on the type of error.
The try tag has an optional else block that is rendered if an exception didn't occur. Here's an example of how to use the else tag within the try tag:
<dtml-try> <dtml-call feedAlligators> <dtml-except NotEnoughFood WrongKindOfFood> <p>Make sure you have enough alligator food first.</p> <dtml-except NotHungry> <p>The alligators aren't hungry yet.</p> <dtml-except> <p>There was some problem trying to feed the alligators.<p> <p>Error type: <dtml-var error_type></p> <p>Error value: <dtml-var error_value></p> <dtml-else> <p>The alligator were successfully fed.</p> </dtml-try>
The first except block to match the type of error raised is rendered. If an except block has no name, then it matches all raised errors. The optional else block is rendered when no exception occurs in the try block. Exceptions in the else block are not handled by the preceding except blocks.
You can also use the try tag in a slightly different way. Instead of handling exceptions, the try tag can be used not to trap exceptions, but to clean up after them.
The finally tag inside the try tag specifies a cleanup block to be rendered even when an exception occurs.
The finally block is only useful if you need to clean up something that will not be cleaned up by the transaction abort code. The finally block will always be called, whether there is an exception or not and whether a return tag is used or not. If you use a return tag in the try block, any output of the finally block is discarded. Here's an example of how you might use the finally tag:
<dtml-call acquireLock> <dtml-try> <dtml-call useLockedResource> <dtml-finally> <!-- this always gets done even if an exception is raised --> <dtml-call releaseLock> </dtml-try>
In this example you first acquire a lock on a resource, then try to perform some action on the locked resource. If an exception is raised, you don't handle it, but you make sure to release the lock before passing control off to an exception handler. If all goes well and no exception is raised, you still release the lock at the end of the try block by executing the finally block.
The try/finally form of the try tag is seldom used in Zope. This kind of complex programming control is often better done in Python or Perl.
DTML provides some very powerful functionality for designing web applications. In this chapter, we looked at the more advanced DTML tags and some of their options. A more complete reference can be found in Appendix A.
The next chapter teaches you how to become a Page Template wizard. While DTML is a powerful tool, Page Templates provide a more elegant solution to HTML generation.