Approaching the Zope Product API

    Extending Zope is quite simple compared to many other
    environments. Since Zope is based on the clear, elegant Python
    scripting language, Zope extensions are quick to develop and clear
    to maintain.

    However, since they can leverage the full power of the Zope
    framework, writing a Zope Product is more complicated than the
    extremely simple extensibility of External Methods. Fortunately
    the step up in complexity is much less than the dramatic increase
    in functionality.
    
    In this tutorial we are going to build a poll Product. 
    
About this tutorial

    Version 0.2
    
    Changes
    
        0.2 -- Cleaned up some language. Added discussion of
        requirements analysis. Added HTML version of the tutorial.
    
        0.1 -- Initial version, there may be some inaccuracies.
    
    Send your feedback about this tutorial to amos@digicool.com

    The included Python and DTML files show some of the steps in the
    development of a sample poll Product. The 'Poll.tar.gz' file is
    the completed Product. To install the completed Product ungzip and
    untar it inside your Zope directory. Then shutdown and restart
    Zope to start using the sample poll Product.
    
What you need to know before you begin

    Before you read this tutorial you should have a basic
    understanding of how Zope publishes Python objects. You should, of
    course, know Python reasonably well. If you've used object
    publishing before, perfect. If not, you might want to read an
    introduction to object publishing before you begin, because the
    Zope Product API is just a special case of object publishing
    programming. Finally you should have a reasonable familiarity with
    Zope itself. You should understand how to do things like create
    Folders and Documents, and use DTML to call methods, and set
    properties. The Zope Manager's Guide is a good place to learn how
    to use Zope.
    
Defining and analyzing your task

    Writing a Zope Product, creating a website, building a database,
    all these activities are tasks which are done to solve specific
    problems. If you want to do a good job building a solution, you
    must understand your problem first. In the next section we'll talk
    about different approaches to solving problems in Zope. However,
    its a good idea to start with a description of the problem.
    
    Here's a short problem description and analysis for our poll
    project.
    
        Requirements
        
            Customers manage their areas of a Zope site and want to
            provide information services. In this case, a customer
            wants to collect feedback from users regarding a certain
            topic. That is, they want to conduct a poll.

            A poll contains ballot with question with one or more
            answers. The ballot is the HTML presentation of the
            question, answers, and control elements. Users can select
            only one answer for a poll. Users can either vote in the
            poll or view the current results or both. The results at
            any time are displayed by default as text using an
            attractive out-of-the-box format. The appearance of the
            ballot should be customizable, and the ballot should be
            able to be embedded in other documents.

            Finally, a poll should allow voting by only a select group
            of users that have the correct credentials. That is,
            should be able to be private.

        Analysis
       
            The Poll appears to have actors of Customer, User,
            Privileged User. The Customer actor is someone able to add
            and manage Zope content. The User actor is someone with
            the Anonymous role that interacts with Zope. A Privileged
            User appears to be a non-Anonymous Zope User. This means
            that the Poll should expose some permissions for
            operations such as voting and presenting results, allowing
            the permissions to include various roles.
            
            The Poll has a number of crisp abstractions. A Poll
            represents all the state and behavior needed to accomplish
            the task. A Ballot appears to be Document with some DTML
            scripting in it. A Question might not be a separate
            object. Answers might be separate objects that leverage
            the normal Zope Folder interface to manage them.
 
Is the Product API right for the project?

    After you have described and thought about your pojects, the next
    step is to evaluate whether your idea would work well as a
    Product, or if it could be better implemented some other way. In
    our case we would like to build a poll product like you see on
    Slashdot and many other sites.
    
    We could probably build it with SQL Methods and DTML without too
    much difficulty. Though this would require mucking around with the
    RDBMS whenever you wanted to create a new poll or modify an
    existing poll.
    
    We could probably build a simple polling facility just using DTML,
    Documents and maybe properties. Again, this approach would
    probably involve more administration hassles than we would like.
    
    Using a Zope Product we could provide easy manageabiliy to Zope
    users who want to create their own polls. Using a Product also
    gives us fine control over access controls, and gives us room to
    refine and develop our poll over a period of time.
    
    So we have a marginal case for turning our idea into a Product.
    OK, good enough let's get going.
    
Getting started in plain old Python
    
    Now that we have decided to harness the full power of the Zope
    Framework to create a reusable Zope Product, the first thing to do
    is to write a prototype in Python. This prototype will try to
    capture the essential algorithms of our product. It will also
    allow us to test the guts of the product and make sure they are
    sound before we start confusing ourselves with exotic Zopisms.
    
    Here's a first cut at what a poll class might look like::

        class Poll:
            "A multiple choice poll."

            def __init__(self,question,choices):
                self.question=question
                self.choices=choices
                self.votes={}
                for choice in range(len(choices)):
                    self.votes[choice]=0

            def vote(self,choice):
                "vote for a choice"
                self.votes[choice]=self.votes[choice]+1

            def total_votes(self):
                "total number of votes cast"
                total=0
                for v in self.votes.values():
                    total=total+v
                return total

            def votes_for(self,choice):
                "number of votes cast for a given choice"
                return self.votes[choice]   
    
    Basically our 'Poll' object keeps track of what is going on in a
    dictionary that maps choices to numbers of votes. The 'vote'
    method actually casts a vote. 'votes' is a dictionary keyed the
    the choice index which keeps track of the number of votes for each
    choice. The 'total_votes' and 'votes_for' methods just report
    statistics about the poll.
    
    Of note is the fact that we haven't made any effort to keep people
    from voting more than once. There are a number of ways we could
    handle this, for example we could somehow record the identity of
    everyone who voted. Then when someone tries to vote we could look
    them up in our list of people who have voted. This approach has
    some problems since it is often difficult to reasonably identify
    someone. Let's leave this problem unresolved for now.
    
