[Checkins] SVN: zdgbook/trunk/ stx to rst

Baiju M baiju.m.mail at gmail.com
Tue Feb 17 08:16:30 EST 2009


Log message for revision 96642:
  stx to rst
  

Changed:
  D   zdgbook/trunk/Products.stx
  A   zdgbook/trunk/source/Products.rst
  U   zdgbook/trunk/source/index.rst

-=-
Deleted: zdgbook/trunk/Products.stx
===================================================================
--- zdgbook/trunk/Products.stx	2009-02-17 13:05:50 UTC (rev 96641)
+++ zdgbook/trunk/Products.stx	2009-02-17 13:16:30 UTC (rev 96642)
@@ -1,1846 +0,0 @@
-Chapter 3: Zope Products
-
-  Introduction
-
-    Zope *products* extend Zope with new functionality. Products most
-    often provide new addable objects, but they can also extend Zope
-    with new DTML tags, new ZClass base classes, and other services.
-
-      % Anonymous User - Nov. 8, 2002 8:49 pm:
-       Zope has an online help system that you can use. Its main features are context-sensitive help and API help.
-       You might also inspect the python source files.
-       Additionally there are mailing lists and mailing list archives which can be searched (at NIP or activestate).
-
-      % Anonymous User - Nov. 10, 2002 9:19 am:
-       API Reference http://www.zope.org/Documentation/Books/ZopeBook/current/AppendixB.stx
-
-      % Anonymous User - May 7, 2004 11:55 am:
-       The width of this page in my browser is huge. This is not the case for other books (Zope Book) on this site. I'm
-       using Mac/Safari
-
-      % Anonymous User - July 15, 2004 2:05 am:
-       I know this is offtopic, but the width was just bugging the hell out of me too. On Mac Safari, Mac Mozilla,
-       and GNU/Linux (Debian) Firefox. I'm about to download the PDF. Maybe there will be fewer offtopic posts on
-       that ;)
-
-    There are two ways to create products in Zope, through the web, and
-    with files in the filesystem. In this chapter we are going to look
-    at building products on the file system. For information on through
-    the web products, and ZClasses see the *Zope Book*, Chapter 12. 
-
-      % tibi - Feb. 22, 2002 6:25 am - Right now it's chapter 14, better use chapter name "Extending Zope"
-
-      % jancolpaert - May 5, 2002 4:51 am:
-       I think that at this stage we need to make clear whether it is possible to 
-       have a mix between both models (on the file system and on the web). Since both 
-       models have their advantages. If such a mix is not possible, we should state 
-       this clearly here since a lot of users might waste their time finding a 
-       solution for this.
-
-      % Anonymous User - Nov. 23, 2003 4:13 pm:
-       xcxcxcxcx
-
-    In comparison to through the web products, filesystem products
-    require more overhead to build, but offer more power and
-    flexibility, and they can be developed with familiar tools such as
-    Emacs and CVS.
-
-      % to_be - Oct. 31, 2002 9:47 am:
-       Newbies should be warned that web products might not run in the next Zope version. I just tried to import our
-       product (developed using v2.5.1) in Zope 2.6.0, and it didn't work. I tried to re-implement it, and it didn't
-       work because of a bug with nested ZClasses.
-
-    Soon we will make the examples referenced in this chapter
-    available for download as an example product.  Until that time,
-    you will see references to files in this chapter that are not
-    available yet.  This will be made available soon.
-
-      % Anonymous User - Jan. 15, 2003 5:04 pm:
-       Are these files already available? That would be real great to get started. Please point out if these
-       examples already come with zope (would be real nice) or if downloadable seperable please point out the URL.
-       djrap
-
-  Development Process
-
-    This chapter begins with a discussion of how you will develop products.
-    We'll focus on common engineering tasks that you'll encounter as you
-    develop products.
-
-    Consider Alternatives
-
-      Before you jump into the development of a product you should
-      consider the alternatives. Would your problem be better solved
-      with ZClasses, External Methods, or Python Scripts? Products excel
-      at extending Zope with new addable classes of objects. If this
-      does not figure centrally in your solution, you should look
-      elsewhere. Products, like External Methods allow you to write
-      unrestricted Python code on the filesystem.
-
-        % Anonymous User - Nov. 17, 2002 6:01 am:
-         Considering the dearth of 'background' material on Zope approaches in general, and relating to Products in
-         particular, I would find it hugely useful for someone knowlegeable to expand this section to discuss these
-         alternatives at much greater length/depth. Please. Pretty please.
-
-        % Anonymous User - Dec. 4, 2002 5:46 pm:
-         Here is what I did.
-         I started with ZClasses.  That was really easy to do, and got me a long way.
-         Then I added some Python extensions, really simple function calls using the 
-         full security allowed to file system based software.
-         Now I am starting to use simple python classes that implement just the application logic, and include
-         persistance. They are then inherited by Zclasses
-         that bring to bear the full zope functionality in a black box format.
-         Then I will move to full python based products.
-         Regards
-         Christopher Lozinski
-         http://Python.jobmart.com
-
-    Starting with Interfaces
-
-      The first step in creating a product is to create one or more
-      interfaces which describe the product. See Chapter 1 for more
-      information on interfaces and how to create them.
-
-      Creating interfaces before you build an implementation is a good
-      idea since it helps you see your design and assess how well it
-      fulfills your requirements.
-
-      Consider this interface for a multiple choice poll component (see
-      "Poll.py":examples/Poll.py)::
-
-        from Interface import Base
-
-        class Poll(Base):
-            "A multiple choice poll"
-
-            def castVote(self, index):
-                "Votes for a choice"
-
-            def getTotalVotes(self):
-                "Returns total number of votes cast"
-
-            def getVotesFor(self, index):
-                "Returns number of votes cast for a given response"
-
-            def getResponses(self):
-                "Returns the sequence of responses"
-
-            def getQuestion(self):
-                "Returns the question
-
-        % dshafer - June 2, 2002 4:21 pm:
-         The last comment in the interface is missing a closing quotation mark.
-
-        % Anonymous User - Feb. 11, 2003 7:10 pm:
-         Is it possible to use a variable to hold the value of the question ? I mean is the use of a method to access
-         so simple information mandatory ?
-
-        % Anonymous User - Apr. 15, 2004 9:43 am:
-         Interfaces are supposed to only have methods, and no variables.
-
-      How you name your interfaces is entirely up to you. Here we've
-      decided not to use an "I" or any other special indicator in the
-      name of the interface. 
-
-    Implementing Interfaces
-
-      After you have defined an interface for your product, the next
-      step is to create a prototype in Python that implements your
-      interface.
-
-        % Anonymous User - Jan. 7, 2002 2:34 pm - typo: "Here is a prototype of a PollImplemtation class that implements the interface you just examined" ... PollImplementation would be correct
-
-      Here is a prototype of a 'PollImplemtation' class that
-      implements the interface you just examined (see
-      "PollImplementation.py":examples/PollImplementation.py)::
-
-        from Poll import Poll
-
-        class PollImplementation:
-            """
-            A multiple choice poll, implements the Poll interface.
-
-            The poll has a question and a sequence of responses. Votes
-            are stored in a dictionary which maps response indexes to a
-            number of votes.
-            """
-
-            __implements__=Poll
-
-            def __init__(self, question, responses):
-                self._question = question
-                self._responses = responses
-                self._votes = {}
-                for i in range(len(responses)):
-                    self._votes[i] = 0
-
-            def castVote(self, index):
-                "Votes for a choice"
-                self._votes[index] = self._votes[index] + 1
-
-            def getTotalVotes(self):
-                "Returns total number of votes cast"
-                total = 0
-                for v in self._votes.values():
-                    total = total + v
-                return total
-
-            def getVotesFor(self, index):
-                "Returns number of votes cast for a given response"
-                return self._votes[index]
-
-            def getResponses(self):
-                "Returns the sequence of responses"
-                return tuple(self._responses)
-
-            def getQuestion(self):
-                "Returns the question"
-                return self._question
-
-        % Anonymous User - June 4, 2002 7:42 am:
-         Typo:  Here is a prototype of a _PollImplementation_ ...
-
-      You can use this class interactively and test it. Here's an
-      example of interactive testing::
-
-        >>> from PollImplementation import PollImplementation
-        >>> p=PollImplementation("What's your favorite color?", ["Red", "Green", "Blue", "I forget"])
-        >>> p.getQuestion()
-        "What's your favorite color?"
-        >>> p.getResponses()
-        ('Red', 'Green', 'Blue', 'I forget')
-        >>> p.getVotesFor(0)
-        0
-        >>> p.castVote(0)
-        >>> p.getVotesFor(0)
-        1
-        >>> p.castVote(2)
-        >>> p.getTotalVotes()
-        2
-        >>> p.castVote(4)
-        Traceback (innermost last):
-        File "<stdin>", line 1, in ?
-        File "PollImplementation.py", line 23, in castVote
-        self._votes[index] = self._votes[index] + 1
-        KeyError: 4          
-
-      Interactive testing is one of Python's great features. It lets you
-      experiment with your code in a simple but powerful way.
-
-        % Anonymous User - Dec. 11, 2001 9:07 pm - I'm having problems getting the interactive code to work with python 2.1.1 .  do you need more includes?  The interpreter says:  "No module named Interface"  was the Interface module included in a later version, or is this just a zope thing?
-
-        % Anonymous User - Dec. 12, 2001 3:51 pm - I'm just supporting previous blame. Same problem here()Python 2.1.1 (Win) Zope 2.5)
-
-        % Anonymous User - Jan. 7, 2002 3:20 pm - Yes, this is a Zope thing. It resides in {ZOPEHOME}/lib/python. Let's assume your ZOPEHOME is /home/Zope. Then you might want to start your interactive session like this:<br><br>  import sys<br>  sys.path.append('/home/Zope/lib/python')<br><br>  et voila! :-)
-
-        % Anonymous User - Sep. 2, 2002 9:29 pm:
-         Don't you need some kind of __init__.py in there somewhere, too?
-
-        % Anonymous User - Sep. 11, 2002 3:46 am:
-         no
-
-      At this point you can do a fair amount of work, testing and
-      refining your interfaces and classes which implement them. See
-      Chapter 7 for more information on testing.
-
-      So far you have learned how to create Python classes that are
-      documented with interfaces, and verified with testing. Next you'll
-      examine the Zope product architecture. Then you'll learn how to
-      fit your well crafted Python classes into the product framework.
-
-  Building Product Classes
-
-    To turn a component into a product you must fulfill many
-    contracts. For the most part these contracts are not yet defined
-    in terms of interfaces. Instead you must subclass from base
-    classes that implement the contracts. This makes building products
-    confusing, and this is an area that we are actively working on
-    improving.
-
-    Base Classes
-
-      Consider an example product class definition::
-
-        from Acquisition import Implicit
-        from Globals import Persistent
-        from AccessControl.Role import RoleManager
-        from OFS.SimpleItem import Item
-
-        class PollProduct(Implicit, Persistent, RoleManager, Item):
-            """
-            Poll product class
-            """
-            ...
-
-        % rboylan - July 24, 2002 1:42 am:
-         Somewhere you should clue people in that they probably want to use SimpleItem. It might also be worth noting
-         that SimpleItem is in fact less simple than Item!
-
-      The order of the base classes depends on which classes you want
-      to take precedence over others.  Most Zope classes do not define
-      similar names, so you usually don't need to worry about what
-      order these classes are used in your product.  Let's take a look
-      at each of these base classes:
-
-        % Anonymous User - Jan. 9, 2002 7:19 am - Arrgh, that's not fair to your Newbie readers: it seems that (in an interactive session on the console) the import of Persisten fails if 'import ZODB' isn't issued before ...
-
-        % Anonymous User - Jan. 9, 2002 7:23 am - Hint for those who have Python 2 installed as part of the Zope installation (but the system installation is still 1.x): in the folder bin/ of the Zope installation is a python 2 executable which can be used for the interactive sessions as well.
-
-        % chrism - Jan. 15, 2002 10:54 pm - Yup, indeed "import ZODB" should be the first thing you do in an interactive session.  You should also consider starting Zope from within the Zope/lib/python directory to make life easy.  For the inevitable question "why?", the answer is "so it will work" ;-)
-
-        % wleftwich - May 30, 2002 7:55 am:
-         Easy way to use Python interpreter while writing Products
-         Somewhere in your Python path, make a file called e.g. zopeinit.py, containing:
-         import sys
-         sys.path.append('/pathToZope/lib/python')
-         import ZODB
-         Then you can start the Python interpreter in the directory
-         where you're writing the product, and say
-         >> from zopinit import *
-
-        % Anonymous User - Jan. 24, 2003 8:43 am:
-         previous comment, last line should be: >> from zopeinit import *
-
-        % Anonymous User - Mar. 6, 2004 7:56 am:
-         I have a debian install of zope which uses /var/... as the root for the zope
-         instance files, such as Data.fs. In my case, the extra that I had to do to start zope manually was:
-           os.environ['INSTANCE_HOME'] = "/var/lib/zope/instance/default"
-         before the 'import Zope' line. (roy)
-
-      'Acquisition.Implicit'
-
-        This is the normal acquisition base class. See the *API
-        Reference* for the full details on this class. Many Zope
-        services such as object publishing and security use acquisition,
-        so inheriting from this class is required for
-        products. Actually, you can choose to inherit from
-        'Acquisition.Explicit' if you prefer, however, it will prevent
-        folks from dynamically binding Python Scripts and DTML Methods
-        to instances of your class. In general you should subclass from
-        'Acquisition.Implicit' unless you have a good reason not to.
-
-          % Anonymous User - Jan. 16, 2003 4:57 am:
-           Where can I find the "API Reference" for these classes ???
-
-        XXX is this true?  I thought that any ExtensionClass.Base can
-        be acquired.  The Implicit and Explicit just control how the
-        class can acquire, not how it *is* acquired.
-
-      'Globals.Persistent'
-
-        This base class makes instances of your product
-        persistent. For more information on persistence and this class
-        see Chapter 4.
-
-        In order to make your poll class persistent you'll need to make
-        one change. Since '_votes' is a dictionary this means that it's
-        a mutable non-persistent sub-object. You'll need to let the
-        persistence machinery know when you change it::
-
-          def castVote(self, index):
-              "Votes for a choice"
-              self._votes[index] = self._votes[index] + 1
-              self._p_changed = 1
-
-        The last line of this method sets the '_p_changed' attribute
-        to 1.  This tells the persistence machinery that this object
-        has changed and should be marked as 'dirty', meaning that its
-        new state should be written to the database at the conclusion
-        of the current transaction.  A more detailed explanation is
-        given in the Persistence chapter of this guide. 
-
-          % Anonymous User - Aug. 24, 2004 7:10 pm:
-           Why is that line (self._p_changed = 1) omited in the packed example (downloadable under "Packaging Products"
-           -> Poll-1.0.tgz) and why is this package working anyway?
-
-          % Anonymous User - Aug. 24, 2004 7:15 pm:
-           Why is that line (self._p_changed = 1) omited in the packed example (downloadable under "Packaging Products"
-           -> Poll-1.0.tgz) and why is this package working anyway?
-
-      'OFS.SimpleItem.Item'
-
-        This base class provides your product with the basics needed to
-        work with the Zope management interface.  By inheriting from
-        'Item' your product class gains a whole host of features: the
-        ability to be cut and pasted, capability with management views,
-        WebDAV support, basic FTP support, undo support, ownership
-        support, and traversal controls.  It also gives you some
-        standard methods for management views and error display
-        including 'manage_main()'.  You also get the 'getId()',
-        'title_or_id()', 'title_and_id()' methods and the 'this()' DTML
-        utility method. Finally this class gives your product basic
-        *dtml-tree* tag support.
-       'Item' is really an
-        everything-but-the-kitchen-sink kind of base class. 
-
-          % Anonymous User - Dec. 10, 2002 9:32 am:
-           Shouldn't this be : 
-           from OFS.SimpleItem import SimpleItem;  ?
-
-          % Anonymous User - Dec. 10, 2002 9:49 am:
-           Delete please :)
-
-        'Item' requires that your class and instances have some
-        management interface related attributes.
-
-          'meta_type' -- This attribute should be a short string which
-          is the name of your product class as it appears in the product
-          add list. For example the poll product class could have a
-          'meta_type' of 'Poll'. 
-
-          'id' or '__name__' -- All 'Item' instances must have an 'id'
-          string attribute which uniquely identifies the instance within
-          it's container. As an alternative you may use '__name__'
-          instead of 'id'. 
-
-          'title' -- All 'Item' instances must have a 'title' string
-          attribute. A title may be an empty string if your instance
-          does not have a title.
-
-          % Anonymous User - Nov. 23, 2003 9:23 pm:
-           If you use __name__ instead of id, you need to mix in the Item_w__name__ class from OFS.SimpleItem
-
-        In order to make your poll class work correctly as an 'Item'
-        you'll need to make a few changes. You must add a 'meta_type'
-        class attribute, and you may wish to add an 'id' parameter to
-        the constructor::
-
-          class PollProduct(..., Item):
-
-              meta_type='Poll'
-              ...
-
-              def __init__(self, id, question, responses):
-                  self.id=id
-                  self._question = question
-                  self._responses = responses
-                  self._votes = {}
-                  for i in range(len(responses)):
-                      self._votes[i] = 0
-
-          % Anonymous User - Sep. 30, 2002 9:55 am:
-           I guess class PollProduct should have a title atrribute!?!
-
-          % Anonymous User - Oct. 9, 2002 4:52 am:
-           As I understand it: either a title='' attribute, so the class will never have a title, or add a title
-           parameter to the __init__ and assign it to self.title.
-
-         Finally, you should probably place 'Item' last in your list of
-         base classes. The reason for this is that 'Item' provides
-         defaults that other classes such as 'ObjectManager' and
-         'PropertyManager' override. By placing other base classes
-         before 'Item' you allow them to override methods in 'Item'.
-
-      'AccessControl.Role.RoleManager'
-
-        This class provides your product with the ability to have its
-        security policies controlled through the web. See Chapter
-        6 for more information on security policies and this
-        class.
-
-      'OFS.ObjectManager'
-
-        This base class gives your product the ability to contain other
-        'Item' instances. In other words, it makes your product class
-        like a Zope folder. This base class is optional. See the *API
-        Reference* for more details. This base class gives you
-        facilities for adding Zope objects, importing and exporting Zope
-        objects, WebDAV, and FTP. It also gives you the 'objectIds',
-        'objectValues', and 'objectItems' methods.
-
-          % Anonymous User - Oct. 1, 2003 5:58 am:
-           What that mean??
-
-        'ObjectManager' makes few requirements on classes that subclass
-        it. You can choose to override some of its methods but there is
-        little that you must do.
-
-        If you wish to control which types of objects can be contained
-        by instances of your product you can set the 'meta_types' class
-        attribute. This attribute should be a tuple of meta_types. This
-        keeps other types of objects from being created in or pasted
-        into instances of your product. The 'meta_types' attribute is
-        mostly useful when you are creating specialized container
-        products.
-
-          % Anonymous User - Mar. 27, 2002 8:04 pm - And what is 'a tuple of meta_types' ? A tuple of strings doesn't work...
-
-          % Anonymous User - June 19, 2002 10:09 am:
-           The following seems to work:
-           meta_types = ( { 'name' : 'Image', 'action' : 'manage_addImageForm', 'permission' : 'Add Image' } , )
-           Then you need to define the action 'manage_addImageForm' in your class:
-           from OFS.Image import Image
-           manage_addImageForm = Image.manage_addImageForm
-
-          % Anonymous User - Sep. 16, 2002 8:56 pm:
-           This method appears to add an object type, not restrict the types that can
-           be contained.  Does anyone have a method that actually works properly in Zope 
-           2.5.1?
-
-          % Anonymous User - Nov. 8, 2002 7:49 pm:
-           When making a special container product with a specialized set of addable
-           components (e.g. a report document with included db-generated images)
-           which should themselves be like products,
-           it maybe desired that these component products should be registered in the
-           container class only and not in zope, the main effect of this is that 
-           these component products wont appear in ordinary zope folder add lists
-           and are only addable in the master container and nowhere else.
-           It would be nice to have this specialized product registry explained also. blf
-
-          % Anonymous User - Feb. 23, 2003 3:05 am:
-           Here's a solution seems to work:
-           all_meta_types = ( { 'name' : 'Image', 'action' : 'manage_addImageForm', 'permission' : 'Add Image' } , )
-           Now the add-list will only show the products specified.
-
-      'OFS.PropertyManager'
-
-        This base class provides your product with the ability to have
-        user-managed instance attributes. See the *API Reference*
-        for more details. This base class is optional.
-
-          % Anonymous User - Jan. 15, 2002 7:58 am - where is this API reference? None seen so far.  Or do you mean Zope Book Appendix B?
-
-          % chrism - Jan. 15, 2002 10:54 pm - API reference is part of Zope help system.
-
-        Your class may specify that it has one or more predefined
-        properties, by specifying a '_properties' class attribute. For
-        example::
-
-          _properties=({'id':'title', 'type': 'string', 'mode': 'w'},
-                       {'id':'color', 'type': 'string', 'mode': 'w'},
-                       )
-
-          % rboylan - July 24, 2002 1:39 am:
-           Is this OK if you also inherit from Item, which has title as one of its magic variables? It seems to work for
-           me, and I suspect the example is meant to indicate it is OK. It might be worth noting explicitly.
-
-        The '_properties' structure is a sequence of dictionaries, where
-        each dictionary represents a predefined property. Note that if a
-        predefined property is defined in the '_properties' structure,
-        you must provide an attribute with that name in your class or
-        instance that contains the default value of the predefined
-        property.
-
-        Each entry in the '_properties' structure must have at least an
-        'id' and a 'type' key. The 'id' key contains the name of the
-        property, and the 'type' key contains a string representing the
-        object's type.  The 'type' string must be one of the values:
-        'float', 'int', 'long', 'string', 'lines', 'text', 'date',
-        'tokens', 'selection', or 'multiple section'. For more
-        information on Zope properties see the *Zope Book*.
-
-        For 'selection' and 'multiple selection' properties, you must
-        include an addition item in the property dictionary,
-        'select_variable' which provides the name of a property or
-        method which returns a list of strings from which the
-        selection(s) can be chosen. For example::
-
-          _properties=({'id' : 'favorite_color', 
-                        'type' : 'selection',
-                        'select_variable' : 'getColors'
-                       },
-                      )
-
-          % Anonymous User - Aug. 23, 2002 11:00 am:
-           Could we have an example of the property or method 'getColors' please? I cannot figure out how to create the
-           options list and nominate the default selected item.
-
-          % Anonymous User - Oct. 29, 2002 5:21 am:
-           getColors = ['red','blue','green']  
-           or a bit fancy  
-           def getColors(self):  
-             """return colors"""  
-             return somelistwithcolors
-
-        Each entry in the '_properties' structure may optionally provide
-        a 'mode' key, which specifies the mutability of the
-        property. The 'mode' string, if present, must be 'w', 'd', or 'wd'.
-
-          % groovehunter - Jan. 24, 2003 9:37 am:
-           În the ZWikiPage.py of the ZWiki product there are properties with mode 'r'. So i miss 'r' in the list above.
-           Or do i confuse something?
-
-          % SmileyChris - Feb. 1, 2005 4:00 pm:
-           I have noticed a lot of products use it but as far as I know, it's exactly the same as leaving it blank ('').
-
-        A 'w' present in the mode string indicates that the value of the
-        property may be changed by the user. A 'd' indicates that the user
-        can delete the property. An empty mode string indicates that the
-        property and its value may be shown in property listings, but that
-        it is read-only and may not be deleted.
-
-          % Anonymous User - Nov. 3, 2002 6:12 pm:
-           Example:
-           _properties=({'id' : 'favorite_color', 
-                         'type' : 'selection',
-                         'select_variable' : 'getColors'
-                        },
-                       )
-
-          % Anonymous User - Nov. 3, 2002 6:18 pm:
-           # Example of mode:
-           _properties=(
-                         {'id':'title', 'type':'string', 'mode':'w'},
-                         {'id' : 'favorite_color', 
-                          'type' : 'selection', 
-                          'select_variable' : 'getColors'
-                          'mode':'wd'},
-                       )
-           # In this case, we don't want the user to delete the property
-           # of 'title'. They can change it, but not remove it altogether.
-
-        Entries in the '_properties' structure which do not have a
-        'mode' item are assumed to have the mode 'wd' (writable and
-        deleteable).
-
-    Security Declarations
-
-      In addition to inheriting from a number of standard base
-      classes, you must declare security information in order to turn
-      your component into a product. See Chapter 6 for more
-      information on security and instructions for declaring security
-      on your components.
-
-        % Anonymous User - May 22, 2004 10:14 pm:
-         s/See Chapter 6/See Chapter 7/
-
-      Here's an example of how to declare security on the poll class::
-
-        from AccessControl import ClassSecurityInfo
-
-        class PollProduct(...):
-            ...
-
-            security=ClassSecurityInfo()
-
-            security.declareProtected('Use Poll', 'castVote')
-            def castVote(self, index):
-                ...
-
-            security.declareProtected('View Poll results', 'getTotalVotes') 
-            def getTotalVotes(self):
-                ...
-
-            security.declareProtected('View Poll results', 'getVotesFor') 
-            def getVotesFor(self, index):
-                ...
-
-            security.declarePublic('getResponses') 
-            def getResponses(self):
-                ...
-
-            security.declarePublic('getQuestion') 
-            def getQuestion(self):
-                ...
-
-        % Anonymous User - Oct. 19, 2005 4:56 am:
-         Where do I find AccessControl module?
-         -Shyam
-
-      For security declarations to be set up Zope requires that you
-      initialize your product class. Here's how to initialize your poll
-      class::
-
-        from Globals import InitializeClass
-
-        class PollProduct(...):
-           ...
-
-        InitializeClass(PollProduct)
-
-    Summary
-
-      Congratulations, you've created a product class. Here it is in all
-      its glory (see "PollProduct.py":examples/PollProduct.py)::
-
-        from Poll import Poll
-        from AccessControl import ClassSecurityInfo
-        from Globals import InitializeClass
-        from Acquisition import Implicit
-        from Globals import Persistent
-        from AccessControl.Role import RoleManager
-        from OFS.SimpleItem import Item
-
-        class PollProduct(Implicit, Persistent, RoleManager, Item):
-            """
-            Poll product class, implements Poll interface.
-
-            The poll has a question and a sequence of responses. Votes
-            are stored in a dictionary which maps response indexes to a
-            number of votes.
-            """
-
-            __implements__=Poll
-
-            meta_type='Poll'
-
-            security=ClassSecurityInfo()
-
-            def __init__(self, id, question, responses):
-                self.id=id
-                self._question = question
-                self._responses = responses
-                self._votes = {}
-                for i in range(len(responses)):
-                    self._votes[i] = 0
-
-            security.declareProtected('Use Poll', 'castVote')
-            def castVote(self, index):
-                "Votes for a choice"
-                self._votes[index] = self._votes[index] + 1
-                self._p_changed = 1
-
-            security.declareProtected('View Poll results', 'getTotalVotes') 
-            def getTotalVotes(self):
-                "Returns total number of votes cast"
-                total = 0
-                for v in self._votes.values():
-                    total = total + v
-                return total
-
-            security.declareProtected('View Poll results', 'getVotesFor') 
-            def getVotesFor(self, index):
-                "Returns number of votes cast for a given response"
-                return self._votes[index]
-
-            security.declarePublic('getResponses') 
-            def getResponses(self):
-                "Returns the sequence of responses"
-                return tuple(self._responses)
-
-            security.declarePublic('getQuestion') 
-            def getQuestion(self):
-                "Returns the question"
-                return self._question
-
-        InitializeClass(Poll)
-
-        % Anonymous User - Nov. 7, 2002 3:39 am:
-         Last line must be InitializeClass(PollProduct).
-
-      Now it's time to test your product class in Zope. To do this you
-      must register your product class with Zope.
-
-        % 1jerry - Nov. 15, 2001 8:41 pm - castVote above didn't work for me without "index = int(index)" after the line "Votes for a choice".  The web passes strings.  a[string] attempts a dictionary lookup.
-
-        % 1jerry - Nov. 19, 2001 10:15 pm - It seems *most* of the examples in this document have typos.  Make sure and read the comments, and/or search for poll-1.0 to find the pointer to download the correct examples.
-
-        % Anonymous User - Jan. 3, 2002 7:43 am - InitialiseClass(PollProduct) seems to have been replaced by InitialiseClass(Poll) in the full listing.
-
-        % Anonymous User - Jan. 3, 2002 7:44 am - That should be "Initialize", by the way.
-
-        % Anonymous User - Jan. 9, 2002 7:59 am - In the full listing, the line 'from Globals import InitializeClass' is missing
-
-  Registering Products
-
-    Products are Python packages that live in 'lib/python/Products'.
-    Products are loaded into Zope when Zope starts up. This process is
-    called *product initialization*. During product initialization, each
-    product is given a chance to register its capabilities with Zope.
-
-    Product Initialization
-
-      When Zope starts up it imports each product and calls the
-      product's 'initialize' function passing it a registrar object. The
-      'initialize' function uses the registrar to tell Zope about its
-      capabilities. Here is an example '__init__.py' file::
-
-        from PollProduct import PollProduct, addForm, addFunction
-
-        def initialize(registrar):
-            registrar.registerClass(
-                PollProduct, 
-                constructors = (addForm, addFunction),
-                )
-
-        % Anonymous User - Nov. 30, 2002 1:09 pm:
-         The advice was to put addForm and addFuction to the Poll module, but here we're trying to import them from
-         the PollProduct module. Can this work?
-
-      This function makes one call to the registrar object which
-      registers a class as an addable object.  The registrar figures
-      out the name to put in the product add list by looking at the
-      'meta_type' of the class. Zope also deduces a permission based
-      on the class's meta-type, in this case *Add Polls* (Zope
-      automatically pluralizes "Poll" by adding an "s").  The
-      'constructors' argument is a tuple of objects consisting of two
-      functions: an add form which is called when a user selects the
-      object from the product add list, and the add method which is
-      the method called by the add form. Note that these functions are
-      protected by the constructor permission.
-
-        % Anonymous User - Oct. 9, 2002 5:12 am:
-         Zope automatically pluralizes by adding an "s"?! Wow, the road to i18n-ization is still long...
-
-        % Anonymous User - Mar. 6, 2004 8:12 am:
-         '-ization' is redundant. i18n stands for 'internationalization' :)
-
-      Note that you cannot restrict which types of containers can
-      contain instances of your classes. In other words, when you
-      register a class, it will appear in the product add list in
-      folders if the user has the constructor permission.
-
-        % Anonymous User - Feb. 10, 2002 9:12 pm - Worse...  I've *LOOKED* in the API reference that can be found from the Zope Help system, and I don't see a mention of ProductRegistrar, nor does "search" find a reference to "registrar".  This is in 2.5.0 Zope.
-
-        % Anonymous User - Apr. 4, 2002 11:47 am:
-         To our opinion, registrar is a 'dummy' name for a parameter. It could be context or SantaClaus. The registrar
-         parameter contains the context of the object, isn't it.
-
-        % Anonymous User - June 2, 2002 2:00 am:
-         A little-known fact.  So little-known, in fact, that I've been unaware of it for three years:
-         Adding a "methods" attribute to your Product's __init__.py as a dictionary in the form:
-         methods = {'function': function, 'function2': function2}
-         Causes the functions to be available as attributes of the root Zope object! This means that they will be
-         available anywhere via acquisition, sort of as mini-singletons.
-         These functions must accept a single argument, which will be the object from which they are acquired.
-         I wish I knew about this a long time ago.
-
-        % Anonymous User - Aug. 9, 2002 8:01 am:
-         Another thing, important for readers and maybe a nice thing to mention here would be the visibility=None
-         thing, to prevent the product from showing up in the add list. I think it's not described anywhere.
-
-        % to_be - Nov. 26, 2002 7:01 am:
-         >>  a nice thing to mention here would be the visibility=None thing
-         I agree. It seems to be quite tricky. A good explanation of _implements_ and Interfaces would be nice. By the
-         way, does anyone know where I can find documentation regarding this topic?
-
-      See the *API Reference* for more information on the
-      'ProductRegistrar' interface.
-
-        % Anonymous User - Jan. 15, 2002 9:31 am - Again, where is this API Reference. Would be more welcomed than this book!
-
-        % Anonymous User - June 13, 2002 10:22 am:
-         That should be ProductContext instead of ProductRegistrar. I couldn't find the class in the reference
-         documentation either (at least not in Apendix B of the Zope Book) but the source file
-         lib/python/App/ProductContext.py is pretty well documented.
-         M. S.
-
-    Factories and Constructors
-
-      Factories allow you to create Zope objects that can be added to
-      folders and other object managers. Factories are discussed in
-      Chapter 12 of the *Zope Book*. The basic work a factory does is
-      to put a name into the product add list and associate a
-      permission and an action with that name. If you have the
-      required permission then the name will appear in the product add
-      list, and when you select the name from the product add list,
-      the action method will be called.
-
-      Products use Zope factory capabilities to allow instances of
-      product classes to be created with the product add list.  In the
-      above example of product initialization you saw how a factory is
-      created by the product registrar. Now let's see how to create the
-      add form and the add list.
-
-      The add form is a function that returns an HTML form that allows a
-      users to create an instance of your product class. Typically this
-      form collects that id and title of the instance along with other
-      relevant data. Here's a very simple add form function for the poll
-      class::
-
-        def addForm():
-            """
-            Returns an HTML form.
-            """
-            return """<html>
-            <head><title>Add Poll</title></head>
-            <body>   
-            <form action="addFunction">
-            id <input type="type" name="id"><br>
-            question <input type="type" name="question"><br>
-            responses (one per line)
-            <textarea name="responses:lines"></textarea>
-            </form>
-            </body>
-            </html>"""
-
-        % Anonymous User - May 11, 2002 2:15 am:
-         I think a <input type=submit> needs to be added to the above 
-         form.
-
-      Notice how the action of the form is 'addFunction'. Also notice
-      how the lines of the response are marshalled into a
-      sequence. See Chapter 2 for more information about argument
-      marshalling and object publishing.
-
-        % strobl - Oct. 18, 2001 6:00 pm - In addForm, the value of "type" is wrong. Replace type="type" by type="text". In context:    id <input type="type" name="id"><br>  question <input type="type" name="question"><br>    should be replaced by    id <input type="text" name="id"><br>  question <input type="text" name="question"><br>
-
-        % Anonymous User - Nov. 6, 2001 10:41 am - Using Zope 2.4 this function does not function properly.  I had to make following changes:  replace "def addForm():" with "def addForm(self):"  and adding  <input type="submit" value="Add">  before </form>
-
-        % Anonymous User - Dec. 20, 2001 10:09 am - "def AddForm(self)" might be missleading. The argument passed to AddFrom()  is a dispatcher-object. As AddForm is not a class-member, "self" is not provided.
-
-        % Anonymous User - Jan. 9, 2002 9:05 am - Note that addForm and addFunction are not methods of the PollProduct class.
-
-        % Anonymous User - June 13, 2002 11:48 am:
-         Under Zope 2.5.1,  an arguement IS passed to the addForm method.
-         From printing it out, i beleive it is a Dispatcher object.
-         (It's type is __FactoryDispatcher__)
-
-        % Anonymous User - Sep. 20, 2005 4:39 pm:
-         It is not clear where the addForm and addFuntion methods should go. Unlike Poll and PollProduct, a link is
-         not provided for __init__.py, or any other files that might contain these methods.
-
-      It's also important to include a HTML 'head' tag in the add
-      form. This is necessary so that Zope can set the base URL to make
-      sure that the relative link to the 'addFunction' works correctly.
-
-      The add function will be passed a 'FactoryDispatcher' as its
-      first argument which proxies the location (usually a Folder)
-      where your product was added. The add function may also be
-      passed any form variables which are present in your add form
-      according to normal object publishing rules.
-
-        % Anonymous User - Jan. 3, 2002 11:18 am - (below example) it might be a good idea to return some kind of confirmation message, in a similar fashion to that used in the previous constructor function.
-
-      Here's an add function for your poll class::
-
-        def addFunction(dispatcher, id, question, responses):
-            """
-            Create a new poll and add it to myself
-            """
-            p=PollProduct(id, question, responses)
-            dispatcher.Destination()._setObject(id, p)
-
-      The dispatcher has three methods:
-
-        'Destination' -- The 'ObjectManager' where your product was
-        added.
-
-        'DestinationURL' -- The URL of the 'ObjectManager' where your
-        product was added.
-
-        'manage_main' -- Redirects to a management view of the
-        'ObjectManager' where your product was added.
-
-      Notice how it calls the '_setObject()' method of the destination
-      'ObjectManager' class to add the poll to the folder. See the
-      *API Reference* for more information on the 'ObjectManager'
-      interface.
-
-        % Anonymous User - Jan. 15, 2002 9:43 am - Where is the API Reference. "_setObject()" is not explained in    http://www.zope.org/Members/michel/ZB/AppendixB.dtml#class ObjectManager
-
-        % zigg - Jan. 26, 2002 10:57 pm - All this blather about the ZB aside, _setObject also does not appear in the online API ref in my copy of Zope 2.5.0.
-
-        % Anonymous User - May 21, 2002 7:03 am:
-         _setObject() must be deprecated, because it's private -- it's not a piece of API.
-
-        % Anonymous User - May 21, 2002 9:34 am:
-         Sory, previous comment (my) isn't correct. _setObject() is OK and shouldn't be deprecated. The rest of the
-         comment is correct -- _setObject() is out of API, because it's private
-
-      The add function should also check the validity of its
-      input. For example the add function should complain if the
-      question or response arguments are not of the correct type.
-
-        % Zen - Mar. 23, 2002 2:03 am - The add function should also redirect the user back to the management screen. To do this, the add function needs 'REQUEST=None' as a keyword argument, and the following code:  if REQUEST is not None: return self.manage_main(self,REQUEST,update_menu=0) # update_menu should be 1 if we are an object manager.
-
-        % Anonymous User - June 13, 2002 3:49 pm:
-         Ok the add function should redirect but self here has no meaning, you must
-         add something like:
-         if REQUEST is not None:
-                     dispatcher.manage_main(dispatcher,REQUEST,update_menu=0)
-
-      Finally you should recognize that the constructor functions are
-      *not* methods on your product class. In fact they are called
-      before any instances of your product class are created. The
-      constructor functions are published on the web so they need to
-      have doc strings, and are protected by a permission defined in
-      during product initialization.
-
-        % Anonymous User - Feb. 15, 2002 3:35 pm - At this point I would like to see another COMPLETE source code summary that lists each file source code... to clarify where all these methods are declared... it's not exactly clear to someone who's never done this before.
-
-        % Anonymous User - Feb. 19, 2002 11:13 pm - Is it possible to define destructor for the product class? If the product instance does something outside zope it will probably have to clean up before being destroyed.
-
-        % Anonymous User - May 30, 2003 4:52 pm:
-         I've been searching everywhere to answer this questions!  I need destructors
-         and copy constructors as I've built a Product whose instances have a 1 to 1 
-         corelation with database tables.  Does anyone have any ideas?  I've been digging
-         through zope's python and have yet to turn anything up!
-
-    Testing
-
-      Now you're ready to register your product with Zope. You need to
-      add the add form and add method to the poll module. Then you
-      should create a 'Poll' directory in your 'lib/python/Products'
-      directory and add the 'Poll.py', 'PollProduct.py', and
-      '__init__.py' files. Then restart Zope.
-
-      Now login to Zope as a manager and visit the web management
-      interface. You should see a 'Poll' product listed inside the
-      *Products* folder in the *Control_Panel*. If Zope had trouble
-      initializing your product you will see a traceback here. Fix your
-      problems, if any and restart Zope. If you are tired of all this
-      restarting, take a look at the *Refresh* facility covered in
-      Chapter 7.
-
-      Now go to the root folder. Select *Poll* from the product add
-      list. Notice how you are taken to the add form. Provide an id, a
-      question, and a list of responses and click *Add*. Notice how you
-      get a black screen. This is because your add method does not
-      return anything. Notice also that your poll has a broken icon, and
-      only has the management views. Don't worry about these
-      problems now, you'll find out how to fix these problems in the next
-      section.
-
-        % Anonymous User - Nov. 6, 2001 10:45 am - I don't end with a black screen (Zope 2.4): the addForm remains visible.
-
-        % 1jerry - Nov. 19, 2001 10:04 pm - Since this doc leaves you hanging, to redirect the page add ,REQUEST=None as an additional parameter on the addFunction.  Then add this line to the bottom of addFunction:  if REQUEST is not None:  dispatcher.manage_main(dispatcher, REQUEST)
-
-      Now you should build some DTML Methods and Python Scripts to test
-      your poll instance. Here's a Python Script to figure out voting
-      percentages::
-
-        ## Script (Python) "getPercentFor"
-        ##parameters=index
-        ##
-        """
-        Returns the percentage of the vote given a response index. Note,
-        this script should be bound a poll by acquisition context.
-        """
-        poll=context
-        return float(poll.getVotesFor(index)) / poll.getTotalVotes()
-
-        % Anonymous User - Nov. 16, 2002 3:01 am:
-         Just exactly where should one add this Script?
-
-      % Anonymous User - Nov. 6, 2001 10:47 am - Entering above Script within Zope does not function.  Entering it as an external Python-file is not explained.  I am stuck at this point of the manual.
-
-      % 1jerry - Nov. 15, 2001 8:21 pm - This does not work because the first time the total votes will be 0.  Python does not like / 0.  Also, it shows a ratio, not percent.  Replace last line with these lines (<br> = <newline>):  if poll.getTotalVotes(): <br>    return "%d" % (float(poll.getVotesFor(index)) / poll.getTotalVotes()* 100) <br>  else: return 0
-
-      Here's a DTML Method that displays poll results and allows you to
-      vote::
-
-        <dtml-var standard_html_header>
-
-        <h2>
-          <dtml-var getQuestion>
-        </h2>
-
-        <form> <!-- calls this dtml method -->
-
-        <dtml-in getResponses>
-          <p>
-            <input type="radio" name="index" value="&dtml-sequence-index;">
-            <dtml-var sequence-item>
-          </p>
-        </dtml-in>
-
-        <input type="submit" value=" Vote ">
-
-        </form>
-
-        <!-- process form -->
-
-        <dtml-if index>
-          <dtml-call expr="castVote(index)">
-        </dtml-if>
-
-        <!-- display results -->
-
-        <h2>Results</h2>
-
-        <p><dtml-var getTotalVotes> votes cast</p>
-
-        <dtml-in getResponses>
-          <p>
-            <dtml-var sequence-item> - 
-            <dtml-var expr="getPercentFor(_.get('sequence-index'))">%
-          </p>
-        </dtml-in>
-
-        <dtml-var standard_html_footer>
-
-        % Anonymous User - Dec. 21, 2001 10:39 am - err function call
-
-      To use this DTML Method, call it on your poll instance. Notice how
-      this DTML makes calls to both your poll instance and the
-      'getPercentFor' Python script.
-
-        % Anonymous User - Nov. 6, 2001 10:50 am - This does not function anymore.  Zope blocks on the function "get" in the (last-4)-th line
-
-        % 1jerry - Nov. 15, 2001 8:26 pm - The above DTML Method also does not work.  Replace _.get('sequence-index') with _.getitem('sequence-index').
-
-        % 1jerry - Nov. 15, 2001 8:33 pm - Also, for beginners, "call it on your poll instance" means append the poll instance name (p1, if you named it that) and the name of the DTML Method (usepoll, or whatever you named it) to your URL.  I.E. if you placed your poll in your root of your site, use something like http://www.yoursitename.com/p1/usepoll as the address in your browser.
-
-        % groovehunter - Jan. 24, 2003 11:26 am:
-         I got the tarball and added the (corrected) script and dtml-method manually. 
-         It does not work: KeyError, Value is the index of the vote (0-4 in my case)::
-          File /usr/local/Zope-2.5.0-src/lib/python/DocumentTemplate/DT_Util.py, line 159, in eval
-             (Object: castVote(index))
-             (Info: index)
-           File <string>, line 2, in f
-           File /usr/local/Zope-2.5.0-src/lib/python/Products/Poll/PollProduct.py, line 88, in castVote
-             (Object: Poll)
-         KeyError: (see above)
-
-        % groovehunter - Jan. 24, 2003 3:51 pm:
-         It's me again. I did some debugging (good intro in the debugging chapter) and found out that 'index' is a
-         string in this function, so it can't be used as an index. - I added the line
-         index = int(index)
-         before 
-         self._votes[index] = self._votes[index] + 1
-         Now it works for me.
-
-      At this point there's quite a bit of testing and refinement that
-      you can do. Your main annoyance will be having to restart Zope
-      each time you make a change to your product class (but see
-      Chapter 7 for information on how to avoid all this
-      restarting). If you vastly change your class you may break
-      existing poll instances, and will need to delete them and create
-      new ones. See Chapter 7 for more information on debugging
-      techniques which will come in handy.
-
-        % Anonymous User - Nov. 8, 2002 8:15 pm:
-         ... if you vastly change: more specific: if you change the class signature
-         by adding/renaming/deleting class members (attribute or method) 
-         or any of the class methods parameter lists
-         product instances of previous trials will break.
-
-  Building Management Interfaces
-
-    Now that you have a working product let's see how to beef up its
-    user interface and create online management facilities.
-
-    Defining Management Views
-
-      All Zope products can be managed through the web. Products have a
-      collection of management tabs or *views* which allow managers to
-      control different aspects of the product.
-
-      A product's management views are defined in the 'manage_options'
-      class attribute. Here's an example::
-
-        manage_options=(
-            {'label' : 'Edit', 'action' : 'editMethod'},
-            {'label' : 'View', 'action' : 'viewMethod'},
-            )
-
-      The 'manage_options' structure is a tuple that contains
-      dictionaries. Each dictionary defines a management view. The view
-      dictionary can have a number of items.
-
-        'label' -- This is the name of the management view
-
-        'action' -- This is the URL that is called when the view is
-        chosen. Normally this is the name of a method that displays a
-        management view.
-
-        'target' -- An optional target frame to display the action. This
-        item is rarely needed.
-
-        'help' -- Optional help information associated with the
-        view. You'll find out more about this option later.
-
-        % Anonymous User - Nov. 8, 2002 8:17 pm:
-         /The view dictionary can have/Each dictionary can have/ blf
-
-      Management views are displayed in the order they are
-      defined. However, only those management views for which the
-      current user has permissions are displayed. This means that
-      different users may see different management views when managing
-      your product.
-
-      Normally you will define a couple custom views and reusing some
-      existing views that are defined in your base classes. Here's an
-      example::
-
-        class PollProduct(..., Item):
-            ...
-
-            manage_options=(
-                {'label' : 'Edit', 'action' : 'editMethod'},
-                {'label' : 'Options', 'action' : 'optionsMethod'},
-                ) + RoleManager.manage_options + Item.manage_options
-
-      This example would include the standard management view defined
-      by 'RoleManager' which is *Security* and those defined by 'Item'
-      which are *Undo* and *Ownership*. You should include these
-      standard management views unless you have good reason not to. If
-      your class has a default view method ('index_html') you should
-      also include a *View* view whose action is an empty string. See
-      Chapter 2 for more information on 'index_html'.
-
-      Note: you should not make the *View* view the first view on your
-      class. The reason is that the first management view is displayed when
-      you click on an object in the Zope management interface. If the
-      *View* view is displayed first, users will be unable to navigate
-      to the other management views since the view tabs will not be
-      visible.
-
-    Creating Management Views
-
-      The normal way to create management view methods is to use
-      DTML. You can use the 'DTMLFile' class to create a DTML Method
-      from a file. For example::
-
-        from Globals import DTMLFile
-
-        class PollProduct(...):
-          ...
-
-          editForm=DTMLFile('dtml/edit', globals())
-          ...
-
-      This creates a DTML Method on your class which is defined in the
-      'dtml/edit.dtml' file. Notice that you do not have to include the
-      '.dtml' file extension. Also, don't worry about the forward slash
-      as a path separator; this convention will work fine on Windows. By
-      convention DTML files are placed in a 'dtml' subdirectory of your
-      product. The 'globals()' argument to the 'DTMLFile' constructor
-      allows it to locate your product directory. If you are running
-      Zope in debug mode then changes to DTML files are reflected right
-      away. In other words you can change the DTML of your product's
-      views without restarting Zope to see the changes.
-
-        % Anonymous User - Dec. 9, 2003 5:50 pm:
-         <quote>don't worry about the forward slash as a path separator; this convention will work fine on
-         Windows</quote> but not in my case (Plone 1.0.5 on Win2000). I would therefore recommend not to use a
-         dtml-folder. (That works.)
-
-      DTML class methods are callable directly from the web, just like
-      other methods. So now users can see your edit form by calling the
-      'editForm' method on instances of your poll class. Typically DTML
-      methods will make calls back to your instance to gather
-      information to display. Alternatively you may decide to wrap your
-      DTML methods with normal methods. This allows you to calculate
-      information needed by your DTML before you call it. This
-      arrangement also ensures that users always access your DTML
-      through your wrapper. Here's an example::
-
-        from Globals import DTMLFile
-
-        class PollProduct(...):
-          ...
-
-          _editForm=DTMLFile('dtml/edit', globals())
-
-          def editForm(self, ...):
-              ...
-
-              return self._editForm(REQUEST, ...)
-
-        % Anonymous User - Nov. 8, 2002 8:24 pm:
-         Please beef up example with parameters to the dtml like
-             return self._editForm(REQUEST, a, b, c)
-
-      % Anonymous User - Jan. 9, 2002 2:07 pm - The following code doesn't work for two reasons: 1) you have declare editForm with the REQUEST parameter: def editForm(self,REQUEST,...) 2) editForm is a published Method so it needs a doc string, see Chapter 2
-
-      When creating management views you should include the DTML
-      variables 'manage_page_header' and 'manage_tabs' at the top, and
-      'manage_page_footer' at the bottom.  These variables are acquired
-      by your product and draw a standard management view header, tabs
-      widgets, and footer. The management header also includes CSS
-      information which you can take advantage of if you wish to add CSS
-      style information to your management views. The management CSS
-      information is defined in the
-      'lib/python/App/dtml/manage_page_style.css.dtml' file. Here are
-      the CSS classes defined in this file and conventions for their
-      use.
-
-        'form-help' -- Explanatory text related to forms. In the future,
-        users may have the option to hide this text.
-
-        'std-text' -- Declarative text unrelated to forms. You should
-        rarely use this class.
-
-        'form-title' -- Form titles.
-
-        'form-label' -- Form labels for required form elements. 
-
-        'form-optional' -- Form labels for optional form elements.
-
-        'form-element' -- Form elements. Note, because of a Netscape
-        bug, you should not use this class on 'textarea' elements.
-
-        'form-text' -- Declarative text in forms.
-
-        'form-mono' -- Fixed width text in forms. You should rarely use
-        this class.
-
-      Here's an example management view for your poll class. It allows
-      you to edit the poll question and responses (see
-      'editPollForm.dtml')::
-
-        <dtml-var manage_page_header>
-        <dtml-var manage_tabs>
-
-        <p class="form-help">
-        This form allows you to change the poll's question and
-        responses. <b>Changing a poll's question and responses
-        will reset the poll's vote tally.</b>.
-        </p>
-
-        <form action="editPoll">
-        <table>
-
-          <tr valign="top">
-            <th class="form-label">Question</th>
-            <td><input type="text" name="question" class="form-element"
-            value="&dtml-getQuestion;"></td>
-          </tr>
-
-          <tr valign="top">
-            <th class="form-label">Responses</th>
-            <td><textarea name="responses:lines" cols="50" rows="10">
-            <dtml-in getResponses>
-            <dtml-var sequence-item html_quote>
-            </dtml-in>
-            </textarea>
-            </td>
-          </tr>
-
-          <tr>
-            <td></td>
-            <td><input type="submit" value="Change" class="form-element"></td>
-          </tr>
-
-        </table>
-        </form>
-
-        <dtml-var manage_page_header>
-
-      This DTML method displays an edit form that allows you to change
-      the questions and responses of your poll. Notice how poll
-      properties are HTML quoted either by using 'html_quote' in the
-      'dtml-var' tag, or by using the 'dtml-var' entity syntax.
-
-        % Anonymous User - Dec. 26, 2001 6:06 pm - the last tag in the script above should be "<dtml-var manage_page_footer>".
-
-      Assuming this DTML is stored in a file 'editPollForm.dtml' in your
-      product's 'dtml' directory, here's how to define this method on
-      your class::
-
-        class PollProduct(...):
-            ...
-
-            security.declareProtected('View management screens', 'editPollForm')
-            editPollForm=DTML('dtml/editPollForm', globals())
-
-      Notice how the edit form is protected by the 'View management
-      screens' permission. This ensures that only managers will be able
-      to call this method.
-
-        % berniey - Nov. 6, 2001 9:19 pm - Should '''editPollForm=DTML('dtml/editPollForm', globals())''' become  '''editPollForm=DTMLFile('dtml/editPollForm', globals())''' ?
-
-        % Anonymous User - Jan. 13, 2002 6:28 pm - yes
-
-      Notice also that the action of this form is 'editPoll'. Since
-      the poll as it stands doesn't include any edit methods you must
-      define one to accept the changes. Here's an 'editPoll' method::
-
-        class PollProduct(...):
-            ...
-
-            def __init__(self, id, question, responses):
-                self.id=id
-                self.editPoll(question, response)
-
-            ...
-
-            security.declareProtected('Change Poll', 'editPoll')
-            def editPoll(self, question, responses):
-                """
-                Changes the question and responses.
-                """
-                self._question = question
-                self._responses = responses
-                self._votes = {}
-                for i in range(len(responses)):
-                    self._votes[i] = 0
-
-      Notice how the '__init__' method has been refactored to use the
-      new 'editPoll' method. Also notice how the 'editPoll' method
-      is protected by a new permissions, 'Change Poll'.
-
-        % Anonymous User - Jan. 9, 2002 12:01 pm - in the above __init__ method, the second argument to self.editPoll should be 'responses' (plural)
-
-      There still is a problem with the 'editPoll' method. When you call
-      it from the 'editPollForm' through the web nothing is
-      returned. This is a bad management interface. You want this method
-      to return an HTML response when called from the web, but you do
-      not want it to do this when it is called from '__init__'. Here's
-      the solution::
-
-        class Poll(...):
-            ...
-
-            def editPoll(self, question, responses, REQUEST=None):
-                """
-                Changes the question and responses.
-                """
-                self._question = question
-                self._responses = responses
-                self._votes = {}
-                for i in range(len(responses)):
-                    self._votes[i] = 0
-                if REQUEST is not None:
-                    return self.editPollForm(REQUEST,
-                        manage_tabs_message='Poll question and responses changed.')
-
-        % rboylan - July 24, 2002 1:49 am:
-         In general, if you want to end up on a certain tab, how do you do it? Are there any conventions or standards?
-         Working it out now seems to require cruising around the base classes to try to find the right tab, and then
-         tracing around some more to find the actual filename that gives the contents of the tab.
-
-        % Anonymous User - Nov. 8, 2002 8:34 pm:
-         wouldnt a redirect do better since it refreshes the client browsers url?
-         (but then of course the tabs_message would not appear...)
-
-      If this method is called from the web, then Zope will
-      automatically supply the 'REQUEST' parameter. (See chapter 2 for
-      more information on object publishing). By testing the 'REQUEST'
-      you can find out if your method was called from the web or not. If
-      you were called from the web you return the edit form again.
-
-      A management interface convention that you should use is the
-      'manage_tab_message' DTML variable. If you set this variable when
-      calling a management view, it displays a status message at the top
-      of the page. You should use this to provide feedback to users
-      indicating that their actions have been taken when it is not
-      obvious. For example if you don't return a status message from
-      your 'editPoll' method, users may be confused and may not realize
-      that their changes have been made.
-
-      Sometimes when displaying management views, the wrong tab will be
-      highlighted. This is because 'manage_tabs' can't figure out from
-      the URL which view should be highlighted. The solution is to set
-      the 'management_view' variable to the label of the view that
-      should be highlighted. Here's an example, using the 'editPoll'
-      method::
-
-        def editPoll(self, question, responses, REQUEST=None):
-            """
-            Changes the question and responses.
-            """
-            self._question = question
-            self._responses = responses
-            self._votes = {}
-            for i in range(len(responses)):
-                self._votes[i] = 0
-            if REQUEST is not None:
-                return self.editPollForm(REQUEST,
-                    management_view='Edit',
-                    manage_tabs_message='Poll question and responses changed.')
-
-      Now let's take a look a how to define an icon for your product.
-
-    Icons
-
-      Zope products are identified in the management interface with
-      icons. An icon should be a 16 by 16 pixel GIF image with a
-      transparent background. Normally icons files are located in a
-      'www' subdirectory of your product package. To associate an icon
-      with a product class, use the 'icon' parameter to the
-      'registerClass' method in your product's constructor. For
-      example::
-
-        def initialize(registrar):
-            registrar.registerClass(
-                PollProduct, 
-                constructors = (addForm, addFunction),
-                icon = 'www/poll.gif'
-                )
-
-        % Anonymous User - Nov. 19, 2002 11:01 am:
-         Would a PNG be acceptable/ preferable?
-
-        % Anonymous User - June 7, 2005 7:09 am:
-         unfortunately, not all browsers support PNGs. Sad but true.
-
-       Notice how in this example, the icon is identified as being
-       within the product's 'www' subdirectory.
-
-       See the *API Reference* for more information on the 'registerClass'
-       method of the 'ProductRegistrar' interface.
-
-    Online Help
-
-      Zope has an online help system that you can use to provide help
-      for your products. Its main features are context-sensitive help
-      and API help. You should provide both for your product.
-
-        % Anonymous User - Nov. 8, 2002 8:45 pm:
-         obviously there were 3 forward refs to the ominous API Reference (or is it API help? glossary drift?).
-         Better give this info at the start of this chapter. 
-         Additionally, give the python files of zope where the resp. functionality is defined (after all, this is Open
-         Source). Many insights will be gained inspecting the source. Part of the zope experience is to learn where to
-         look for help. part of the mailing list traffic would become obsolete if people knew howto search the
-         archives.
-
-        % Anonymous User - Nov. 16, 2002 3:10 am:
-         I echo the plea for some concise information/pointers on how and where to search for stuff. I'm more than
-         happy to go look for stuff (that's part of a programmers job, after all?) but it has taken me an age to get a
-         basic grip on all the various (scattered) sources for said stuff.
-
-      Context Sensitive Help
-
-        To create context sensitive help, create one help file per
-        management view in your product's 'help' directory. You have a
-        choice of formats including: HTML, DTML, structured text, GIF,
-        JPG, and PNG. 
-
-        Register your help files at product initialization with the
-        'registerHelp()' method on the registrar object::
-
-          def initialize(registrar):
-              ...
-              registrar.registerHelp()
-
-        This method will take care of locating your help files and
-        creating help topics for each help file. It can recognize these
-        file extensions: '.html', '.htm', '.dtml', '.txt', '.stx',
-        '.gif', '.jpg', '.png'.
-
-        If you want more control over how your help topics are created
-        you can use the 'registerHelpTopic()' method which takes an id
-        and a help topic object as arguments. For example::
-
-          from mySpecialHelpTopics import MyTopic
-
-          def initialize(context):
-              ...
-              context.registerHelpTopic('myTopic', MyTopic())
-
-        % Anonymous User - Jan. 15, 2002 7:40 pm - Please include something about how you create a HelpTopic and maybe (if   possible) how you group topics (HelpSys.tpValues mentions something about   topics having the same title would be grouped though I cant get it to work).
-
-        Your help topic should adhere to the 'HelpTopic'
-        interface. See the *API Reference* for more details.
-
-        The chief way to bind a help topic to a management screen is to
-        include information about the help topic in the class's
-        manage_options structure. For example::
-
-          manage_options=(
-              {'label':'Edit', 
-               'action':'editMethod',
-               'help':('productId','topicId')},
-              )
-
-        The 'help' value should be a tuple with the name of your
-        product's Python package, and the file name (or other id) of
-        your help topic. Given this information, Zope will automatically
-        draw a *Help* button on your management screen and link it to
-        your help topic.
-
-        To draw a help button on a management screen that is not a view
-        (such as an add form), use the 'HelpButton' method of the
-        'HelpSys' object like so::
-
-          <dtml-var "HelpSys.HelpButton('productId', 'topicId')">
-
-        This will draw a help button linked to the specified help
-        topic. If you prefer to draw your own help button you can use
-        the helpURL method instead like so::
-
-          <dtml-var "HelpSys.helpURL(
-            topic='productId',
-            product='topicId')">
-
-        This will give you a URL to the help topic. You can choose to
-        draw whatever sort of button or link you wish.
-
-    Other User Interfaces
-
-      In addition to providing a through the web management interface
-      your products may also support many other user interfaces. You
-      product might have no web management interfaces, and might be
-      controlled completely through some other network protocol. Zope
-      provides interfaces and support for FTP, WebDAV and XML-RPC. If
-      this isn't enough you can add other protocols.
-
-      FTP and WebDAV Interfaces
-
-        Both FTP and WebDAV treat Zope objects like files and
-        directories. See Chapter 2 for more information on FTP and
-        WebDAV.
-
-        By simply sub-classing from 'SimpleItem.Item' and
-        'ObjectManager' if necessary, you gain basic FTP and WebDAV
-        support. Without any work your objects will appear in FTP
-        directory listings and if your class is an 'ObjectManager' its
-        contents will be accessible via FTP and WebDAV.  See Chapter 2
-        for more information on implementing FTP and WebDAV support.
-
-      XML-RPC and Network Services
-
-        XML-RPC is covered in Chapter 2. All your product's methods can
-        be accessible via XML-RPC. However, if your are implementing
-        network services, you should explicitly plan one or more methods
-        for use with XML-RPC.
-
-        Since XML-RPC allows marshalling of simple strings, lists, and
-        dictionaries, your XML-RPC methods should only accept and return
-        these types. These methods should never accept or return Zope
-        objects. XML-RPC also does not support 'None' so you should use
-        zero or something else in place of 'None'.
-
-        Another issue to consider when using XML-RPC is security. Many
-        XML-RPC clients still don't support HTTP basic
-        authorization. Depending on which XML-RPC clients you
-        anticipate, you may wish to make your XML-RPC methods public and
-        accept authentication credentials as arguments to your methods.
-
-          % Anonymous User - Nov. 16, 2002 3:14 am:
-           An example here (or reference to one appearing elsewhere) showing how to accept authentication credentials as
-           arguments to your methods would be very valuable.
-
-      Content Management Framework Interface
-
-        The "Content Management Framework":http://cmf.zope.org is an
-        evolving content management extension for Zope. It provides a
-        number of interfaces and conventions for content objects. If you
-        wish to support the CMF you should consult the CMF user
-        interface guidelines and interface documentation.
-
-          % Anonymous User - Mar. 28, 2002 7:17 pm:
-           Please add pointers to the appropriate documentation. I have surfed cmf.zope.org, and looked over its
-           documentation section, and have not found anything identifiable as "user interface guidelines" or "interface
-           documentation". I found the occasional guide to creating new CMF skins, some marketing garbage, the ZWACK
-           Chapter, and that's about it. I did not find anything which explains how to write a product that is
-           CMF-aware, or why I would want so to do. Thanks in advance.
-
-        Supporting the CMF interfaces is not a large burden if you
-        already support the Zope management interface. You should
-        consider supporting the CMF if your product class handles user
-        manageable content such as documents, images, business forms,
-        etc.
-
-  Packaging Products
-
-    Zope products are normally packaged as tarballs. You should create
-    your product tarball in such a way as to allow it to be unpacked in
-    the Products directory. For example, 'cd' to the Products directory and then
-    issue a 'tar' comand like so::
-
-      $ tar cvfz MyProduct-1.0.1.tgz MyProduct
-
-    This will create a gzipped tar archive containing your product. You
-    should include your product name and version number in file name of
-    the archive.
-
-    See the "Poll-1.0.tgz":examples/Poll-1.0.tgz file for an example
-    of a fully packaged Python product.
-
-      % Anonymous User - Nov. 16, 2002 3:24 am:
-       Confusing for some - albeit momentarily - under windows / IE6 when trying to download this file IE6 screws up
-       the name (thinks it should be a simple .gz file rather than .tgz) and this in turn - if uncorrected -
-       confuses WinZip into opening it incorrectly, look like it was packed wrong.
-       Simple work-around: rename the file in the save-as dialog back to the correct .tgz extension.
-
-    Product Information Files
-
-      Along with your Python and DTML files you should include some
-      information about your product in its root directory.
-
-        'README.txt' -- Provides basic information about your product.
-        Zope will parse this file as "structured
-        text":http://www.zope.org/Members/millejoh/structuredText and
-        make it available on the *README* view of your product in the
-        control panel.
-
-        'VERSION.txt' -- Contains the name and version of your product
-        on a single line. For example, 'Mutiple Choice Poll 1.1.0'.
-        Zope will display this information as the 'version' property of
-        your product in the control panel.
-
-        'LICENSE.txt' -- Contains your product license, or a link to it.
-
-        % Anonymous User - Feb. 18, 2003 11:44 am:
-         On 2.5.1 VERSION.txt needs to be version.txt
-
-      You may also wish to provide additional information. Here are some
-      suggested optional files to include with your product.
-
-        'INSTALL.txt' -- Provides special instructions for installing
-        the product and components on which it depends. This file is
-        only optional if your product does not require more than an
-        ungzip/untar into a Zope installation to work.
-
-        'TODO.txt' -- This file should make clear where this product
-        release needs work, and what the product author intends to do
-        about it.
-
-        'CHANGES.txt' and 'HISTORY.txt' -- 'CHANGES.txt' should
-        enumerate changes made in particular product versions from the
-        last release of the product. Optionally, a 'HISTORY.txt' file
-        can be used for older changes, while 'CHANGES.txt' lists only
-        recent changes.
-
-        'DEPENDENCIES.txt' -- Lists dependencies including required os
-        platform, required Python version, required Zope version,
-        required Python packages, and required Zope products.
-
-        % Anonymous User - Jan. 15, 2002 11:09 am - typo: /Mutiple/Multiple/
-
-    Product Directory Layout
-
-      By convention your product will contain a number of
-      sub-directories. Some of these directories have already been
-      discussed in this chapter. Here is a summary of them.
-
-        'dtml' -- Contains your DTML files.
-
-        'www' -- Contains your icon files.
-
-        'help' -- Contains your help files.
-
-        'tests' -- Contains your unit tests.
-
-        % Anonymous User - Nov. 16, 2002 3:29 am:
-         Double-negative in explanation of INSTALL.txt (some way above this comment) leads to lack of clarity.
-
-      It is not necessary to include these directories if your don't
-      have anything to go in them.
-
-        % Anonymous User - Nov. 8, 2002 8:57 pm:
-         /your/you/
-
-        % jmeile - Nov. 28, 2002 2:43 pm:
-         Shouldn't be the "www" folder called "icons" or "images"?
-
-  Product Frameworks
-
-    Creating Zope products is a complex business. There are a number of
-    frameworks available to help ease the burden of creating products.
-    Different frameworks focus on different aspects of product
-    construction.
-
-    ZClass Base Classes
-
-      As an alternative to creating full blown products you may choose
-      to create Python base classes which can be used by ZClasses. This
-      allows you to focus on application logic and use ZClasses to take
-      care of management interface issues.
-
-      The chief drawback to this approach is that your code will be split
-      between a ZClass and a Python base class. This makes it harder to
-      edit and to visualize.
-
-      See the *Zope Book* for more information on ZClasses.
-
-    TransWarp and ZPatterns
-
-      TransWarp and ZPatterns are two related product framework
-      packages by Phillip Eby and Ty Sarna.  You can find out more
-      information on TransWarp from the "TransWarp Home
-      Page":http://www.zope.org/Members/pje/Wikis/TransWarp/HomePage.
-      More information on ZPatterns can be found at the "ZPatterns
-      Home Page":http://www.zope.org/Members/pje/Wikis/ZPatterns/HomePage
-
-  Evolving Products
-
-    As you develop your product classes you will generally make a series
-    of product releases. While you don't know in advance how your
-    product will change, when it does change there are measures that
-    you can take to minimize problems.
-
-    Evolving Classes
-
-      Issues can occur when you change your product class because
-      instances of these classes are generally persistent. This means
-      that instances created with an old class will start using a new
-      class. If your class changes drastically this can break existing
-      instances.
-
-      The simplest way to handle this situation is to provide class
-      attributes as defaults for newly added attributes. For example if
-      the latest version of your class expects an 'improved_spam'
-      instance attribute while earlier versions only sported 'spam'
-      attributes, you may wish to define an 'improved_spam' class
-      attribute in your new class so your old objects won't break when
-      they run with your new class. You might set 'improved_spam' to None
-      in your class, and in methods where you use this attribute you may
-      have to take into account that it may be None. For example::
-
-        class Sandwich(...):
-
-            improved_spam=None
-            ...
-
-            def assembleSandwichMeats(self):
-                ...
-                # test for old sandwich instances
-                if self.improved_spam is None:
-                    self.updateToNewSpam()
-                ...
-
-        % Anonymous User - Mar. 13, 2005 7:39 am:
-         i've searched now for hours, but could not find any example to apply *PERSISTENT* product upgrades in
-         __setstate__. every time i restart my zope, upgrades are performed again (and Data.fs does not change).
-
-      Another solution is to use the standard Python pickling hook
-      '__setstate__', however, this is in general more error prone and
-      complex.
-
-        % Anonymous User - Nov. 8, 2002 9:26 pm:
-         I'd like to get examples here.
-         Instead of pickling, how about including methods for export to/import from disk.
-         these may be editable strings (__repr__) for export and python eval for import. 
-         caveat: eval doesnt like newlines.
-         The next more complicated solution is parse/unparse methods for im/export.
-         This of course must be included in the very first distributed product and then
-         migrating to a newer version still requires manual work for each instance
-         (export, delete old instance, create new instance, import).
-         Or is there a way to modify product instances in the ZODB? example, plz.
-
-      A third option is to create a method to update old instances. Then
-      you can manually call this method on instances to update to
-      them. Note, this won't work unless the instances function well
-      enough to be accessible via the Zope management screens.
-
-      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 is breakage caused by
-      renaming your product classes. You should avoid this since it
-      breaks all existing instances. If you really must change your
-      class name, provide aliases to it using the old name.  You may
-      however, change your class's base classes without causing these
-      kinds of problems.
-
-    Evolving Interfaces
-
-      The basic rule of evolving interfaces is *don't do it*. While you are
-      working privately you can change your interfaces all you wish. But
-      as soon as you make your interfaces public you should freeze
-      them. The reason is that it is not fair to users of your
-      interfaces to changes them after the fact. An interface is
-      contract. It specifies how to use a component and it specifies how
-      to implement types of components. Both users and developers will
-      have problems if your change the interfaces they are using or
-      implementing.
-
-        % Anonymous User - Jan. 15, 2002 11:35 am - typo: /changes/change/
-
-        % Anonymous User - Nov. 16, 2002 3:35 am:
-         I don't think 'fairness' cuts any ice in a technical discussion. Rather, explain how changing an interface
-         will likely 'break' any existing code dependent on the contract(s) implicit in that interface.
-
-      The general solution is to create simple interfaces in the first
-      place, and create new ones when you need to change an existing
-      interface. If your new interfaces are compatible with your
-      existing interfaces you can indicate this by making your new
-      interfaces extend your old ones. If your new interface replaces
-      an old one but does not extend it you should give it a new name
-      such as, 'WidgetWithBellsOn'. Your components should continue to
-      support the old interface in addition to the new one for a few
-      releases.
-
-  Conclusion
-
-    Migrating your components into fully fledged Zope products is a
-    process with a number of steps. There are many details to keep track
-    of. However, if you follow the recipe laid out in this chapter you
-    should have no problems.
-
-    As Zope grows and evolves we want to simplify the Zope development
-    model. We hope to remove much of the management interface details
-    from product development. We also want to move to a fuller component
-    framework that makes better use of interfaces.
-
-    Nevertheless, Zope products are a powerful framework for building
-    web applications. By creating products you can take advantage of
-    Zope's features including security, scalability, through the web
-    management, and collaboration.

Copied: zdgbook/trunk/source/Products.rst (from rev 96617, zdgbook/trunk/Products.stx)
===================================================================
--- zdgbook/trunk/source/Products.rst	                        (rev 0)
+++ zdgbook/trunk/source/Products.rst	2009-02-17 13:16:30 UTC (rev 96642)
@@ -0,0 +1,1381 @@
+#############
+Zope Products
+#############
+
+Introduction
+============
+
+Zope *products* extend Zope with new functionality. Products most
+often provide new addable objects, but they can also extend Zope with
+new DTML tags, new ZClass base classes, and other services.
+
+There are two ways to create products in Zope, through the web, and
+with files in the filesystem. In this chapter we are going to look at
+building products on the file system. For information on through the
+web products, and ZClasses see the *Zope Book*, Chapter 12.
+
+In comparison to through the web products, filesystem products
+require more overhead to build, but offer more power and flexibility,
+and they can be developed with familiar tools such as Emacs and CVS.
+
+Soon we will make the examples referenced in this chapter available
+for download as an example product.  Until that time, you will see
+references to files in this chapter that are not available yet.  This
+will be made available soon.
+
+Development Process
+===================
+
+This chapter begins with a discussion of how you will develop
+products.  We'll focus on common engineering tasks that you'll
+encounter as you develop products.
+
+Consider Alternatives
+---------------------
+
+Before you jump into the development of a product you should consider
+the alternatives. Would your problem be better solved with ZClasses,
+External Methods, or Python Scripts? Products excel at extending Zope
+with new addable classes of objects. If this does not figure
+centrally in your solution, you should look elsewhere. Products, like
+External Methods allow you to write unrestricted Python code on the
+filesystem.
+
+
+Starting with Interfaces
+------------------------
+
+The first step in creating a product is to create one or more
+interfaces which describe the product. See Chapter 1 for more
+information on interfaces and how to create them.
+
+Creating interfaces before you build an implementation is a good idea
+since it helps you see your design and assess how well it fulfills
+your requirements.
+
+Consider this interface for a multiple choice poll component (see
+`Poll.py <examples/Poll.py>`_)::
+
+  from Interface import Base
+
+  class Poll(Base):
+      "A multiple choice poll"
+
+      def castVote(self, index):
+          "Votes for a choice"
+
+      def getTotalVotes(self):
+          "Returns total number of votes cast"
+
+      def getVotesFor(self, index):
+          "Returns number of votes cast for a given response"
+
+      def getResponses(self):
+          "Returns the sequence of responses"
+
+      def getQuestion(self):
+          "Returns the question
+
+How you name your interfaces is entirely up to you. Here we've
+decided not to use an "I" or any other special indicator in the name
+of the interface.
+
+Implementing Interfaces
+-----------------------
+
+After you have defined an interface for your product, the next step
+is to create a prototype in Python that implements your interface.
+
+Here is a prototype of a 'PollImplemtation' class that implements the
+interface you just examined (see `PollImplementation.py
+<examples/PollImplementation.py>`_)::
+
+  from Poll import Poll
+
+  class PollImplementation:
+      """
+      A multiple choice poll, implements the Poll interface.
+
+      The poll has a question and a sequence of responses. Votes
+      are stored in a dictionary which maps response indexes to a
+      number of votes.
+      """
+
+      __implements__ = Poll
+
+      def __init__(self, question, responses):
+          self._question = question
+          self._responses = responses
+          self._votes = {}
+          for i in range(len(responses)):
+              self._votes[i] = 0
+
+      def castVote(self, index):
+          "Votes for a choice"
+          self._votes[index] = self._votes[index] + 1
+
+      def getTotalVotes(self):
+          "Returns total number of votes cast"
+          total = 0
+          for v in self._votes.values():
+              total = total + v
+          return total
+
+      def getVotesFor(self, index):
+          "Returns number of votes cast for a given response"
+          return self._votes[index]
+
+      def getResponses(self):
+          "Returns the sequence of responses"
+          return tuple(self._responses)
+
+      def getQuestion(self):
+          "Returns the question"
+          return self._question
+
+You can use this class interactively and test it. Here's an example
+of interactive testing::
+
+  >>> from PollImplementation import PollImplementation
+  >>> p = PollImplementation("What's your favorite color?",
+  ...                        ["Red", "Green", "Blue", "I forget"])
+  >>> p.getQuestion()
+  "What's your favorite color?"
+  >>> p.getResponses()
+  ('Red', 'Green', 'Blue', 'I forget')
+  >>> p.getVotesFor(0)
+  0
+  >>> p.castVote(0)
+  >>> p.getVotesFor(0)
+  1
+  >>> p.castVote(2)
+  >>> p.getTotalVotes()
+  2
+  >>> p.castVote(4)
+  Traceback (innermost last):
+  File "<stdin>", line 1, in ?
+  File "PollImplementation.py", line 23, in castVote
+  self._votes[index] = self._votes[index] + 1
+  KeyError: 4
+
+Interactive testing is one of Python's great features. It lets you
+experiment with your code in a simple but powerful way.
+
+At this point you can do a fair amount of work, testing and refining
+your interfaces and classes which implement them. See Chapter 7 for
+more information on testing.
+
+So far you have learned how to create Python classes that are
+documented with interfaces, and verified with testing. Next you'll
+examine the Zope product architecture. Then you'll learn how to fit
+your well crafted Python classes into the product framework.
+
+Building Product Classes
+------------------------
+
+To turn a component into a product you must fulfill many
+contracts. For the most part these contracts are not yet defined in
+terms of interfaces. Instead you must subclass from base classes that
+implement the contracts. This makes building products confusing, and
+this is an area that we are actively working on improving.
+
+Base Classes
+------------
+
+Consider an example product class definition::
+
+  from Acquisition import Implicit
+  from Globals import Persistent
+  from AccessControl.Role import RoleManager
+  from OFS.SimpleItem import Item
+
+  class PollProduct(Implicit, Persistent, RoleManager, Item):
+      """
+      Poll product class
+      """
+      ...
+
+The order of the base classes depends on which classes you want to
+take precedence over others.  Most Zope classes do not define similar
+names, so you usually don't need to worry about what order these
+classes are used in your product.  Let's take a look at each of these
+base classes:
+
+
+Acquisition.Implicit
+~~~~~~~~~~~~~~~~~~~~
+
+This is the normal acquisition base class. See the *API Reference*
+for the full details on this class. Many Zope services such as object
+publishing and security use acquisition, so inheriting from this
+class is required for products. Actually, you can choose to inherit
+from 'Acquisition.Explicit' if you prefer, however, it will prevent
+folks from dynamically binding Python Scripts and DTML Methods to
+instances of your class. In general you should subclass from
+'Acquisition.Implicit' unless you have a good reason not to.
+
+  XXX: is this true?  I thought that any ExtensionClass.Base can be
+  acquired.  The Implicit and Explicit just control how the class can
+  acquire, not how it *is* acquired.
+
+Globals.Persistent
+~~~~~~~~~~~~~~~~~~
+
+This base class makes instances of your product persistent. For more
+information on persistence and this class see Chapter 4.
+
+In order to make your poll class persistent you'll need to make one
+change. Since '_votes' is a dictionary this means that it's a mutable
+non-persistent sub-object. You'll need to let the persistence
+machinery know when you change it::
+
+  def castVote(self, index):
+      "Votes for a choice"
+      self._votes[index] = self._votes[index] + 1
+      self._p_changed = 1
+
+The last line of this method sets the '_p_changed' attribute to 1.
+This tells the persistence machinery that this object has changed and
+should be marked as 'dirty', meaning that its new state should be
+written to the database at the conclusion of the current transaction.
+A more detailed explanation is given in the Persistence chapter of
+this guide.
+
+
+OFS.SimpleItem.Item
+~~~~~~~~~~~~~~~~~~~
+
+This base class provides your product with the basics needed to work
+with the Zope management interface.  By inheriting from 'Item' your
+product class gains a whole host of features: the ability to be cut
+and pasted, capability with management views, WebDAV support, basic
+FTP support, undo support, ownership support, and traversal controls.
+It also gives you some standard methods for management views and
+error display including 'manage_main()'.  You also get the 'getId()',
+'title_or_id()', 'title_and_id()' methods and the 'this()' DTML
+utility method. Finally this class gives your product basic
+*dtml-tree* tag support.  'Item' is really an
+everything-but-the-kitchen-sink kind of base class.
+
+'Item' requires that your class and instances have some management
+interface related attributes.
+
+'meta_type' -- This attribute should be a short string which is the
+name of your product class as it appears in the product add list. For
+example the poll product class could have a 'meta_type' of 'Poll'.
+
+'id' or '__name__' -- All 'Item' instances must have an 'id' string
+attribute which uniquely identifies the instance within it's
+container. As an alternative you may use '__name__' instead of 'id'.
+
+'title' -- All 'Item' instances must have a 'title' string
+attribute. A title may be an empty string if your instance does not
+have a title.
+
+In order to make your poll class work correctly as an 'Item' you'll
+need to make a few changes. You must add a 'meta_type' class
+attribute, and you may wish to add an 'id' parameter to the
+constructor::
+
+  class PollProduct(..., Item):
+
+      meta_type='Poll'
+      ...
+
+      def __init__(self, id, question, responses):
+          self.id=id
+          self._question = question
+          self._responses = responses
+          self._votes = {}
+          for i in range(len(responses)):
+              self._votes[i] = 0
+
+
+Finally, you should probably place 'Item' last in your list of base
+classes. The reason for this is that 'Item' provides defaults that
+other classes such as 'ObjectManager' and 'PropertyManager'
+override. By placing other base classes before 'Item' you allow them
+to override methods in 'Item'.
+
+AccessControl.Role.RoleManager
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This class provides your product with the ability to have its
+security policies controlled through the web. See Chapter 6 for more
+information on security policies and this class.
+
+OFS.ObjectManager
+~~~~~~~~~~~~~~~~~
+
+This base class gives your product the ability to contain other
+'Item' instances. In other words, it makes your product class like a
+Zope folder. This base class is optional. See the *API Reference* for
+more details. This base class gives you facilities for adding Zope
+objects, importing and exporting Zope objects, WebDAV, and FTP. It
+also gives you the 'objectIds', 'objectValues', and 'objectItems'
+methods.
+
+'ObjectManager' makes few requirements on classes that subclass
+it. You can choose to override some of its methods but there is
+little that you must do.
+
+If you wish to control which types of objects can be contained by
+instances of your product you can set the 'meta_types' class
+attribute. This attribute should be a tuple of meta_types. This keeps
+other types of objects from being created in or pasted into instances
+of your product. The 'meta_types' attribute is mostly useful when you
+are creating specialized container products.
+
+OFS.PropertyManager
+~~~~~~~~~~~~~~~~~~~
+
+This base class provides your product with the ability to have
+user-managed instance attributes. See the *API Reference* for more
+details. This base class is optional.
+
+Your class may specify that it has one or more predefined properties,
+by specifying a '_properties' class attribute. For example::
+
+  _properties=({'id':'title', 'type': 'string', 'mode': 'w'},
+               {'id':'color', 'type': 'string', 'mode': 'w'},
+              )
+
+The '_properties' structure is a sequence of dictionaries, where each
+dictionary represents a predefined property. Note that if a
+predefined property is defined in the '_properties' structure, you
+must provide an attribute with that name in your class or instance
+that contains the default value of the predefined property.
+
+Each entry in the '_properties' structure must have at least an 'id'
+and a 'type' key. The 'id' key contains the name of the property, and
+the 'type' key contains a string representing the object's type.  The
+'type' string must be one of the values: 'float', 'int', 'long',
+'string', 'lines', 'text', 'date', 'tokens', 'selection', or
+'multiple section'. For more information on Zope properties see the
+*Zope Book*.
+
+For 'selection' and 'multiple selection' properties, you must include
+an addition item in the property dictionary, 'select_variable' which
+provides the name of a property or method which returns a list of
+strings from which the selection(s) can be chosen. For example::
+
+  _properties=({'id' : 'favorite_color',
+                'type' : 'selection',
+                'select_variable' : 'getColors'
+               },
+              )
+
+Each entry in the '_properties' structure may optionally provide a
+'mode' key, which specifies the mutability of the property. The
+'mode' string, if present, must be 'w', 'd', or 'wd'.
+
+A 'w' present in the mode string indicates that the value of the
+property may be changed by the user. A 'd' indicates that the user
+can delete the property. An empty mode string indicates that the
+property and its value may be shown in property listings, but that it
+is read-only and may not be deleted.
+
+Entries in the '_properties' structure which do not have a 'mode'
+item are assumed to have the mode 'wd' (writable and deleteable).
+
+Security Declarations
+---------------------
+
+In addition to inheriting from a number of standard base classes, you
+must declare security information in order to turn your component
+into a product. See Chapter 6 for more information on security and
+instructions for declaring security on your components.
+
+Here's an example of how to declare security on the poll class::
+
+  from AccessControl import ClassSecurityInfo
+
+  class PollProduct(...):
+      ...
+
+      security=ClassSecurityInfo()
+
+      security.declareProtected('Use Poll', 'castVote')
+      def castVote(self, index):
+          ...
+
+      security.declareProtected('View Poll results', 'getTotalVotes')
+      def getTotalVotes(self):
+          ...
+
+      security.declareProtected('View Poll results', 'getVotesFor')
+      def getVotesFor(self, index):
+          ...
+
+      security.declarePublic('getResponses')
+      def getResponses(self):
+          ...
+
+      security.declarePublic('getQuestion')
+      def getQuestion(self):
+          ...
+
+For security declarations to be set up Zope requires that you
+initialize your product class. Here's how to initialize your poll
+class::
+
+  from Globals import InitializeClass
+
+  class PollProduct(...):
+     ...
+
+  InitializeClass(PollProduct)
+
+Summary
+-------
+
+Congratulations, you've created a product class. Here it is in all
+its glory (see "PollProduct.py":examples/PollProduct.py)::
+
+  from Poll import Poll
+  from AccessControl import ClassSecurityInfo
+  from Globals import InitializeClass
+  from Acquisition import Implicit
+  from Globals import Persistent
+  from AccessControl.Role import RoleManager
+  from OFS.SimpleItem import Item
+
+  class PollProduct(Implicit, Persistent, RoleManager, Item):
+      """
+      Poll product class, implements Poll interface.
+
+      The poll has a question and a sequence of responses. Votes
+      are stored in a dictionary which maps response indexes to a
+      number of votes.
+      """
+
+      __implements__=Poll
+
+      meta_type='Poll'
+
+      security=ClassSecurityInfo()
+
+      def __init__(self, id, question, responses):
+          self.id=id
+          self._question = question
+          self._responses = responses
+          self._votes = {}
+          for i in range(len(responses)):
+              self._votes[i] = 0
+
+      security.declareProtected('Use Poll', 'castVote')
+      def castVote(self, index):
+          "Votes for a choice"
+          self._votes[index] = self._votes[index] + 1
+          self._p_changed = 1
+
+      security.declareProtected('View Poll results', 'getTotalVotes')
+      def getTotalVotes(self):
+          "Returns total number of votes cast"
+          total = 0
+          for v in self._votes.values():
+              total = total + v
+          return total
+
+      security.declareProtected('View Poll results', 'getVotesFor')
+      def getVotesFor(self, index):
+          "Returns number of votes cast for a given response"
+          return self._votes[index]
+
+      security.declarePublic('getResponses')
+      def getResponses(self):
+          "Returns the sequence of responses"
+          return tuple(self._responses)
+
+      security.declarePublic('getQuestion')
+      def getQuestion(self):
+          "Returns the question"
+          return self._question
+
+  InitializeClass(Poll)
+
+Now it's time to test your product class in Zope. To do this you must
+register your product class with Zope.
+
+Registering Products
+====================
+
+Products are Python packages that live in 'lib/python/Products'.
+Products are loaded into Zope when Zope starts up. This process is
+called *product initialization*. During product initialization, each
+product is given a chance to register its capabilities with Zope.
+
+Product Initialization
+----------------------
+
+When Zope starts up it imports each product and calls the product's
+'initialize' function passing it a registrar object. The 'initialize'
+function uses the registrar to tell Zope about its capabilities. Here
+is an example '__init__.py' file::
+
+  from PollProduct import PollProduct, addForm, addFunction
+
+  def initialize(registrar):
+      registrar.registerClass(
+          PollProduct,
+          constructors = (addForm, addFunction),
+          )
+
+This function makes one call to the registrar object which registers
+a class as an addable object.  The registrar figures out the name to
+put in the product add list by looking at the 'meta_type' of the
+class. Zope also deduces a permission based on the class's meta-type,
+in this case *Add Polls* (Zope automatically pluralizes "Poll" by
+adding an "s").  The 'constructors' argument is a tuple of objects
+consisting of two functions: an add form which is called when a user
+selects the object from the product add list, and the add method
+which is the method called by the add form. Note that these functions
+are protected by the constructor permission.
+
+Note that you cannot restrict which types of containers can contain
+instances of your classes. In other words, when you register a class,
+it will appear in the product add list in folders if the user has the
+constructor permission.
+
+See the *API Reference* for more information on the
+'ProductRegistrar' interface.
+
+Factories and Constructors
+--------------------------
+
+Factories allow you to create Zope objects that can be added to
+folders and other object managers. Factories are discussed in Chapter
+12 of the *Zope Book*. The basic work a factory does is to put a name
+into the product add list and associate a permission and an action
+with that name. If you have the required permission then the name
+will appear in the product add list, and when you select the name
+from the product add list, the action method will be called.
+
+Products use Zope factory capabilities to allow instances of product
+classes to be created with the product add list.  In the above
+example of product initialization you saw how a factory is created by
+the product registrar. Now let's see how to create the add form and
+the add list.
+
+The add form is a function that returns an HTML form that allows a
+users to create an instance of your product class. Typically this
+form collects that id and title of the instance along with other
+relevant data. Here's a very simple add form function for the poll
+class::
+
+        def addForm():
+            """
+            Returns an HTML form.
+            """
+            return """<html>
+            <head><title>Add Poll</title></head>
+            <body>
+            <form action="addFunction">
+            id <input type="type" name="id"><br>
+            question <input type="type" name="question"><br>
+            responses (one per line)
+            <textarea name="responses:lines"></textarea>
+            </form>
+            </body>
+            </html>"""
+
+Notice how the action of the form is 'addFunction'. Also notice how
+the lines of the response are marshalled into a sequence. See Chapter
+2 for more information about argument marshalling and object
+publishing.
+
+It's also important to include a HTML 'head' tag in the add
+form. This is necessary so that Zope can set the base URL to make
+sure that the relative link to the 'addFunction' works correctly.
+
+The add function will be passed a 'FactoryDispatcher' as its first
+argument which proxies the location (usually a Folder) where your
+product was added. The add function may also be passed any form
+variables which are present in your add form according to normal
+object publishing rules.
+
+Here's an add function for your poll class::
+
+  def addFunction(dispatcher, id, question, responses):
+      """Create a new poll and add it to myself
+      """
+      p = PollProduct(id, question, responses)
+      dispatcher.Destination()._setObject(id, p)
+
+The dispatcher has three methods:
+
+'Destination' -- The 'ObjectManager' where your product was added.
+
+'DestinationURL' -- The URL of the 'ObjectManager' where your product
+was added.
+
+'manage_main' -- Redirects to a management view of the
+'ObjectManager' where your product was added.
+
+Notice how it calls the '_setObject()' method of the destination
+'ObjectManager' class to add the poll to the folder. See the *API
+Reference* for more information on the 'ObjectManager' interface.
+
+
+The add function should also check the validity of its input. For
+example the add function should complain if the question or response
+arguments are not of the correct type.
+
+Finally you should recognize that the constructor functions are *not*
+methods on your product class. In fact they are called before any
+instances of your product class are created. The constructor
+functions are published on the web so they need to have doc strings,
+and are protected by a permission defined in during product
+initialization.
+
+Testing
+-------
+
+Now you're ready to register your product with Zope. You need to add
+the add form and add method to the poll module. Then you should
+create a 'Poll' directory in your 'lib/python/Products' directory and
+add the 'Poll.py', 'PollProduct.py', and '__init__.py' files. Then
+restart Zope.
+
+Now login to Zope as a manager and visit the web management
+interface. You should see a 'Poll' product listed inside the
+*Products* folder in the *Control_Panel*. If Zope had trouble
+initializing your product you will see a traceback here. Fix your
+problems, if any and restart Zope. If you are tired of all this
+restarting, take a look at the *Refresh* facility covered in Chapter
+7.
+
+Now go to the root folder. Select *Poll* from the product add
+list. Notice how you are taken to the add form. Provide an id, a
+question, and a list of responses and click *Add*. Notice how you get
+a black screen. This is because your add method does not return
+anything. Notice also that your poll has a broken icon, and only has
+the management views. Don't worry about these problems now, you'll
+find out how to fix these problems in the next section.
+
+Now you should build some DTML Methods and Python Scripts to test
+your poll instance. Here's a Python Script to figure out voting
+percentages::
+
+        ## Script (Python) "getPercentFor"
+        ##parameters=index
+        ##
+        """Returns the percentage of the vote given a response index. Note,
+        this script should be bound a poll by acquisition context."""
+        poll = context
+        return float(poll.getVotesFor(index)) / poll.getTotalVotes()
+
+
+Here's a DTML Method that displays poll results and allows you to
+vote::
+
+        <dtml-var standard_html_header>
+
+        <h2>
+          <dtml-var getQuestion>
+        </h2>
+
+        <form> <!-- calls this dtml method -->
+
+        <dtml-in getResponses>
+          <p>
+            <input type="radio" name="index" value="&dtml-sequence-index;">
+            <dtml-var sequence-item>
+          </p>
+        </dtml-in>
+
+        <input type="submit" value=" Vote ">
+
+        </form>
+
+        <!-- process form -->
+
+        <dtml-if index>
+          <dtml-call expr="castVote(index)">
+        </dtml-if>
+
+        <!-- display results -->
+
+        <h2>Results</h2>
+
+        <p><dtml-var getTotalVotes> votes cast</p>
+
+        <dtml-in getResponses>
+          <p>
+            <dtml-var sequence-item> -
+            <dtml-var expr="getPercentFor(_.get('sequence-index'))">%
+          </p>
+        </dtml-in>
+
+        <dtml-var standard_html_footer>
+
+To use this DTML Method, call it on your poll instance. Notice how
+this DTML makes calls to both your poll instance and the
+'getPercentFor' Python script.
+
+
+At this point there's quite a bit of testing and refinement that you
+can do. Your main annoyance will be having to restart Zope each time
+you make a change to your product class (but see Chapter 7 for
+information on how to avoid all this restarting). If you vastly
+change your class you may break existing poll instances, and will
+need to delete them and create new ones. See Chapter 7 for more
+information on debugging techniques which will come in handy.
+
+Building Management Interfaces
+------------------------------
+
+Now that you have a working product let's see how to beef up its user
+interface and create online management facilities.
+
+Defining Management Views
+-------------------------
+
+All Zope products can be managed through the web. Products have a
+collection of management tabs or *views* which allow managers to
+control different aspects of the product.
+
+A product's management views are defined in the 'manage_options'
+class attribute. Here's an example::
+
+        manage_options=(
+            {'label' : 'Edit', 'action' : 'editMethod'},
+            {'label' : 'View', 'action' : 'viewMethod'},
+            )
+
+The 'manage_options' structure is a tuple that contains
+dictionaries. Each dictionary defines a management view. The view
+dictionary can have a number of items.
+
+'label' -- This is the name of the management view
+
+'action' -- This is the URL that is called when the view is
+chosen. Normally this is the name of a method that displays a
+management view.
+
+'target' -- An optional target frame to display the action. This item
+is rarely needed.
+
+'help' -- Optional help information associated with the view. You'll
+find out more about this option later.
+
+
+Management views are displayed in the order they are
+defined. However, only those management views for which the current
+user has permissions are displayed. This means that different users
+may see different management views when managing your product.
+
+Normally you will define a couple custom views and reusing some
+existing views that are defined in your base classes. Here's an
+example::
+
+  class PollProduct(..., Item):
+      ...
+
+      manage_options=(
+          {'label' : 'Edit', 'action' : 'editMethod'},
+          {'label' : 'Options', 'action' : 'optionsMethod'},
+          ) + RoleManager.manage_options + Item.manage_options
+
+This example would include the standard management view defined by
+'RoleManager' which is *Security* and those defined by 'Item' which
+are *Undo* and *Ownership*. You should include these standard
+management views unless you have good reason not to. If your class
+has a default view method ('index_html') you should also include a
+*View* view whose action is an empty string. See Chapter 2 for more
+information on 'index_html'.
+
+Note: you should not make the *View* view the first view on your
+class. The reason is that the first management view is displayed when
+you click on an object in the Zope management interface. If the
+*View* view is displayed first, users will be unable to navigate to
+the other management views since the view tabs will not be visible.
+
+Creating Management Views
+-------------------------
+
+The normal way to create management view methods is to use DTML. You
+can use the 'DTMLFile' class to create a DTML Method from a file. For
+example::
+
+  from Globals import DTMLFile
+
+  class PollProduct(...):
+    ...
+
+    editForm=DTMLFile('dtml/edit', globals())
+    ...
+
+This creates a DTML Method on your class which is defined in the
+'dtml/edit.dtml' file. Notice that you do not have to include the
+'.dtml' file extension. Also, don't worry about the forward slash as
+a path separator; this convention will work fine on Windows. By
+convention DTML files are placed in a 'dtml' subdirectory of your
+product. The 'globals()' argument to the 'DTMLFile' constructor
+allows it to locate your product directory. If you are running Zope
+in debug mode then changes to DTML files are reflected right away. In
+other words you can change the DTML of your product's views without
+restarting Zope to see the changes.
+
+
+DTML class methods are callable directly from the web, just like
+other methods. So now users can see your edit form by calling the
+'editForm' method on instances of your poll class. Typically DTML
+methods will make calls back to your instance to gather information
+to display. Alternatively you may decide to wrap your DTML methods
+with normal methods. This allows you to calculate information needed
+by your DTML before you call it. This arrangement also ensures that
+users always access your DTML through your wrapper. Here's an
+example::
+
+  from Globals import DTMLFile
+
+  class PollProduct(...):
+    ...
+
+    _editForm=DTMLFile('dtml/edit', globals())
+
+    def editForm(self, ...):
+        ...
+
+        return self._editForm(REQUEST, ...)
+
+
+When creating management views you should include the DTML variables
+'manage_page_header' and 'manage_tabs' at the top, and
+'manage_page_footer' at the bottom.  These variables are acquired by
+your product and draw a standard management view header, tabs
+widgets, and footer. The management header also includes CSS
+information which you can take advantage of if you wish to add CSS
+style information to your management views. The management CSS
+information is defined in the
+'lib/python/App/dtml/manage_page_style.css.dtml' file. Here are the
+CSS classes defined in this file and conventions for their use.
+
+'form-help' -- Explanatory text related to forms. In the future,
+users may have the option to hide this text.
+
+'std-text' -- Declarative text unrelated to forms. You should rarely
+use this class.
+
+'form-title' -- Form titles.
+
+'form-label' -- Form labels for required form elements.
+
+'form-optional' -- Form labels for optional form elements.
+
+'form-element' -- Form elements. Note, because of a Netscape bug, you
+should not use this class on 'textarea' elements.
+
+'form-text' -- Declarative text in forms.
+
+'form-mono' -- Fixed width text in forms. You should rarely use this
+class.
+
+Here's an example management view for your poll class. It allows you
+to edit the poll question and responses (see 'editPollForm.dtml')::
+
+        <dtml-var manage_page_header>
+        <dtml-var manage_tabs>
+
+        <p class="form-help">
+        This form allows you to change the poll's question and
+        responses. <b>Changing a poll's question and responses
+        will reset the poll's vote tally.</b>.
+        </p>
+
+        <form action="editPoll">
+        <table>
+
+          <tr valign="top">
+            <th class="form-label">Question</th>
+            <td><input type="text" name="question" class="form-element"
+            value="&dtml-getQuestion;"></td>
+          </tr>
+
+          <tr valign="top">
+            <th class="form-label">Responses</th>
+            <td><textarea name="responses:lines" cols="50" rows="10">
+            <dtml-in getResponses>
+            <dtml-var sequence-item html_quote>
+            </dtml-in>
+            </textarea>
+            </td>
+          </tr>
+
+          <tr>
+            <td></td>
+            <td><input type="submit" value="Change" class="form-element"></td>
+          </tr>
+
+        </table>
+        </form>
+
+        <dtml-var manage_page_header>
+
+This DTML method displays an edit form that allows you to change the
+questions and responses of your poll. Notice how poll properties are
+HTML quoted either by using 'html_quote' in the 'dtml-var' tag, or by
+using the 'dtml-var' entity syntax.
+
+
+Assuming this DTML is stored in a file 'editPollForm.dtml' in your
+product's 'dtml' directory, here's how to define this method on your
+class::
+
+        class PollProduct(...):
+            ...
+
+            security.declareProtected('View management screens', 'editPollForm')
+            editPollForm=DTML('dtml/editPollForm', globals())
+
+Notice how the edit form is protected by the 'View management
+screens' permission. This ensures that only managers will be able to
+call this method.
+
+Notice also that the action of this form is 'editPoll'. Since the
+poll as it stands doesn't include any edit methods you must define
+one to accept the changes. Here's an 'editPoll' method::
+
+        class PollProduct(...):
+            ...
+
+            def __init__(self, id, question, responses):
+                self.id=id
+                self.editPoll(question, response)
+
+            ...
+
+            security.declareProtected('Change Poll', 'editPoll')
+            def editPoll(self, question, responses):
+                """
+                Changes the question and responses.
+                """
+                self._question = question
+                self._responses = responses
+                self._votes = {}
+                for i in range(len(responses)):
+                    self._votes[i] = 0
+
+Notice how the '__init__' method has been refactored to use the new
+'editPoll' method. Also notice how the 'editPoll' method is protected
+by a new permissions, 'Change Poll'.
+
+There still is a problem with the 'editPoll' method. When you call it
+from the 'editPollForm' through the web nothing is returned. This is
+a bad management interface. You want this method to return an HTML
+response when called from the web, but you do not want it to do this
+when it is called from '__init__'. Here's the solution::
+
+        class Poll(...):
+            ...
+
+            def editPoll(self, question, responses, REQUEST=None):
+                """
+                Changes the question and responses.
+                """
+                self._question = question
+                self._responses = responses
+                self._votes = {}
+                for i in range(len(responses)):
+                    self._votes[i] = 0
+                if REQUEST is not None:
+                    return self.editPollForm(REQUEST,
+                        manage_tabs_message='Poll question and responses changed.')
+
+If this method is called from the web, then Zope will automatically
+supply the 'REQUEST' parameter. (See chapter 2 for more information
+on object publishing). By testing the 'REQUEST' you can find out if
+your method was called from the web or not. If you were called from
+the web you return the edit form again.
+
+A management interface convention that you should use is the
+'manage_tab_message' DTML variable. If you set this variable when
+calling a management view, it displays a status message at the top of
+the page. You should use this to provide feedback to users indicating
+that their actions have been taken when it is not obvious. For
+example if you don't return a status message from your 'editPoll'
+method, users may be confused and may not realize that their changes
+have been made.
+
+Sometimes when displaying management views, the wrong tab will be
+highlighted. This is because 'manage_tabs' can't figure out from the
+URL which view should be highlighted. The solution is to set the
+'management_view' variable to the label of the view that should be
+highlighted. Here's an example, using the 'editPoll' method::
+
+        def editPoll(self, question, responses, REQUEST=None):
+            """
+            Changes the question and responses.
+            """
+            self._question = question
+            self._responses = responses
+            self._votes = {}
+            for i in range(len(responses)):
+                self._votes[i] = 0
+            if REQUEST is not None:
+                return self.editPollForm(REQUEST,
+                    management_view='Edit',
+                    manage_tabs_message='Poll question and responses changed.')
+
+Now let's take a look a how to define an icon for your product.
+
+Icons
+-----
+
+Zope products are identified in the management interface with
+icons. An icon should be a 16 by 16 pixel GIF image with a
+transparent background. Normally icons files are located in a 'www'
+subdirectory of your product package. To associate an icon with a
+product class, use the 'icon' parameter to the 'registerClass' method
+in your product's constructor. For example::
+
+        def initialize(registrar):
+            registrar.registerClass(
+                PollProduct,
+                constructors = (addForm, addFunction),
+                icon = 'www/poll.gif'
+                )
+
+Notice how in this example, the icon is identified as being within
+the product's 'www' subdirectory.
+
+See the *API Reference* for more information on the 'registerClass'
+method of the 'ProductRegistrar' interface.
+
+Online Help
+-----------
+
+Zope has an online help system that you can use to provide help for
+your products. Its main features are context-sensitive help and API
+help. You should provide both for your product.
+
+
+Context Sensitive Help
+----------------------
+
+To create context sensitive help, create one help file per management
+view in your product's 'help' directory. You have a choice of formats
+including: HTML, DTML, structured text, GIF, JPG, and PNG.
+
+Register your help files at product initialization with the
+'registerHelp()' method on the registrar object::
+
+          def initialize(registrar):
+              ...
+              registrar.registerHelp()
+
+This method will take care of locating your help files and creating
+help topics for each help file. It can recognize these file
+extensions: '.html', '.htm', '.dtml', '.txt', '.stx', '.gif', '.jpg',
+'.png'.
+
+If you want more control over how your help topics are created you
+can use the 'registerHelpTopic()' method which takes an id and a help
+topic object as arguments. For example::
+
+          from mySpecialHelpTopics import MyTopic
+
+          def initialize(context):
+              ...
+              context.registerHelpTopic('myTopic', MyTopic())
+
+Your help topic should adhere to the 'HelpTopic' interface. See the
+*API Reference* for more details.
+
+The chief way to bind a help topic to a management screen is to
+include information about the help topic in the class's
+manage_options structure. For example::
+
+  manage_options=(
+      {'label':'Edit',
+       'action':'editMethod',
+       'help':('productId','topicId')},
+      )
+
+The 'help' value should be a tuple with the name of your product's
+Python package, and the file name (or other id) of your help
+topic. Given this information, Zope will automatically draw a *Help*
+button on your management screen and link it to your help topic.
+
+To draw a help button on a management screen that is not a view (such
+as an add form), use the 'HelpButton' method of the 'HelpSys' object
+like so::
+
+  <dtml-var "HelpSys.HelpButton('productId', 'topicId')">
+
+This will draw a help button linked to the specified help topic. If
+you prefer to draw your own help button you can use the helpURL
+method instead like so::
+
+  <dtml-var "HelpSys.helpURL(
+    topic='productId',
+    product='topicId')">
+
+This will give you a URL to the help topic.  You can choose to draw
+whatever sort of button or link you wish.
+
+Other User Interfaces
+---------------------
+
+In addition to providing a through the web management interface your
+products may also support many other user interfaces. You product
+might have no web management interfaces, and might be controlled
+completely through some other network protocol. Zope provides
+interfaces and support for FTP, WebDAV and XML-RPC. If this isn't
+enough you can add other protocols.
+
+FTP and WebDAV Interfaces
+-------------------------
+
+Both FTP and WebDAV treat Zope objects like files and
+directories. See Chapter 2 for more information on FTP and WebDAV.
+
+By simply sub-classing from 'SimpleItem.Item' and 'ObjectManager' if
+necessary, you gain basic FTP and WebDAV support. Without any work
+your objects will appear in FTP directory listings and if your class
+is an 'ObjectManager' its contents will be accessible via FTP and
+WebDAV.  See Chapter 2 for more information on implementing FTP and
+WebDAV support.
+
+XML-RPC and Network Services
+----------------------------
+
+XML-RPC is covered in Chapter 2. All your product's methods can be
+accessible via XML-RPC. However, if your are implementing network
+services, you should explicitly plan one or more methods for use with
+XML-RPC.
+
+Since XML-RPC allows marshalling of simple strings, lists, and
+dictionaries, your XML-RPC methods should only accept and return
+these types. These methods should never accept or return Zope
+objects. XML-RPC also does not support 'None' so you should use zero
+or something else in place of 'None'.
+
+Another issue to consider when using XML-RPC is security. Many
+XML-RPC clients still don't support HTTP basic
+authorization. Depending on which XML-RPC clients you anticipate, you
+may wish to make your XML-RPC methods public and accept
+authentication credentials as arguments to your methods.
+
+Content Management Framework Interface
+--------------------------------------
+
+The "Content Management Framework":http://cmf.zope.org is an evolving
+content management extension for Zope. It provides a number of
+interfaces and conventions for content objects. If you wish to
+support the CMF you should consult the CMF user interface guidelines
+and interface documentation.
+
+
+Supporting the CMF interfaces is not a large burden if you already
+support the Zope management interface. You should consider supporting
+the CMF if your product class handles user manageable content such as
+documents, images, business forms, etc.
+
+Packaging Products
+------------------
+
+Zope products are normally packaged as tarballs. You should create
+your product tarball in such a way as to allow it to be unpacked in
+the Products directory. For example, 'cd' to the Products directory
+and then issue a 'tar' comand like so::
+
+  $ tar zcvf MyProduct-1.0.1.tgz MyProduct
+
+This will create a gzipped tar archive containing your product. You
+should include your product name and version number in file name of
+the archive.
+
+See the "Poll-1.0.tgz":examples/Poll-1.0.tgz file for an example of a
+fully packaged Python product.
+
+
+Product Information Files
+-------------------------
+
+Along with your Python and DTML files you should include some
+information about your product in its root directory.
+
+'README.txt' -- Provides basic information about your product.  Zope
+will parse this file as "structured
+text":http://www.zope.org/Members/millejoh/structuredText and make it
+available on the *README* view of your product in the control panel.
+
+'VERSION.txt' -- Contains the name and version of your product on a
+single line. For example, 'Multiple Choice Poll 1.1.0'.  Zope will
+display this information as the 'version' property of your product in
+the control panel.
+
+'LICENSE.txt' -- Contains your product license, or a link to it.
+
+You may also wish to provide additional information. Here are some
+suggested optional files to include with your product.
+
+'INSTALL.txt' -- Provides special instructions for installing
+the product and components on which it depends. This file is
+only optional if your product does not require more than an
+ungzip/untar into a Zope installation to work.
+
+'TODO.txt' -- This file should make clear where this product
+release needs work, and what the product author intends to do
+about it.
+
+'CHANGES.txt' and 'HISTORY.txt' -- 'CHANGES.txt' should
+enumerate changes made in particular product versions from the
+last release of the product. Optionally, a 'HISTORY.txt' file
+can be used for older changes, while 'CHANGES.txt' lists only
+recent changes.
+
+'DEPENDENCIES.txt' -- Lists dependencies including required os
+platform, required Python version, required Zope version,
+required Python packages, and required Zope products.
+
+Product Directory Layout
+------------------------
+
+By convention your product will contain a number of
+sub-directories. Some of these directories have already been
+discussed in this chapter. Here is a summary of them.
+
+  'dtml' -- Contains your DTML files.
+
+  'www' -- Contains your icon files.
+
+  'help' -- Contains your help files.
+
+  'tests' -- Contains your unit tests.
+
+It is not necessary to include these directories if your don't have
+anything to go in them.
+
+
+Product Frameworks
+==================
+
+Creating Zope products is a complex business. There are a number of
+frameworks available to help ease the burden of creating products.
+Different frameworks focus on different aspects of product
+construction.
+
+ZClass Base Classes
+-------------------
+
+As an alternative to creating full blown products you may choose to
+create Python base classes which can be used by ZClasses. This allows
+you to focus on application logic and use ZClasses to take care of
+management interface issues.
+
+The chief drawback to this approach is that your code will be split
+between a ZClass and a Python base class. This makes it harder to
+edit and to visualize.
+
+See the *Zope Book* for more information on ZClasses.
+
+TransWarp and ZPatterns
+-----------------------
+
+TransWarp and ZPatterns are two related product framework packages by
+Phillip Eby and Ty Sarna.  You can find out more information on
+TransWarp from the "TransWarp Home
+Page":http://www.zope.org/Members/pje/Wikis/TransWarp/HomePage.  More
+information on ZPatterns can be found at the "ZPatterns Home
+Page":http://www.zope.org/Members/pje/Wikis/ZPatterns/HomePage
+
+Evolving Products
+=================
+
+As you develop your product classes you will generally make a series
+of product releases. While you don't know in advance how your product
+will change, when it does change there are measures that you can take
+to minimize problems.
+
+Evolving Classes
+----------------
+
+Issues can occur when you change your product class because instances
+of these classes are generally persistent. This means that instances
+created with an old class will start using a new class. If your class
+changes drastically this can break existing instances.
+
+The simplest way to handle this situation is to provide class
+attributes as defaults for newly added attributes. For example if the
+latest version of your class expects an 'improved_spam' instance
+attribute while earlier versions only sported 'spam' attributes, you
+may wish to define an 'improved_spam' class attribute in your new
+class so your old objects won't break when they run with your new
+class. You might set 'improved_spam' to None in your class, and in
+methods where you use this attribute you may have to take into
+account that it may be None. For example::
+
+  class Sandwich(...):
+
+      improved_spam=None
+      ...
+
+      def assembleSandwichMeats(self):
+          ...
+          # test for old sandwich instances
+          if self.improved_spam is None:
+              self.updateToNewSpam()
+          ...
+
+Another solution is to use the standard Python pickling hook
+'__setstate__', however, this is in general more error prone and
+complex.
+
+A third option is to create a method to update old instances. Then
+you can manually call this method on instances to update to
+them. Note, this won't work unless the instances function well enough
+to be accessible via the Zope management screens.
+
+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 is breakage caused by renaming
+your product classes. You should avoid this since it breaks all
+existing instances. If you really must change your class name,
+provide aliases to it using the old name.  You may however, change
+your class's base classes without causing these kinds of problems.
+
+Evolving Interfaces
+-------------------
+
+The basic rule of evolving interfaces is *don't do it*. While you are
+working privately you can change your interfaces all you wish. But as
+soon as you make your interfaces public you should freeze them. The
+reason is that it is not fair to users of your interfaces to changes
+them after the fact. An interface is contract. It specifies how to
+use a component and it specifies how to implement types of
+components. Both users and developers will have problems if your
+change the interfaces they are using or implementing.
+
+The general solution is to create simple interfaces in the first
+place, and create new ones when you need to change an existing
+interface. If your new interfaces are compatible with your existing
+interfaces you can indicate this by making your new interfaces extend
+your old ones. If your new interface replaces an old one but does not
+extend it you should give it a new name such as,
+'WidgetWithBellsOn'. Your components should continue to support the
+old interface in addition to the new one for a few releases.
+
+Conclusion
+==========
+
+Migrating your components into fully fledged Zope products is a
+process with a number of steps. There are many details to keep track
+of. However, if you follow the recipe laid out in this chapter you
+should have no problems.
+
+As Zope grows and evolves we want to simplify the Zope development
+model. We hope to remove much of the management interface details
+from product development. We also want to move to a fuller component
+framework that makes better use of interfaces.
+
+Nevertheless, Zope products are a powerful framework for building web
+applications. By creating products you can take advantage of Zope's
+features including security, scalability, through the web management,
+and collaboration.


Property changes on: zdgbook/trunk/source/Products.rst
___________________________________________________________________
Added: svn:mergeinfo
   + 

Modified: zdgbook/trunk/source/index.rst
===================================================================
--- zdgbook/trunk/source/index.rst	2009-02-17 13:05:50 UTC (rev 96641)
+++ zdgbook/trunk/source/index.rst	2009-02-17 13:16:30 UTC (rev 96642)
@@ -14,6 +14,7 @@
    Introduction.rst
    ComponentsAndInterfaces.rst
    ObjectPublishing.rst
+   Products.rst 
    AppendixA.rst
    AppendixB.rst
 



More information about the Checkins mailing list