<html> <head> <title>Chapter 6: Creating Basic Zope Applications</title> </head> <body bgcolor="#FFFFFF"> <h1>Chapter 6: Creating Basic Zope Applications</h1> <p> In Chapter 3, "Using Basic Zope Objects" and Chapter 4, "Dynamic Content with DTML" you learned about basic Zope objects and DTML. In this chapter you'll see how you can build simple but powerful web applications using these tools. In later chapters of the book you'll discover more complex objects and more complex DTML. However, the design techniques covered in this chapter are still relevant.</p><p> <em>Note: in chapter 3, "Basic Zope Objects", we explained how Zope Page Templates are new to Zope and should be used for presentation. We have not yet converted this chapter over to use Page Templates instead of DTML. We will be rewriting this chapter soon to reflect new methedologies based on page templates soon.</em></p><h2> Building Applications with Folders</h2> <p> Folders are the "basic building blocks" of Zope applications. Folders allow you to organize your Zope objects, and actively participate in your web applications. Folders are given behavior by adding scripts to them.</p><p> Scripts and folders work together to build simple applications. Folders provide structure for your information and also provide a framework for your site's behavior. Later in this chapter, an example of a simple guest book application based on this design concept is given. A folder is used to hold the methods, scripts and data of the guest book application, the scripts provide behavior that define how the application works, and the methods provide presentation to the application.</p><p> For example, suppose you have an <em>Invoices</em> folder to hold invoices. You could create objects inside that folder named <em>addInvoice</em> and <em>editInvoice</em> to allow you to add and edit invoices. Now your <em>Invoices</em> folder becomes a small application.</p><p> Zope's simple and expressive URLs are used to work with the invoices application. As you've seen, you can display a Zope object by going to its URL in your browser. So for example, the URL <em>http://localhost:8080/Invoices/addInvoice</em> calls the <em>addInvoice</em> object on the <em>Invoices</em> folder. This URL might take you to a screen that lets you add an invoice. Likewise, the URL <em>http://localhost:8080/Invoices/editInvoice?invoice_number=42</em> calls the <em>editInvoice</em> object on the <em>Invoices</em> folder and passes it the argument <em>invoice_number</em> with a value of 42. This URL could allow you to edit invoice number 42.</p><h3> Calling Objects on Folders with URLs </h3> <p> The invoices example demonstrates a powerful Zope feature. You can call an object on a folder by going to a URL that consists of the folder's URL followed by the id of the object. This facility is used throughout Zope and is a very general design pattern. In fact you are not just restricted to calling objects on folders. You'll see later how you can call objects on all kinds of Zope objects using the same URL technique.</p><p> For example suppose you want to call an object named <em>viewFolder</em> on one of your folders. Perhaps you have many different <em>viewFolder</em> objects in different locations. Zope figures out which one you want by first looking in the folder that you are calling the object on. If it can't find the object there it goes up one level and looks in the folder's containing folder. If the object can't be found there it goes up another level. This process continues until Zope finds the object or gets to the root folder. If Zope can't find the object in the root it gives up and raises an exception.</p><p> You'll see this kind of dynamic behavior in many different places in Zope. This technique is called <em>acquisition</em>. A folder is said to <em>acquire</em> a object by searching for the object in its containers.</p><h3> The Special Folder Object <em>index_html</em></h3> <p> As you've seen, folders can acquire all kinds of objects. There is one special object that Zope uses to display a folder. This object is named <em>index_html</em>.</p><p> The <em>index_html</em> object provides a default view of the folder. This is analogous to how an <em>index.html</em> file provides a default view for a directory in Apache and other web servers.</p><p> For example, if you create an <em>index_html</em> object in your <em>Invoices</em> folder and view the folder by clicking the View tab or by visiting the URL <em>http://localhost:8080/Invoices/</em>, Zope will call the <em>index_html</em> object on the <em>Invoices</em> folder.</p><p> A folder can also acquire an <em>index_html</em> object from its parent folders just as it can acquire any object. You can use this behavior to create a default view for a bunch of folders all in one place. If you want a different default view of a given folder, just create a custom <em>index_html</em> object in that folder. This way you can override the <em>index_html</em> object defined higher up.</p><h2> Building the Zope Zoo Website</h2> <p> In this section, you'll create a simple web site for the Zope Zoo. As the Zoo webmaster, it is your job to make the web site easy to use and manage. Here are some things you'll need:<ul> <li>Zoo users must easily move around the site, just as if they were walking through a real Zoo.</li> <li>All of your shared web layout tools, like a Cascading Style Sheet (CSS), must be in one easy to manage location.</li> <li>You must provide a simple file library of various documents that describe the animals.</li> <li>You need a site map so that users can quickly get an idea of the layout of the entire Zoo.</li> <li>A Guest book must be created so that Zoo visitors can give you feedback and comments about your site.</li> <li>A what's new section must be added to the guest book so that you can see any recent comments that have been added.</li> </ul> </p><h3> Navigating the Zoo</h3> <p> In order for your navigation system to work, your site will need some basic structure through which to navigate. Create some folders in your Zope system that represent the structure of your site. Let's use a zoo structure with the following layout, as shown in <a href="#5-1">Figure 5-1</a>.</p><p> <a name="5-1"></a> <img src="Figures/5-1.png" alt="Zoo folder structure."> <p><b>Figure 5-1</b> Zoo folder structure.</p> </p><p> The main structure of the Zope Zoo contains three top level folders, <em>Reptiles</em>, <em>Mammals</em> and <em>Fish</em>. To navigate your site, users should first go to your home page and click on one of the top level folders to enter that particular part of the Zoo. They should also be able to use a very similar interface to keep going deeper into the site; i.e. the snakes section. Also, the user should be able to back out of a section and go up to the parent section.</p><p> You can accomplish this easily with Zope. In your <em>ZopeZoo</em> folder, create a DTML Method called <em>navigation</em>:<pre> <ul> <dtml-in expr="objectValues('Folder')"> <li><a href="&dtml-absolute_url;"><dtml-var title_or_id></a></li><br> </dtml-in> </ul></pre> </p><p> The method you just created shows a list of links to the various sub-sections of the zoo. It's important to notice that this method can work on any zoo folder since it makes no assumptions about the folder. Also since we placed this method in the <em>ZopeZoo</em> folder, all the zoo folders can acquire it.</p><p> Now, you need to incorporate this method into the site. Let's put a reference to it in the <em>standard_html_header</em> object so that the navigation system is available on every page of the site. Your <em>standard_html_header</em> could look like this:<pre> <html> <head><title><dtml-var title></title></head> <body> <dtml-var navigation></pre> </p><p> Next we need to add a front page to the Zoo site and then we can view the site and verify that the navigation works correctly.</p><h3> Adding a Front Page to the Zoo</h3> <p> Now, you need a front page that serves as the welcome screen for Zoo visitors. Let's create a DTML Method in the <em>ZopeZoo</em> folder called <em>index_html</em> with the following content:<pre> <dtml-var standard_html_header> <h1>Welcome to the Zope Zoo</h1> <p>Here you will find all kinds of cool animals. You are in the <b><dtml-var getId></b> section.</p> <dtml-var standard_html_footer></pre> </p><p> Take a look at how your site appears by clicking on the <em>View</em> tab in the root folder, as shown in <a href="#5-2">Figure 5-2</a>.</p><p> <a name="5-2"></a> <img src="Figures/5-2.png" alt="Zope Zoo front page."> <p><b>Figure 5-2</b> Zope Zoo front page.</p> </p><p> Here you start to see how things come together. At the top of your main page you see a list of links to the various subsections. These links are created by the <em>navigation</em> method that is called by the <em>standard_html_header</em> method.</p><p> You can use the navigation links to travel through the various sections of the Zoo. Use this navigation interface to find the reptiles section.</p><p> Zope builds this page to display a folder by looking for the default folder view method ,<em>index_html</em>. It walks up the zoo site folder by folder until it finds the <em>index_html</em> method in the <em>ZopeZoo</em> folder. It then calls this method on the <em>Reptiles</em> folder. The <em>index_html</em> method calls the <em>standard_html_header</em> method which in turn calls the <em>navigation</em> method. Finally, the <em>index_html</em> method displays a welcome message and calls the <em>standard_html_footer</em>.</p><p> What if you want the reptile page to display something besides the welcome message? You can replace the <em>index_html</em> method in the reptile section with a more appropriate display method and still take advantage of the zoo header and footer including navigation.</p><p> In the <em>Reptile</em> folder create a DTML Method named <em>index_html</em>. Give it some content more appropriate to reptiles:<pre> <dtml-var standard_html_header> <h1>The Reptile House</h1> <p>Welcome to the Reptile House.</p> <p>We are open from 6pm to midnight Monday through Friday.</p> <dtml-var standard_html_footer></pre> </p><p> Now take a look at the reptile page by going to the <em>Reptile</em> folder and clicking the View tab.</p><p> Since the <em>index_html</em> method in the <em>Reptile</em> folder includes the standard headers and footers, the reptile page still includes your navigation system.</p><p> Click on the <em>Snakes</em> link on the reptile page to see what the Snakes section looks like. The snakes page looks like the <em>Reptiles</em> page because the <em>Snakes</em> folder acquires its <em>index_html</em> display method from the <em>Reptiles</em> folder.</p><h3> Improving Navigation</h3> <p> The navigation system for the zoo works pretty well, but it has one big problem. Once you go deeper into the site you need to use your browser's <em>back</em> button to go back. There are no navigation links to allow you to navigate up the folder hierarchy. Let's add a navigation link to allow you to go up the hierarchy. Change the <em>navigation</em> method in the root folder:<pre> <a href="..">Return to parent</a><br> <ul> <dtml-in expr="objectValues('Folder')"> <li><a href="&dtml-absolute_url;"><dtml-var title_or_id></a><br></li> </dtml-in> </ul></pre> </p><p> Now browse the Zoo site to see how this new link works, as shown in Figure <a href="#5-3">Figure 5-3</a>.</p><p> <a name="5-3"></a> <img src="Figures/5-3.png" alt="Improved zoo navigation controls."> <p><b>Figure 5-3</b> Improved zoo navigation controls.</p> </p><p> As you can see, the <em>Return to parent</em> link allows you to go back up from a section of the site to its parent. However some problems remain; when you are at the top level of the site you still get a <em>Return to parent</em> link which leads nowhere. Let's fix this by changing the <em>navigation</em> method to hide the parent link when you're in the <em>ZopeZoo</em> folder:<pre> <dtml-if expr="_.len(PARENTS) > 2"> <a href="..">Return to parent</a><br> </dtml-if> <ul> <dtml-in expr="objectValues('Folder')"> <li><a href="&dtml-absolute_url;"><dtml-var title_or_id></a><br></li> </dtml-in> </ul></pre> </p><p> Now the method tests to see if the current object has any parents before it display a link to the parent. <em>PARENTS</em> is a list of the current object's parents, and <em>len</em> is a utility function which returns the length of a list. See Appendix A for more information on DTML utility functions. Now view the site. Notice that now there is no parent link when you're viewing the main zoo page.</p><p> There are still some things that could be improved about the navigation system. For example, it's pretty hard to tell what section of the Zoo you're in. You've changed the reptile section, but the rest of the site all looks pretty much the same with the exception of having different navigation links. It would be nice to have each page tell you what part of the Zoo you're in.</p><p> Let's change the <em>navigation</em> method once again to display where you are:<pre> <dtml-if expr="_.len(PARENTS) > 2"> <h2><dtml-var title_or_id> Section</h2> <a href="..">Return to parent</a><br> </dtml-if> <ul> <dtml-in expr="objectValues('Folder')"> <li><a href="&dtml-absolute_url;"><dtml-var title_or_id></a><br></li> </dtml-in> </ul></pre> </p><p> Now view the site again.</p><p> <a name="5-4"></a> <img src="Figures/5-4.png" alt="Zoo page with section information."> <p><b>Figure 5-4</b> Zoo page with section information.</p> </p><p> As you can see in <a href="#5-4">Figure 5-4</a>, the navigation method now tells you what section you're in along with links to go to different sections of the zoo.</p><h3> Factoring out Style Sheets</h3> <p> Zoo pages are built by collections of methods that operate on folders. For example, the header method calls the navigation method to display navigation links on all pages. In addition to factoring out shared behavior such as navigation controls, you can use different Zope objects to factor out shared content.</p><p> Suppose you'd like to use CSS (<a href="http://www.w3.org/Style/CSS/">Cascading Style Sheets</a> ) to tailor the look and feel of the zoo site. One way to do this would be to include the CSS tags in the <em>standard_html_header</em> method. This way every page of the site would have the CSS information. This is a good way to reuse content, however, this is not a flexible solution since you may want a different look and feel in different parts of your site. Suppose you want the background of the snakes page to be green, while the rest of the site should have a white background. You'd have to override the <em>standard_html_header</em> in the <em>Snakes</em> folder and make it exactly the same as the normal header with the exception of the style information. This is an inflexible solution since you can't vary the CSS information without changing the entire header.</p><p> You can create a more flexible way to define CSS information by factoring it out into a separate object that the header will insert. Create a DTML Document in the <em>ZopeZoo</em> folder named <em>style_sheet</em>. Change the contents of the document to include some style information:<pre> <style type="text/css"> h1{ font-size: 24pt; font-family: sans-serif; } p{ color: #220000; } body{ background: #FFFFDD; } </style></pre> </p><p> This is a CSS style sheet that defines how to display <em>h1</em>, <em>p</em> and <em>body</em> HTML tags. Now let's include this content into our web site by inserting it into the <em>standard_html_header</em> method:<pre> <html> <head> <dtml-var style_sheet> </head> <body> <dtml-var navigation></pre> </p><p> Now, when you look at documents on your site, all of their paragraphs will be dark red, and the headers will be in a sans-serif font.</p><p> To change the style information in a part of the zoo site, just create a new <em>style_sheet</em> document and drop it into a folder. All the pages in that folder and its sub-folders will use the new style sheet.</p><h3> Creating a File Library</h3> <p> File libraries are common on web sites since many sites distribute files of some sort. The old fashioned way to create a file library is to upload your files, then create a web page that contains links to those files. With Zope you can dynamically create links to files. When you upload, change or delete files, the file library's links can change automatically.</p><p> Create a folder in the <em>ZopeZoo</em> folder called <em>Files</em>. This folder contains all of the file you want to distribute to your web visitors.</p><p> In the <em>Files</em> folder create some empty file objects with names like <em>DogGrooming</em> or <em>HomeScienceExperiments</em>, just to give you some sample data to work with. Add some descriptive titles to these files.</p><p> DTML can help you save time maintaining this library. Create an <em>index_html</em> DTML Method in the <em>Files</em> folder to list all the files in the library:<pre> <dtml-var standard_html_header> <h1>File Library</h1> <ul> <dtml-in expr="objectValues('File')"> <li><a href="&dtml-absolute_url;"><dtml-var title_or_id></a></li> </dtml-in> </ul> <dtml-var standard_html_footer> </pre> </p><p> Now view the <em>Files</em> folder. You should see a list of links to the files in the <em>Files</em> folder as shown in <a href="#5-5">Figure 5-5</a>.</p><p> <a name="5-5"></a> <img src="Figures/5-5.png" alt="File library contents page."> <p><b>Figure 5-5</b> File library contents page.</p> </p><p> If you add another file, Zope will dynamically adjust the file library page. You may also want to try changing the titles of the files, uploading new files, or deleting some of the files.</p><p> The file library as it stands is functional but Spartan. The library doesn't let you know when a file was created, and it doesn't let you sort the files in any way. Let's make the library a little fancier.</p><p> Most Zope objects have a <em>bobobase_modification_time</em> method that returns the time the object was last modified. We can use this method in the file library's <em>index_html</em> method:<pre> <dtml-var standard_html_header> <h1>File Library</h1> <table> <tr> <th>File</th> <th>Last Modified</th> </tr> <dtml-in expr="objectValues('File')"> <tr> <td><a href="&dtml-absolute_url;"><dtml-var title_or_id></a></td> <td><dtml-var bobobase_modification_time fmt="aCommon"></td> </tr> </dtml-in> </table> <dtml-var standard_html_footer> </pre> </p><p> The new file library method uses an HTML table to display the files and their modification times.</p><p> Finally let's add the ability to sort this list by file name or by modification date. Change the <em>index_html</em> method again:<pre> <dtml-var standard_html_header> <h1>File Library</h1> <table> <tr> <th><a href="&dtml-URL0;?sort=name">File</a></th> <th><a href="&dtml-URL0;?sort=date">Last Modified</a></th> </tr> <dtml-if expr="_.has_key('sort') and sort=='date'"> <dtml-in expr="objectValues('File')" sort="bobobase_modification_time" reverse> <tr> <td><a href="&dtml-absolute_url;"><dtml-var title_or_id></a></td> <td><dtml-var bobobase_modification_time fmt="aCommon"><td> </tr> </dtml-in> <dtml-else> <dtml-in expr="objectValues('File')" sort="id"> <tr> <td><a href="&dtml-absolute_url;"><dtml-var title_or_id></a></td> <td><dtml-var bobobase_modification_time fmt="aCommon"><td> </tr> </dtml-in> </dtml-if> </table> <dtml-var standard_html_footer> </pre> </p><p> Now view the file library and click on the <em>File</em> and <em>Last Modified</em> links to sort the files. This method works with two sorting loops. One uses the <em>in</em> tag to sort on an object's <em>id</em>. The other does a reverse sort on an object's <em>bobobase_modification_time</em> method. The <em>index_html</em> method decides which loop to use by looking for the <em>sort</em> variable. If there is a <em>sort</em> variable and if it has a value of <em>date</em> then the files are sorted by modification time. Otherwise the files are sorted by id.</p><h3> Building a Guest Book</h3> <p> A guest book is a common and useful web application that allows visitors to your site to leave messages. Figure <a href="#5-6">Figure 5-6</a> shows what the guest book you're going to write looks like.</p><p> <a name="5-6"></a> <img src="Figures/5-6.png" alt="Zoo guest book."> <p><b>Figure 5-6</b> Zoo guest book.</p> </p><p> Start by creating a folder called <em>GuestBook</em> in the root folder. Give this folder the title <code>The Zope Zoo Guest Book</code>. The <em>GuestBook</em> folder will hold the guest book entries and methods to view and add entries. The folder will hold everything the guest book needs. After the guest book is done you will be able to copy and paste it elsewhere in your site to create new guest books.</p><p> You can use Zope to create a guest book several ways, but for this example, you'll use one of the simplest. The <em>GuestBook</em> folder will hold a bunch of DTML Documents, one document for each guest book entry. When a new entry is added to the guest book, a new document is created in the <em>GuestBook</em> folder. To delete an unwanted entry, just go into the <em>GuestBook</em> folder and delete the unwanted document using the management interface.</p><p> Let's create a method that displays all of the entries. Call this method <em>index_html</em> so that it is the default view of the <em>GuestBook</em> folder:<pre> <dtml-var standard_html_header> <h2><dtml-var title_or_id></h2> <!-- Provide a link to add a new entry, this link goes to the addEntryForm method --> <p> <a href="addEntryForm">Sign the guest book</a> </p> <!-- Iterate over each DTML Document in the folder starting with the newest documents first. --> <dtml-in expr="objectValues('DTML Document')" sort="bobobase_modification_time" reverse> <!-- Display the date, author and contents of each document --> <p> <b>On <dtml-var bobobase_modification_time fmt="aCommon">, <dtml-var guest_name html_quote null="Anonymous"> said:</b><br> <dtml-var sequence-item html_quote newline_to_br> <!-- Make sure we use html_quote so the users can't sneak any HTML onto our page --> </p> </dtml-in> <dtml-var standard_html_footer></pre> </p><p> This method loops over all the documents in the folder and displays each one. Notice that this method assumes that each document will have a <em>guest_name</em> property. If that property doesn't exist or is empty, then Zope will use <em>Anonymous</em> as the guest name. When you create a entry document you'll have to make sure to set this property.</p><p> Next, let's create a form that your site visitors will use to add new guest book entries. In the <em>index_html</em> method above we already created a link to this form. In your <em>GuestBook</em> folder create a new DTML Method named <em>addEntryForm</em>:<pre> <dtml-var standard_html_header> <p>Type in your name and your comments and we'll add it to the guest book.</p> <form action="addEntryAction" method="POST"> <p> Your name: <input type="text" name="guest_name" value="Anonymous"> </p> <p> Your comments: <br> <textarea name="comments" rows="10" cols="60"></textarea> </p> <p> <input type="submit" value="Send Comments"> </p> </form> <dtml-var standard_html_footer></pre> </p><p> Now when you click on the <em>Sign Guest Book</em> link on the guest book page you'll see a form allowing you to type in your comments. This form collects the user's name and comments and submits this information to a method named <em>addEntryAction</em>.</p><p> Now create an <em>addEntryAction</em> DTML Method in the <em>GuestBook</em> folder to handle the form. This form will create a new entry document and return a confirmation message:<pre> <dtml-var standard_html_header> <dtml-call expr="addEntry(guest_name, comments)"> <h1>Thanks for signing our guest book!</h1> <p><a href="<dtml-var URL1>">Return</a> to the guest book.</p> <dtml-var standard_html_footer></pre> </p><p> This method creates a new entry by calling the <em>addEntry</em> method and returns a message letting the user know that their entry has been added.</p><p> The last remaining piece of the puzzle is to write the script that will create a document and sets its contents and properties. We'll do this in Python since it is much clearer than doing it in DTML. Create a Python-based Script in the <em>GuestBook</em> folder called <em>addEntry</em> with parameters <em>guest_name</em> and <em>comments</em>:<pre> ## Script (Python) "addEntry" ##parameters=guest_name, comments ## """ Create a guest book entry. """ # create a unique document id id='entry_%d' % len(context.objectIds()) # create the document context.manage_addProduct['OFSP'].manage_addDTMLDocument(id, title="", file=comments) # add a guest_name string property doc=getattr(context, id) doc.manage_addProperty('guest_name', guest_name, 'string')</pre> </p><p> This script uses Zope API calls to create a DTML Document and to create a property on that document. This script performs the same sort of actions in a script that you could do manually; it creates a document, edits it and sets a property.</p><p> The guest book is now almost finished. To use the simple guest book, just visit <em>http://localhost:8080/GuestBook/</em>.</p><p> One final thing is needed to make the guest book complete. More than likely your security policy will not allow anonymous site visitors to create documents. However the guest book application should be able to be used by anonymous visitors. In Chapter 7, User and Security, we'll explore this scenario more fully. The solution is to grant special permission to the <em>addEntry</em> method to allow it to do its work of creating a document. You can do this by setting the <em>Proxy role</em> of the method to <em>Manager</em>. This means that when the method runs it will work as though it was run by a manager regardless of who is actually running the method. To change the proxy roles go to the <em>Proxy</em> view of the <em>addEntry</em> method, as shown in <a href="#5-7">Figure 5-7</a>.</p><p> <a name="5-7"></a> <img src="Figures/5-7.png" alt="Setting proxy roles for the addEntry method."> <p><b>Figure 5-7</b> Setting proxy roles for the addEntry method.</p> </p><p> Now select <em>Manager</em> from the list of proxy roles and click <em>Change</em>.</p><p> Congratulations, you've just completed a functional web application. The guest book is complete and can be copied to different sites if you want.</p><h3> Extending the Guest Book to Generate XML</h3> <p> All Zope objects can create XML. It's fairly easy to create XML with DTML. XML is just a way of describing information. The power of XML is that it lets you easily exchange information across the network. Here's a simple way that you could represent your guest book in XML:<pre> <guestbook> <entry> <comments>My comments</comments> </entry> <entry> <comments>I like your web page</comments> </entry> <entry> <comments>Please no blink tags</comments> </entry> </guestbook></pre> </p><p> This XML document may not be that complex but it's easy to generate. Create a DTML Method named "entries.xml" in your guest book folder with the following contents:<pre> <guestbook> <dtml-in expr="objectValues('DTML Document')"> <entry> <comments><dtml-var document_src html_quote></comments> </entry> </dtml-in> </guestbook></pre> </p><p> As you can see, DTML is equally adept at creating XML as it is at creating HTML. Simply embed DTML tags among XML tags and you're set. The only tricky thing that you may wish to do is to set the content-type of the response to <em>text/xml</em>, which can be done with this DTML code:<pre> <dtml-call expr="RESPONSE.setHeader('content-type', 'text/xml')"></pre> </p><p> The whole point of generating XML is producing data in a format that can be understood by other systems. Therefore you will probably want to create XML in an existing format understood by the systems you want to communicate with. In the case of the guest book a reasonable format may be the RSS (Rich Site Summary) XML format. RSS is a format developed by Netscape for its <em>my.netscape.com</em> site, which has since gained popularity among other web logs and news sites. The Zope.org web site uses DTML to build a dynamic RSS document.</p><p> Congratulations! You've XML-enabled your guest book in just a couple minutes. Pat yourself on the back. If you want extra credit, research RSS enough to figure out how to change <em>entries.xml</em> to generate RSS.</p><h2> The Next Step</h2> <p> This chapter shows how simple web applications can be made. Zope has many more features in addition to these, but these simple examples should get you started on create well managed, complex web sites.</p><p> In the next chapter, we'll see how the Zope security system lets Zope work with many different users at the same time and allows them to collaborate together on the same projects.</p></body> </html>