Testing our initial implementation in Python

    An original purpose of Zope was to hide the publishing details and
    allow Python developers to think in terms of Python. Thus, at this
    stage let's go into Python and test what we have.

    OK, so does our first attempt at a poll work? Let's exercise it a
    little::
    
        def test():
            p=Poll("What's for breakfast?",["spam","eggs","toast"])
            p.vote(0) # vote for spam
            p.vote(2) # vote for toast
            p.vote(0) # vote for spam
            print p.total_votes() #returns 3
            print p.votes_for(0)  #returns 2
    
    You can find this code in the included 'Poll1.py' example file.
    Run it and see for yourself that it works.
    
    At first glance it seems pretty reasonable. Though if you try to
    vote for a non-existent choice you get an 'KeyError'. Also there's
    nothing forcing you to initialize your poll class with reasonable
    arguments. Well that's OK for now. But we may want to add some
    error handling later. Zope provides a number of error handling
    features, like automated user notification of errors, and
    transaction handling to abort transactions when uncaught
    exceptions are raised.
    
Adding a user interface

    OK, now we have a semi-functional poll, let's add a user interface
    so that people can actually use it through the web. The normal
    Zopish way to do this is to use DocumentTemplate. A product can
    choose to use a full fledged Document object, or a plain old
    DocumentTemplate object. Let's start simple by defining a
    'DocumentTemplate.HTMLFile' to display the poll. Let's create a
    file called 'poll2.dtml'::
    
        <p><!--#var question--></p>
        <form action="vote">
        <!--#in choices-->
        <input type="radio" name="choice:int"
        value="<!--#var sequence-index-->">
        <!--#var sequence-item--><br>
        <!--#/in-->
        <input type="submit" value="Vote">
        </form>
        
    Then create a class attribute for template::
    
        index_html=DocumentTemplate.HTMLFile("poll2.dtml")
        
    OK, looking petty good. Now let's test it::
    
        def test2():
            p=Poll("What's for breakfast?",["spam","eggs","toast"])
            print p.index_html(p)
    
    You can see the complete program in the 'Poll2.py' example file.
    Run it and see for yourself that it works.
    
    What this test function does is create a 'Poll' instance and
    display it using its template. When Zope publishes an object it
    will publish an object's 'index_html' method if no other method is
    specified. So by naming our template 'index_html' we ensure that
    it is the default view of our object. 
    
    Additionally, when Zope publishes a template it will pass the
    template's object and the REQUEST as arguments to the template. So
    by calling our poll's template with itself as an argument we are
    emulating to a certain extent what Zope will do when it publishes
    our object.
    
    If these methods seem strange to you, go back read up on Zope's
    object publishing system. You may also want to read some
    introductions to the Zope ORB and its conventions for object
    publishing. The basic idea of publishing an object is that a
    template is used to provide a skeleton of how an object should be
    displayed. Then when an object is published, its data is used to
    fill in the template. The Document Template User's Guide gives
    more details about templates.
    
    Here's what we get when our Poll object is displayed::
    
        <p>What's for breakfast?</p>
        <form action="vote">
        <input type="radio" name="choice:int" value="0"> spam<br>
        <input type="radio" name="choice:int" value="1"> eggs<br>
        <input type="radio" name="choice:int" value="2"> toast<br>
        <input type="submit" value="Vote">
        </form>
        
    This seems more of less right. We have the question followed by a
    form which allows you to select one of the choices. The form calls
    the poll's 'vote' method, with the 'choice' argument set. So far
    so good.
    
You've got a publishable object

    At this point you've developed a pretty simple publishable Python
    object. If you are not interested in the Zope framework, you could
    at this point start publishing your poll objects with the Zope ORB
    and be done with it. Of course our poll still doesn't do a lot of
    things like return a response when you vote, or handle
    persistence. However, we do in fact have a publishable Zope
    object--pretty easy isn't it.
    
    Now we can start publishing our poll object. If you're an
    experienced Zope user, this is old news to you. You may wish to
    skip ahead to the next section. If not welcome to publishable
    objects.
    
    The core element in Zope is the object publishing ORB. The
    publisher lives in the 'ZPublisher.Publisher' package. Zope runs
    the publisher for you over the web converting URLs into object
    calls. After our poll object is plugged into Zope it will be
    published normally, by being referenced in a Zope URL. This is
    exactly how other Zope objects like Folders are published. 
    
Testing the poll with the debugger

    You can run the Zope publisher from the commandline to test
    objects before they are integrated into Zope.
    
    The Zope debugger (formerly 'bobo.py') is located in the
    'ZPublisher.Test' module. It allows you to activate the publishing
    ORB from the command line. To test our 'Poll' class we need to
    create an instance of that class and publish it. Create a new file
    'poll_test.py'::
    
        import Poll2
        p=Poll2.Poll("What's for breakfast?",["spam","eggs","toast"])
    
    You now should have three example files, 'Poll1.py' our original
    class, 'Poll2.py' which contains the latest 'Poll' class
    definition, and 'pole_test.py' which imports 'Poll' and creates a
    'Poll' instance. 
    
    Now you can publish the poll object by issuing this command from
    within the directory where your two poll files are located.::
    
        <Zope directory>/bin/python <Zope
        directory>/lib/python/Zpublisher/Test.py poll_test p
        
    Note, this command should be one complete line.
    
    '<Zope directory>' refers to your Zope directory, for example,
    '/usr/local/Zope'. This command will fire up Zope's copy of Python
    and run the 'ZPublisher.Test' module with 'poll_test' as the
    published module and 'p' as the published object. (If you are
    using the source rather than the binary distribution of Zope you
    should use your own copy of Python, rather than Zope's to run the
    debugger.)
    
    Here's what you should get back when you run the debugger::
    
        Status: 200 OK
        Content-Length: 332
        Content-Type: text/html

        <html><head>    <base href="http://127.0.0.1/poll_test/p/">
        </head>
        <p>What's for breakfast?</p>
        <form action="vote">
        <input type="radio" name="choice:int" value="0"> spam<br>
        <input type="radio" name="choice:int" value="1"> eggs<br>
        <input type="radio" name="choice:int" value="2"> toast<br>
        <input type="submit" value="Vote">
        </form>
        
    This is letting you know that ZPublisher will successfully publish
    your poll object. In fact, this is exactly what Zope will send
    back to your browser if you were able to publish your poll object
    through the web right now.
    
    We can see that we're getting a status of '200' which is right
    for most conditions. Also the HTML looks pretty much like what we
    defined in our template. The main difference is that Zope has
    inserted a base href for us. 
    
    So, now let's test what will happen when you vote by submitting
    the form. We can do this by telling the debugger to publish the
    voting method like so::
    
        <Zope directory>/bin/python <Zope
        directory>/lib/python/Zpublisher/Test.py poll_test
        p/vote?choice:int=0
    
    Note, this command should be one complete line.
    
    This tells the debugger to publish the poll's 'vote' method with
    an argument of 'choice=0'. So we are simulating what would happen
    if the poll was published on the web and we clicked the spam radio
    button on the poll's form and clicked the submit button.
    
    This is the result we get::
    
        Status: 204 No Content
    
    Which is understandable considering that the 'vote' method doesn't
    return anything.

    So far so good. Our work with the debugger is done for now, but we
    may have good reason to come back to it later. The debugger can do
    some pretty powerful things including allow you to run the
    publishing process through the standard Python debugger which can
    come in pretty handy. Also, right now we are debugging an object
    which is not installed in Zope. We can however, debug objects
    inside Zope. The only difference is to tell the debugger that we
    want to operate on object inside Zope's 'Main' module, rather our
    own module.

How are you doing?

    If you are lost at this point you should read up on the Zope ORB
    and come back to this tutorial later when you have a better
    feeling for how Zope object publishing works.
    
    If you're doing fine, get yourself a snack and then let's
    continue.

Going from publishable object to Zope Product

    There is a long way between having a publishable object and having
    a Zope product. You have to attend to a number of things in order
    to turn your publishable object into a functioning Product:
    
        Create a Product package -- All Zope products need to be
        structured as Python packages. There are a number of details
        you will need to attend to in order to make your package
        fulfill all the Product requirements. Most of these
        requirements are met by including various attributes in your
        package's '__init__.py' file.
        
        Adhere to the Product API -- The Zope Framework gives a
        managed environment providing a number of facilities for
        Products. The more Zope conventions you adhere to, the more
        manageable your Product will be in Zope. For example Zope
        requires specific handling of object creation, object access
        controls, object meta data, etcetera. You will fulfill these
        requirements by inheriting from Zope Product classes,
        implementing appropriate methods and setting appropriate class
        attributes. 
    
Product packaging

    A Zope product has many attributes, but one of the most essential,
    is that it lives as a Python package inside the
    '/lib/python/Products' directory. Zope interprets all packages in
    this directory as Products, and tries to install them at startup.
    
    To create a package you need to create a directory inside the
    'lib/python/Products' directory to hold your files. So we create a
    directory called 'Poll'. Inside our package directory we need at
    least two files: an '__init__.py' file which lets Python know that
    our directory is a package, and additional Python files. In our
    case we will probably only have two Python files, '__init__.py',
    and 'Poll.py'. 
    
    Our package directory will hold other files too, for example, DTML
    files, and an icon GIF file. You may want to include other files
    in your package, too such as README files.
    
    You also may choose to locate some support Python files in the
    shared package. The shared package is a central repository of
    utility modules located in 'lib/python/Shared'. To use the shared
    package create a sub-package inside it with the name of your
    organization. This measure is to help minimize the risk of package
    name collisions. So for example you could create a package
    'KingSuperInc' inside the shared package. Then you could import
    that package like so::
    
        import Shared.KingSuperInc
        
    This way you can access to your shared package from within your
    product Python files. You may even want to create sub-packages
    inside your shared package.
        
Beefing up the 'Poll' class with inheritance

    Now that we've got a basic publishable poll class, we're ready to
    start dressing up our poll in Zope finery. Zope can provide our
    classes with many services but let's start with the basics. One of
    the most common ways to give your Zope product abilities is to use
    standard Zope mix-in classes. Here's how we'll define our class
    for starters::
    
        class Poll(
            Acquisition.Implicit,
            Persistent,
            AccessControl.Role.RoleManager
            OFS.SimpleItem.Item,
            ):
            "A Poll product"
            ...
        
    This is a lot of inheritance. Let's look at what we're buying. 
    
    'Acquisiton.Implicit' provides our poll with the ability to
    acquire attributes from it container objects. This probably won't
    be vitally important to our poll object, but it might come in
    handy. More importantly, acquisition is central to the Zope way.
    Even if we don't plan on using acquisition too much, we shouldn't
    assume that attributes of the poll object won't need to use it.
    This is less important for non-Folderish objects, but take my word
    for it--if you're writing a product, inherit from
    'Acquisition.Implicit'.
    
    'Persistent' gives your Product the ability to have its state
    transparently stored and retrieved in Zope's object database. By
    inheriting from the class your Product will also participate in
    transactions, and all the other benefits of Zope's storage system.
    Products must inherit from this class. Using persistence requires
    a small amount of care and discipline. Read up on Bobo POS to find
    out more.
    
    'AccessControl.Role.RoleManager' allows your class to use Zope's
    permissions machinery. By inheriting from 'RoleManager' we allow
    our poll to be managed normally. For example, we will be able to
    have a 'Security' tab in our poll management screen to set and
    modify permissions.
    
    'OFS.SimpleItem.Item' is a basic Zope base class for non-Folderish
    Products. This class gives your Product the ability to be managed
    normally via the Zope management interface. By inheriting from the
    class your Product gains the ability to be cut and pasted, and to
    work with management tabs. It also gives you some standard methods
    including 'manage' which is the standard management URL for Zope
    products. You also get the 'title_or_id', 'title_and_id', and
    'this' DTML utility methods. Finally this class gives your Product
    basic tree tag support.
    
    This collection of base classes provides any simple Zope Product
    with a good place to start. You may want to add to or change from
    this list of base classes later when you have a better
    understanding of Zope's internals. You may also want to override
    some of the methods of these base classes as you gain more
    knowledge.

How Product creation works

    Now that our poll class is starting to get closer to a working
    Product, we need to add some methods in order to allow Zope to
    manage it as a Product. By inheriting from 'OFS.SimpleItem.Item'
    we gain a couple management methods, notably 'manage', however,
    will still need to provide other methods.
    
    One of the defining characteristics of Zope Products are that they
    can be added to Zope Folders. To allow your Product to be created
    in this way you need to provide some management methods.
    
    The normal way to accomplish Product creation is to provide two
    methods for each product. The first method displays a Product
    creation form to collect pertinent data such as the Product 'id',
    'title', and various parameters. The second method is called by
    the creation form and it actually creates the Product object and
    installs it in its parent Folder.
    
    If you are paying close attention, you may notice that these
    methods can't be methods of the Product's class, since they are
    needed before the Product object is created. In fact, these two
    creation methods need to be methods of the Zope Folder class.
    
    To facilitate the Product creation process, Zope installs special
    Product creation methods in the Zope Folder class when it starts
    up. So to allow your Product to be created in Zope, you will have
    to write Folder methods.

Product creation form
    
    OK, so how will we handle actually creating poll objects and
    installing them in Folders? It's relatively simple. Let's start
    with the poll creation form defined in 'pollAdd.dtml'::

        <html>
          <head><title>Add Poll</title></head>
          <body bgcolor="#FFFFFF">

            <h2>Add Poll</h2>

            <form action="manage_addPoll" method="POST">
              <table cellspacing="2">

            <tr>
              <th align="LEFT" valign="TOP">Id</th>
              <td align="LEFT" valign="TOP">
                    <input type="TEXT" name="id" size="50">
                  </td>
            </tr>

            <tr>
              <th align="LEFT" valign="TOP"><em>Title</em></th>
              <td align="LEFT" valign="TOP">
                    <input type="TEXT" name="title" size="50">
                  </td>
            </tr>

            <tr>
              <th align="LEFT" valign="TOP">Poll Question</th>
              <td align="LEFT" valign="TOP">
                    <input type="TEXT" name="question" size="50">
                  </td>
            </tr>

            <tr>
              <th align="LEFT" valign="TOP">Poll Answers<br>(one per
              line)</th>
              <td align="LEFT" valign="TOP">
                    <textarea name="choices:lines" cols="50"
                    rows="10"></textarea>
                  </td>
            </tr>

            <tr>
              <td></td>
              <td><br><input type="SUBMIT" value="Add"></td>
            </tr>

            </table>
            </form>
          </body>
        </html>

    What does this form do? It collects information needed to create a
    poll. We recognize the 'question' and 'answers' parameters, but
    what are the 'id' and 'title' for?
    
    Zope Products are connected to each other in an object hierarchy.
    Each Folder has a number of sub-objects which are bound to its
    attributes. So for example a Folder might contain a Document with
    an id 'index_html'. This means that the Folder's 'index_html'
    attribute is bound to the folder, and that the Document's 'id'
    attribute is 'index_html'. It's pretty simple actually. 
    
    Another important thing about an object's 'id' is that its 'id' is
    its web address. For example, if our example Folder has a URL of
    '/myFolder' then, the URL of the 'index_html' Document inside it
    would be '/myFolder/index_html'. In fact all Zope URLs consist of
    concatenations of Zope object ids.
    
    An object's 'title' is an optional string property which is used
    to give it more description than is possible with an 'id'.  
    
    There are a few more interesting things about our poll creation
    form. If you look closely you will notice that the name of the
    answers textarea is 'choices:lines' this is a specially encoded
    name which tells the Zope ORB to convert the contents of to a list
    of strings rather than one large string with line breaks. To find
    out more about form data marshalling and coercion, see the Zope
    ORB documentation.
    
    Finally we note that the action of the poll creation form is
    'manage_addPoll'. This is the name of the poll installation method
    which we will describe next.
    
Product installation method

    The job of the installation method is to create an instance of the
    Product and install it in the parent Folder. As we mentioned
    before, this method will be installed in the Folder class by Zope.
    
    Here's how we define the product installation method for our
    poll::

        def addPoll(self,id,title,question,answers,REQUEST=None):
            """Create a poll and install it in its parent Folder.

            The argument 'self' will be bound to the parent Folder.
            """
            poll=Poll(id, title, question, answers)
            self._setObject(id, poll)
            if REQUEST is not None:
                return self.manage_main(self,REQUEST)

    This method creates a poll and then installs it with the Folder's
    '_setObject' method. The reason it tests for the REQUEST is allow
    the method to forgo returning a management screen if it is not
    called from the web.
    
    Next we will see how we need to specially indicate the object
    creation form and the object installation method in our Product's
    package so that they are recognized by Zope.

Making creation methods available to Zope

    Now that we have a creation form and an installation method
    defined in 'Poll.py' it's time to add them to '__init__.py' so
    that Zope can easily find them when it inspects our package.
    
    You must define these methods in a special 'methods' dictionary in
    your Product's '__init__.py' file. Here's how we do this for our
    poll Product::

        methods={
            'manage_addPollForm': Poll.addPollForm,
            'manage_addPoll': Poll.addPoll,
        }
    
    So what we've done is let Zope know about two special methods of
    our Poll module, 'addPollForm' which is the creation form, and
    'addPoll' which is the installation method. By listing them in the
    'methods' dictionary, we are telling Zope to install them as
    methods of normal Zope Folders at startup time. These methods will
    be by bound to Folders with the names we give them in the
    'methods' dictionary. So, for example, 'Folder.manage_addPollForm'
    is 'Poll.addPollForm'. You may wonder why we rename these methods.
    By convention creation methods (and other management related
    methods) begin with the prefix 'manage_'. 
    
    By now we have done much of the work of making our poll class
    available to Zope as a Product.

Are you still with us?

    Right now the process of turning your simple publishable object
    into a full fledged Zope Product is probably starting to seem a
    bit tedious. It's true, it is a bit tedious. Perhaps in the future
    there will be a stream lined process.
    
    Take heart. You're most of the way there. Most of the rest of the
    work is fairly straightforward. Soon you'll bask in the glory of
    seeing your Product appear the the Zope add list.
    
    Get another snack and when you're ready let's continue
    Productifying our poll.
    
Product editing
    
    Next on our list of features to add to our poll is the ability to
    edit its attributes through the web. This process closely mirrors
    the process of creating and installing the poll. It will be
    accomplished with two methods: an editing form which displays the
    current information and a method which processes the form input
    and changes the poll object. These two components will closely
    mirror the poll adding and installation methods, with the
    exception that they will be methods of the 'Poll' class itself,
    rather than the Zope Folder class.
    
Product editing form

    Our poll editing form is quite similar to the poll creation form.
    In fact, it's easiest just to make a copy of the adding form and
    go through and make small changes to create the editing form.
    Here's what it looks like::
    
        <html>
          <head><title>Edit Poll</title></head>
          <body bgcolor="#FFFFFF">

          <!--#var manage_tabs-->

            <h2>Edit Poll</h2>

            <form action="manage_edit" method="POST">
              <table cellspacing="2">

            <tr>
              <th align="LEFT" valign="TOP"><em>Title</em></th>
              <td align="LEFT" valign="TOP">
                    <input type="TEXT" name="title" size="50"
                    value="<!--#var title-->">
                  </td>
            </tr>

            <tr>
              <th align="LEFT" valign="TOP">Poll Question</th>
              <td align="LEFT" valign="TOP">
                    <input type="TEXT" name="question" size="50"
                    value="<!--#var question-->">
                  </td>
            </tr>

            <tr>
              <th align="LEFT" valign="TOP">Poll Answers<br>(one per
              line)</th>
              <td align="LEFT" valign="TOP">
                    <textarea name="choices:lines">
                    <!--#in answers-->
                    <!--#var sequence-item fmt="html-quote"-->
                    <!--#/in-->
                    </textarea>
                  </td>
            </tr>

            <tr>
              <td></td>
              <td><br><input type="SUBMIT" value="Edit"></td>
            </tr>

            </table>
            </form>
          </body>
        </html>

    There are a couple things to notice here. For one we've included a
    'manage_tabs' variable at the top to create the characteristic
    Zope tabbed management interface. We've also changed the form's
    action to point to 'manage_edit' which is the name of editing
    method that will we discuss next. Also we do not allow editing the
    the poll's id. This should be handled by the standard Zope rename
    facility. Finally, we've included 'value' attributes for the form
    inputs to supply the current values. In the case of the answers
    textarea, we've dumped all the answers into the textarea with an
    'in' loop.

Product editing method
    
    To allow editing of the poll's attributes, we need to have a
    method to accept the editing form's input. By convention such a
    method is called 'manage_edit' though you need not always name
    your editing method this. In fact complex products will have a
    number of editing methods with different names.
    
    The editing method isn't very complex. It simply updates the
    poll's attributes and returns a confirmation message to the user
    indicating that their update was successful. it is a good idea to
    provide the user with a confirmation message when they perform a
    management task, unless the results of their action is readily
    visible in some other way.
    
    Here's what our poll's editing method looks like::
    
        def manage_edit(self,title,question,choices,REQUEST=None):
            "edit the poll's characteristics"
            self.title=title
            self.question=question
            self.choices=[]
            for choice in choices:
                if choice:
                    self.choices.append(choice)
            if len(self.choices) < 2:
                raise ValueError, "You must supply at least two valid
                choices."
            if REQUEST is not None:
                return MessageDialog(
                    title='Edited',
                    message='<strong>%s</strong> has been edited.' %
                    self.id,
                    action ='./manage_main',
                    )       

    Since this is a published method it needs to have a doc string.
    The method replaces the title and question attributes. For the
    choices it makes sure to only add non blank choices to the poll.
    
    The method returns a confirmation message using the
    'MessageDialog' facility, which is a standard Zope document for
    notifying users. The title and message of the 'MessageDialog'
    inform the user of what has happened. The 'action' of the message
    dialog is the URL that the user will be taken to when they click
    'OK' after reading the confirmation. Since 'manage_main' is the
    default management screen for Zope Products we have chosen that as
    the URL to display following a confirmation.
    
    The method tests to see if the REQUEST exists before sending a
    confirmation message since, this will indicate whether the method
    has been called through the web or not. In cases where the method
    is called in DTML or External Methods, etcetera, it is not
    necessary to return an HTML confirmation.
    
    OK, so now our poll is looking pretty complete. Congratulations!
    At this point there is very little logic that we need to add to
    it. The majority of what's left to do are niggling Zope details
    which we need to nail down.
    
Defining Zope permissions

    Permissions are at the heart of Zope's access control system. All
    Products need to define their use in terms of permissions.
    Permissions are what allows users to define who gets to access
    objects through the web and through DTML. Permissions describe
    activities, and they are bound to roles through the management
    interface. Roles in turn are bound to users. 
    
    A permission consists of a number of methods along with default
    roles settings. To define permissions the Product class should
    have an '__ac_permissions__' class attribute which is a tuple of
    tuples. The individual permissions are defined by a tuple of three
    items: the permission name, a tuple of methods bound to the
    permission, and an optional tuple of roles. 

    Here's how we initially define permissions for our poll Product::
    
        __ac_permissions__=(
            ('View management screens',('manage_tabs','manage_main')),
            ('Change permissions',('manage_access',)),
            ('Change Polls',('manage_edit',),('Manager',)),
            ('Vote',('vote',),('Anonymous','Manager')),
            ('View',('','total_votes','votes_for')),
        )
    
    You should not assign the same method to more than one permission.
    Also you should probably only count on 'Manager' and 'Anonymous'
    roles being present when you set your default roles. Finally you
    should try to define as much of your Product's permissions with
    the same names as those of existing Products. This way your
    Product can profitably inherit permission settings. Categorizing
    methods into permissions is a tricky business.
    
    In our case we have chosen to use three existing permissions:
    'View management screens', 'View' and 'Change permissions'. We
    have added only two new permissions, 'Vote' and 'Change Polls'.
    
    In order for your Product's permissions to work well with
    acquisition, you should export your Product's unique permissions
    to the Folder class, so that they can be set on Folders and
    acquired easily. To do this you need to define a second set of
    '__ac_permissions__' in your Product's '__init__.py' file. So in
    our 'Poll' package's '__init__.py' file we define the following::
    
        __ac_permissions__=(
            ('Add Polls',('manage_addPollForm','manage_addPoll')),
            ('Change Polls', ()),
            ('Vote', ()),
        )
    
    As the example shows, you needn't fully define permission that are
    already defined in your Product's class. In other words, since
    'Change Polls' is already defined in our poll class, we only need
    to add its name here.
    
    Also, you may wish to added permissions in the '__init__.py' file
    to cover the creation methods that you define there. We have done
    this by defining 'Add Polls' here. Why didn't we define 'Add
    Polls' in our 'Poll' class? Because the 'Add Polls' permission
    doesn't actually cover methods of the 'Poll' class, it covers
    methods of the Folder class. Remember that Zope installs the
    product creation methods in its Folder class.
    
    Whew, we've got permissions mostly nailed. Now on the the next
    detail.

Defining management tabs

    All Products are manageable through the web. This is the Zope way.
    The standard convention for Product management is to provide a
    series of tabbed management screens. These tabs are visible on the
    top of the right frame in the Zope management interface. As we
    have seen you create these tabs with the 'manage_tabs' method in
    the DTML of the management screens. You can inherit the
    'manage_tabs' method, but you need to define the tabs in order for
    the method to do its work.
    
    To provide tabs for your Product you need to define them with the
    'manage_options' class attribute. Here are the tabs that we decide
    to define for our poll Product::
    
        manage_options=(
            {'label':'Properties','action':'manage_main'},
            {'label':'View','action':''},
            {'label':'Security','action':'manage_access'},
        )

    This tuple of dictionaries defines a couple management tabs. The
    'label' item defines the tab's name. The 'action' item defines the
    method that the tab calls. Note that a blank method is the same as
    calling 'index_html'. You can optionally provide a 'target' item
    to indicate the frame target.
    
    Defining management tabs is not always straightforward. In general
    you should group like management functions together onto one
    management screen. You can also choose to include more standard
    management tabs than we have chosen to include like 'Undo' and
    'Find'. You're best bet is to study how existing Products work and
    try to steal good ideas from them.
    
    If you're paying very close attention you will notice that Zope
    automatically adds a 'Help' tab for you. 

Specifying Product 'meta-type'

    An object's 'meta-type' is a string which describes what sort of
    Product it is. A meta-type is just a name; it has nothing to do
    with Python meta-classes. All Products need to have different
    meta-types. The meta-type of Folder objects is 'Folder', the
    meta-type of Documents is 'Document'. You get the idea. A
    meta-type is the name of a Zope Product as displayed in the
    management interface, and it is needed to create a Zope Product.
    
    To define a meta-type for your object you need to include a
    'meta_types' tuple in your Product's '__init__.py' file. Here's
    how we will do this for our poll Product::

        meta_types=(
            {'name': 'Poll',
            'action': 'manage_addPollForm'
            },
        )

    So basically what we have is a tuple of addable objects with their
    names and the object creation form method name in dictionaries.
    Note that your Product can define more than one meta-type, if it
    defines more than one type of addable object.

    You will also need to define you Product's meta-type in its class
    as a class attribute.   For example this is how we indicate our
    poll's meta-type::

        class Poll:
            ...
            meta_type='Poll'
            ...

    The Product's meta-type appears in the management interface in the
    list of objects which can be added. The meta-type is also used in
    a number of Zope methods (such as 'objectValues') when querying an
    object about its sub-objects.
    
    OK, that was pretty easy. Only a few more details and we're done.
    
Product icons

    Products are identified in the management interface by their
    icons. Icons are 16 pixel by 16 pixel GIFs with transparent
    backgrounds. By convention Folderish products have icons that
    looks somewhat like a folder.

    Product icons are defined in the Product's '__init__.py' file with
    the 'misc_' mapping.

    Zope defines a special top-level object, 'misc_', which has a
    sub-object for each installed Product.  Each Product sub-object in
    'misc_' has sub-objects for each of the keys defined in the
    'misc_' dictionary in the Product's '__init__.py'.  This provides
    a mechanism for creating Product-dependent, instance-independent
    objects that are web accessible. By convention, 'misc_' only holds
    Product icons.

    Here's our poll's 'misc_' definition::

        misc_={
            'poll': ImageFile('poll.gif',globals()),
        }

    It only defines one thing--the poll's icon. For your Product's
    icons you should use 'ImageFile's.

    Objects defined in a Product's 'misc_' mapping are made available
    through the web at '/misc_/<ProductName>/<name>'. 
    
    To allow Zope to find this icon you need to specify its URL in
    your Product's class. So here's how we indicate our poll's icon::
    
        class Poll:
            ...
            icon='misc_/Poll/poll'
            ...

We're done for now

    Hooray! We've now wrapped up our poll Product. It was a fair
    amount of work, but hopefully it helped you learn a little more
    about how Zope functions under the hood.
    
    Now that we have our Product fleshed out let's install it and test
    it out. You should not break for a snack now, the anticipation is
    too great at this point.

Installing the poll

    To install you Product simply create a directory inside
    'lib/python/Products' and place your files in it. So for our poll
    we will create a directory 'lib/python/Products/Poll' and inside
    we will put these files:
        
        '__init__.py' -- misc poll product data.

        'Poll.py' -- the poll class definition.

        'poll.dtml' -- the display method.

        'pollAdd.dtml' -- the creation form.

        'pollEdit.dtml' -- the editing form.

        'poll.gif' -- the icon file.

    Later we may want to add additional files to the distribution like
    a README file and a version file.
    
    To make the Product accessible to Zope you now need to shutdown
    and restart Zope. The best way to do this is via the Zope Control
    Panel.

Testing the poll

    Now let's see if our Product works. If the Product took, you
    should see 'Poll' listed in the pop-up menu of items to add on the
    management screen. You can also check things out under the Zope
    Control Panel. In the Control Panel there is a sub-object called
    Products. If your Product failed to load you should be able to see
    a traceback there which will give you information about what went
    wrong loading your Product. 
    
    If a Product fails to load, existing instances of the Product will
    be unusable and will appear broken.
    
    Edit the Product. Shutdown and restart Zope. Repeat as necessary
    until the Product loads.
    
    If you really mess things up you can cause Zope to fail to start.
    In these cases you'll need to read the traceback and figure out
    what you did wrong.
    
    Once the poll actually loads correctly into Zope we can test it
    further by creating a poll object, editing it and using it.

Refining the poll

    As soon as you have a semi-functional Product it's time to start
    refining. In the case of our Poll product the main things we need
    to work on are user interface issues. We have a simple method for
    displaying a Poll's form, but no feedback mechanism. Another
    thing we would like to be able to do is to embed a poll in another
    document. Here's a plan to deal with these issues. 
    
        * Factor the user interface into a couple different components
        
        * Provide a default view and operation for the Poll
        
    By factoring the UI, we allow access to different poll component
    to be embedded in Documents. By providing a default view and
    operation we allow simple use of a poll without any other
    Documents.
    
    Let's factor the voting form and results display into two class
    attributes, 'form' and 'results'. Next we provide a default poll
    view, 'index_html' which calls these components. We also need to
    make sure that we define the appropriate permissions for these
    different user interface components.
    
    Here's the poll form::
    
        <p><!--#var question--></p>
        <form action="<!--#var id-->/vote" method="post">
        <!--#in choices-->
        <input type="radio" name="choice:int"
        value="<!--#var sequence-index-->"> <!--#var
        sequence-item--><br>
        <!--#/in-->
        <!--#if action-->
        <input type="hidden" name="action" value="<!--#var action-->">
        <!--#/if-->
        <input type="submit" value="Vote">
        </form>

    It's basically our original poll user interface. It is not a
    complete HTML document.
    
    Here's the poll results template::
    
        <!--#if total_votes-->
        <p>
        <!--#in choices-->
        <!--#var sequence-item-->: <!--#var
        expr="percent_for(_['sequence-index'])" fmt="%2d"--> %<br>
        <!--#/in-->
        </p>
        <p>Total number of votes cast: <!--#var total_votes--></p>
        <!--#else-->
        <em>No votes have been cast yet.</em>
        <!--#/if-->

    Again this is an HTML fragment. It uses a new method we've added
    called 'percent_for' to display the percentage of votes for each
    choice.    

    Finally we can redo the main poll interface template now::

        <html>
        <head><title>Poll:<!--#var title_or_id--></title></head>
        <body>
        <h1>Poll: <!--#var title_or_id--></h1>
        <h2>Vote</h2>
        <!--#var form-->
        <h2>Results</h2>
        <!--#var results-->
        </body>
        </html> 
    
    This template presents a complete way to use the poll. It allows
    you to vote, and it displays the results.
    
    To finish up the user interface changes we need to modify the
    'vote' method a little::
    
        def vote(self,choice,action=None,REQUEST=None):
            """Vote for a choice
            
            'choice' is an integer which represents a choice.
            'action' is an optional URL which the user will
            be taken to after they vote."""
            
            # this is done like this in order to allow Zope's
            # persistence system notice that we're updating 
            # a mutable attribute.
            votes=self.votes
            votes[choice]=votes[choice]+1
            self.votes=votes
            if action is None:
                action=''
            if REQUEST is not None:
                return MessageDialog(
                    title='Vote Accepted',
                    message='Your vote has been accepted.',
                    action =action,
                    )
    
    What we have here is a moderately flexible way of responding to a
    user when they vote. We display a standard Zope message indicating
    that their vote was accepted, and then we send them to a URL. If
    you call the vote method with an 'action' URL you can set the
    return page, if not, you are sent to the poll's default view after
    you vote.

We can always do better

    Well, this is a reasonable solution, but we could do much better.
    For example, it would be nice to make the Poll Folderish, and
    provide 'form', 'results', and 'index_html' not as fixed class
    attributes, but as default Documents inside the Poll. Then users
    could modify the look and feel of a Poll and even add more
    methods.

    There are many other things that might be nice to do as well. Like
    we might want to provide more hooks for different voting controls.
    For example, we could keep track of users and make sure they only
    vote once. We could limit anonymous voters by IP address.
    
    We could also expand our concept of poll to include more than just
    multiple choice polls. For example, we could allow write in votes,
    voting for multiple choices at once, and weighted votes--ie
    allowing you to vote for *or* against choices.
    
    We might want to have some concept of poll closure. Managers
    should be able to close a poll to further voting. Alos, polls
    could have some time limit, so they close automatically after a
    week for example.
    
    We could add graphs showing voting trends and results. In fact we
    could make simple ones by stretching GIFs in proportion to the
    numbers of votes.

    OK, we're not going to do these things right now, but you might
    want to try some of these ideas on your in order to learn more.

    Since we chose to create a Zope Product, all these goals are
    easily within our reach, and if we plan it right, we should be
    able to roll out our poll Product now, and add more features to it
    later.

Congratulations

    If you made it this far, congratulations! You've slogged through a
    long discussion. At this point you might want to re-read the
    example poll code and maybe the final poll Product code, now that
    you understand some of the ideas behind it better.
    
    The rest of our discussion will cover issues that arise as you get
    more comfortable with writing Zope products and prepare to share
    them with other people.
    
    Now might be a good time for another snack, or maybe a actual
    meal--you've earned it.

Control Panel Product features

    There are a couple interesting tricks you can do with the Zope
    Control Panel. Besides showing your Product's traceback if it's
    broken, the Control Panel will show your Product's version
    information and a README file.
    
    To include version information with your Product create a
    'version.txt' file at the top level of your Product's package
    directory. Include one line of text in the file to name your
    Product's version. For example we might want to include something
    like this::

        Poll 0.1.0
        
    To indicate this is an early version of our poll Product.

    Additionally if you include a README file with your Product, it
    will be made available through the Control Panel. You need to make
    sure to name your README file 'README.txt' and to include it at
    the top level of your Product's package directory. Zope will
    interpret your README file as structured text. To find out more
    about structured text visit the Zope site's documentation area.
    Suffice it to say that your README file should look OK if you use
    normal text and include blank lines in between paragraphs.

Distributing Products

    The standard way to distribute a Zope product is in a gzipped tar
    archive. You should arrange you tarball so that it can be untarred
    from within the Zope directory. For example assuming we have
    developed our poll Product in Zope and that it is currently in
    'lib/python/Products/Poll', we can create an archive for our poll
    product by first cding to the Zope directory, and then issuing
    this command::
    
        tar cvfz Poll.tar.gz lib/python/Products/Poll
    
    If your Product relies on resources in the Shared package you will
    also need to include those packages in your tarball.
    
    If you reasonably document your Product with doc strings, Zope
    gives you reasonable online help via the help tab. It's really
    worth it to make the effort to document your Product.
    
    In order for your Product's methods to show up in the online help,
    you need to have both a doc string for the method and assign it to
    a permission.
    
    Also don't forget the 'version.txt' and the 'README.txt' files
    that we mentioned earlier. Providing these files helps others who
    want to use your Product quite a bit.

Upgrading and evolving Products

    Issues can occur when you change your Product class and then
    reload objects that were created with an earlier version of the
    class. The simplest way to handle these sorts of situations is to
    provide class attributes as defaults for added attributes. For
    example if the latest version of your Product expects a
    'improved_spam' attribute while earlier versions only sported
    'spam' attributes, you may wish to define 'improved_spam' class
    attribute in your new class so your old object won't break  when
    they run under your new class. Another solution is to use the
    standard Python pickling hook '__setstate__', however, this is in
    general more error prone and complex.

    While you are developing a Product you won't have to worry too
    much about these details, since you can always delete old
    instances that break with new class definitions. However, once you
    release your Product and other people start using it, then you
    need to start planning for the eventuality of upgrading.

    Another nasty problem that can occur when unpickling objects is
    what to do when you change the name of your Product's class. This
    should be avoided by not renaming your Products class, unless you
    wish to lose all stored instances.
    
We're done

    OK, that's it for now. Now you should be ready to build your own
    Products and distribute them.
    
    Go for it!
    