[Checkins] SVN: z3c.form/trunk/ - fix coverage report generator script buildout setup

Roger Ineichen roger at projekt01.ch
Sat Oct 29 20:58:59 UTC 2011


Log message for revision 123178:
  - fix coverage report generator script buildout setup
  
  - Note: z3c.pt and chameleon are not fully compatible right now ith TAL.
    Traversing the repeat wwrapper is not done the same way. ZPT uses the
    following pattern:
    <tal:block condition="not:repeat/value/end">, </tal:block>
   
    Chameleon only supports python style traversing:
    <tal:block condition="not:python:repeat['value'].end">, </tal:block>
  
  - Upgrade to chameleon 2.0 template engine and use the newest z3c.pt and
    z3c.ptcompat packages adjusted to work with chameleon 2.0.
    
    See the notes from the z3c.ptcompat package:
  
    Update z3c.ptcompat implementation to use component-based template engine
    configuration, plugging directly into the Zope Toolkit framework.
  
    The z3c.ptcompat package no longer provides template classes, or ZCML
    directives; you should import directly from the ZTK codebase.
  
    Also, note that the ``PREFER_Z3C_PT`` environment option has been
    rendered obsolete; instead, this is now managed via component
    configuration.
    
    Attention: You need to include the configure.zcml file from z3c.ptcompat
    for enable the z3c.pt template engine. The configure.zcml will plugin the 
    template engine. Also remove any custom built hooks which will import
    z3c.ptcompat in your tests or other places.
    
    You can directly use the BoundPageTemplate and ViewPageTempalteFile from
    zope.browserpage.viewpagetemplatefile if needed. This templates will implicit
    use the z3c.pt template engine if the z3c.ptcompat configure.zcml is
    loaded.

Changed:
  U   z3c.form/trunk/CHANGES.txt
  U   z3c.form/trunk/benchmark/benchmark/tests.py
  U   z3c.form/trunk/buildout.cfg
  U   z3c.form/trunk/setup.py
  U   z3c.form/trunk/src/z3c/form/adding.txt
  U   z3c.form/trunk/src/z3c/form/browser/object.txt
  U   z3c.form/trunk/src/z3c/form/browser/objectmulti.txt
  U   z3c.form/trunk/src/z3c/form/browser/tests.py
  U   z3c.form/trunk/src/z3c/form/compatibility.py
  U   z3c.form/trunk/src/z3c/form/contentprovider.py
  U   z3c.form/trunk/src/z3c/form/contentprovider.txt
  U   z3c.form/trunk/src/z3c/form/error.py
  U   z3c.form/trunk/src/z3c/form/error.txt
  A   z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt
  U   z3c.form/trunk/src/z3c/form/form.py
  U   z3c.form/trunk/src/z3c/form/form.txt
  U   z3c.form/trunk/src/z3c/form/group.txt
  U   z3c.form/trunk/src/z3c/form/object.py
  D   z3c.form/trunk/src/z3c/form/ptcompat.py
  U   z3c.form/trunk/src/z3c/form/subform.txt
  U   z3c.form/trunk/src/z3c/form/testing.py
  U   z3c.form/trunk/src/z3c/form/tests/test_doc.py
  U   z3c.form/trunk/src/z3c/form/widget.py
  U   z3c.form/trunk/src/z3c/form/zcml.txt

-=-
Modified: z3c.form/trunk/CHANGES.txt
===================================================================
--- z3c.form/trunk/CHANGES.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/CHANGES.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -2,12 +2,45 @@
 CHANGES
 =======
 
-2.4.5 (unreleased)
+2.5.0 (2011-10-29)
 ------------------
 
-- Nothing changed yet.
+- fix coverage report generator script buildout setup
 
+- Note: z3c.pt and chameleon are not fully compatible right now ith TAL.
+  Traversing the repeat wwrapper is not done the same way. ZPT uses the
+  following pattern:
+  <tal:block condition="not:repeat/value/end">, </tal:block>
+ 
+  Chameleon only supports python style traversing:
+  <tal:block condition="not:python:repeat['value'].end">, </tal:block>
 
+- Upgrade to chameleon 2.0 template engine and use the newest z3c.pt and
+  z3c.ptcompat packages adjusted to work with chameleon 2.0.
+  
+  See the notes from the z3c.ptcompat package:
+
+  Update z3c.ptcompat implementation to use component-based template engine
+  configuration, plugging directly into the Zope Toolkit framework.
+
+  The z3c.ptcompat package no longer provides template classes, or ZCML
+  directives; you should import directly from the ZTK codebase.
+
+  Also, note that the ``PREFER_Z3C_PT`` environment option has been
+  rendered obsolete; instead, this is now managed via component
+  configuration.
+  
+  Attention: You need to include the configure.zcml file from z3c.ptcompat
+  for enable the z3c.pt template engine. The configure.zcml will plugin the 
+  template engine. Also remove any custom built hooks which will import
+  z3c.ptcompat in your tests or other places.
+  
+  You can directly use the BoundPageTemplate and ViewPageTempalteFile from
+  zope.browserpage.viewpagetemplatefile if needed. This templates will implicit
+  use the z3c.pt template engine if the z3c.ptcompat configure.zcml is
+  loaded.
+
+
 2.4.4 (2011-07-11)
 ------------------
 

Modified: z3c.form/trunk/benchmark/benchmark/tests.py
===================================================================
--- z3c.form/trunk/benchmark/benchmark/tests.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/benchmark/benchmark/tests.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -8,16 +8,16 @@
 sys.setdefaultencoding('utf-8')
 
 import zope.configuration.xmlconfig
+import zope.interface
+import zope.component
+import zope.component.globalregistry
+import zope.schema
+from zope.pagetemplate.interfaces import IPageTemplateEngine
+from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
+
 import z3c.pt
-
-from zope import interface
-from zope import component
-from zope import schema
-
-from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
-
+import z3c.ptcompat.engine
 from z3c.pt.pagetemplate import ViewPageTemplateFile as z3cViewPageTemplateFile
-import z3c.ptcompat as compat
 
 from z3c.form import form
 from z3c.form import term
@@ -44,32 +44,32 @@
         t2 = time.time()
     return 100*(t2-t1)/i
 
-class ISmallForm(interface.Interface):
-    name = schema.TextLine(
+class ISmallForm(zope.interface.Interface):
+    name = zope.schema.TextLine(
         title=u"Name",
         description=u"Please enter your first and last name.")
 
-    address = schema.TextLine(
+    address = zope.schema.TextLine(
         title=u"Address",
         description=u"Please enter a valid address.")
 
-class ILargeDataSetsForm(interface.Interface):
-    lucky_numer = schema.Choice(
+class ILargeDataSetsForm(zope.interface.Interface):
+    lucky_numer = zope.schema.Choice(
         range(500),
         title=u"Lucky number",
         description=u"Choose your lucky number.")
 
-    favorite_letters = schema.Set(
+    favorite_letters = zope.schema.Set(
         title=u"Favorite letter",
         description=u"Choose your favorite letter.",
-        value_type=schema.Choice(
+        value_type=zope.schema.Choice(
            ["".join(chr(random.randint(65, 90)) for i in range(10))]
         ))
 
 def build_many_fields(size):
     for i in range(size):
         name = "".join(chr(random.randint(65, 90)) for i in range(10))
-        yield schema.TextLine(
+        yield zope.schema.TextLine(
             __name__=name,
             description=u"This field renders %s" % name,
             title=u"Title of %s" % name.capitalize())
@@ -81,11 +81,25 @@
         testing.setUp(suite)
         testing.setupFormDefaults()
         zope.configuration.xmlconfig.XMLConfig('configure.zcml', z3c.pt)()
-        component.provideAdapter(term.CollectionTerms)
+        zope.component.provideAdapter(term.CollectionTerms)
 
     def _tearDown(suite):
         testing.tearDown(suite)
 
+
+def enableZ3CPT():
+    """Enable z3c.pt engine"""
+    base = zope.component.globalregistry.base
+    base.registerUtility(z3c.ptcompat.engine.Program, IPageTemplateEngine,
+        name=u'', event=False)
+    
+def disableZ3CPT():
+    """Disable z3c.pt engine"""
+    base = zope.component.globalregistry.base
+    base.unregisterUtility(z3c.ptcompat.engine.Program, IPageTemplateEngine,
+        name=u'')
+
+
 class BenchmarkTestCase(BaseTestCase):
     def simple_form(self, cls, iface):
         class SimpleForm(form.AddForm):
@@ -105,8 +119,8 @@
         f_zope = self.simple_form(
             ViewPageTemplateFile, ISmallForm)(context, request)
 
-        t_z3c = self.benchmark(compat.config.enable, f_z3c)
-        t_zope = self.benchmark(compat.config.disable, f_zope)
+        t_z3c = self.benchmark(enableZ3CPT, f_z3c)
+        t_zope = self.benchmark(disableZ3CPT, f_zope)
 
         print "z3c.pt:            %.3f" % t_z3c
         print "zope.pagetemplate: %.3f" % t_zope
@@ -122,8 +136,8 @@
         f_zope = self.simple_form(
             ViewPageTemplateFile, ILargeDataSetsForm)(context, request)
 
-        t_z3c = self.benchmark(compat.config.enable, f_z3c)
-        t_zope = self.benchmark(compat.config.disable, f_zope)
+        t_z3c = self.benchmark(enableZ3CPT, f_z3c)
+        t_zope = self.benchmark(disableZ3CPT, f_zope)
 
         print "z3c.pt:            %.3f" % t_z3c
         print "zope.pagetemplate: %.3f" % t_zope
@@ -139,8 +153,8 @@
         f_zope = self.simple_form(
             ViewPageTemplateFile, IManyFields)(context, request)
 
-        t_z3c = self.benchmark(compat.config.enable, f_z3c)
-        t_zope = self.benchmark(compat.config.disable, f_zope)
+        t_z3c = self.benchmark(enableZ3CPT, f_z3c)
+        t_zope = self.benchmark(disableZ3CPT, f_zope)
 
         print "z3c.pt:            %.3f" % t_z3c
         print "zope.pagetemplate: %.3f" % t_zope
@@ -162,4 +176,3 @@
 
 if __name__ == "__main__":
     unittest.main(defaultTest="test_suite")
-

Modified: z3c.form/trunk/buildout.cfg
===================================================================
--- z3c.form/trunk/buildout.cfg	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/buildout.cfg	2011-10-29 20:58:58 UTC (rev 123178)
@@ -1,16 +1,15 @@
 [buildout]
 develop = . benchmark
 parts = test test-no-z3cpt checker coverage-test coverage-report docs i18n
-        benchmark python omelette pocompile
+        chameleon-cache-dir benchmark python omelette pocompile
 versions = versions
 
 [versions]
-Chameleon = 1.2.13
+; please comment if you fix versions!
+;lxml 2.3.1 is not avaiable for windows
 lxml = 2.3
 python-gettext = 1.0
 sourcecodegen = 0.6.14
-z3c.pt = 1.2.1
-z3c.ptcompat = 0.5.7
 
 [lxml]
 recipe = z3c.recipe.staticlxml
@@ -19,9 +18,13 @@
 libxml2-url = http://xmlsoft.org/sources/libxml2-2.7.7.tar.gz
 libxslt-url = http://xmlsoft.org/sources/libxslt-1.1.26.tar.gz
 
+[chameleon-cache-dir]
+recipe = z3c.recipe.dev:mkdir
+path = parts/chameleon
+
 [test-environment]
 CHAMELEON_DEBUG = False
-CHAMELEON_CACHE = False
+CHAMELEON_CACHE = ${buildout:directory}/parts/chameleon
 
 [test]
 recipe = zc.recipe.testrunner
@@ -46,8 +49,8 @@
 recipe = zc.recipe.egg
 eggs =
     z3c.coverage
-scripts = coverage=coverage-report
-arguments = ('coverage', 'coverage/report')
+scripts = coveragereport
+arguments = ('parts/coverage', 'parts/coverage/report')
 
 [pocompile]
 recipe = zc.recipe.egg
@@ -93,7 +96,7 @@
 
 [benchmark-environment]
 CHAMELEON_DEBUG = False
-CHAMELEON_CACHE = True
+CHAMELEON_CACHE = ${buildout:directory}/parts/chameleon
 
 [python]
 recipe = zc.recipe.egg

Modified: z3c.form/trunk/setup.py
===================================================================
--- z3c.form/trunk/setup.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/setup.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -47,7 +47,7 @@
 
 setup(
     name='z3c.form',
-    version = '2.4.5dev',
+    version = '2.5.0',
     author="Stephan Richter, Roger Ineichen and the Zope Community",
     author_email="zope-dev at zope.org",
     description="An advanced form and widget framework for Zope 3",
@@ -76,8 +76,9 @@
     namespace_packages=['z3c'],
     extras_require=dict(
         extra=[
-            'z3c.pt >= 1.0b4',
-            'z3c.ptcompat',
+            'z3c.pt >= 2.1',
+            'z3c.ptcompat>=1.0',
+            'zope.pagetemplate >= 3.6.2',
         ],
         test=[
             'lxml >= 2.1.1',
@@ -112,7 +113,7 @@
         'zope.interface',
         'zope.lifecycleevent',
         'zope.location',
-        'zope.pagetemplate',
+        'zope.pagetemplate', # >= 3.6.2 if z3c.pt is used
         'zope.publisher',
         'zope.schema >= 3.6.0',
         'zope.security',

Modified: z3c.form/trunk/src/z3c/form/adding.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/adding.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/adding.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -52,12 +52,12 @@
 for it now:
 
   >>> import os
-  >>> from z3c.form import ptcompat as viewpagetemplatefile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
   >>> from z3c.form import tests, field
   >>> from z3c.form.adding import AddForm
 
   >>> class AddPersonForm(AddForm):
-  ...     template = viewpagetemplatefile.ViewPageTemplateFile(
+  ...     template = ViewPageTemplateFile(
   ...         'simple_edit.pt', os.path.dirname(tests.__file__))
   ...
   ...     fields = field.Fields(IPerson)

Modified: z3c.form/trunk/src/z3c/form/browser/object.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/object.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/browser/object.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -461,11 +461,12 @@
 If we want to render the addform, we must give it a template:
 
   >>> import os
-  >>> from zope.app.pagetemplate import viewpagetemplatefile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
+  >>> from zope.browserpage.viewpagetemplatefile import BoundPageTemplate
   >>> from z3c.form import tests
   >>> def addTemplate(form):
-  ...     form.template = viewpagetemplatefile.BoundPageTemplate(
-  ...         viewpagetemplatefile.ViewPageTemplateFile(
+  ...     form.template = BoundPageTemplate(
+  ...         ViewPageTemplateFile(
   ...             'simple_edit.pt', os.path.dirname(tests.__file__)), form)
   >>> addTemplate(myaddform)
 

Modified: z3c.form/trunk/src/z3c/form/browser/objectmulti.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/objectmulti.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/browser/objectmulti.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -1089,11 +1089,12 @@
 If we want to render the addform, we must give it a template:
 
   >>> import os
-  >>> from zope.app.pagetemplate import viewpagetemplatefile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
+  >>> from zope.browserpage.viewpagetemplatefile import BoundPageTemplate
   >>> from z3c.form import tests
   >>> def addTemplate(form):
-  ...     form.template = viewpagetemplatefile.BoundPageTemplate(
-  ...         viewpagetemplatefile.ViewPageTemplateFile(
+  ...     form.template = BoundPageTemplate(
+  ...         ViewPageTemplateFile(
   ...             'simple_edit.pt', os.path.dirname(tests.__file__)), form)
   >>> addTemplate(myaddform)
 

Modified: z3c.form/trunk/src/z3c/form/browser/tests.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/tests.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/browser/tests.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -23,13 +23,22 @@
 
 from z3c.form import testing
 from z3c.form import outputchecker
-from z3c.form.ptcompat import AVAILABLE, Z3CPT_AVAILABLE
 
+Z3CPT_AVAILABLE = False
+try:
+    import z3c.pt
+    import z3c.ptcompat
+except ImportError:
+    Z3CPT_AVAILABLE = False
 
 def test_suite():
     checker = outputchecker.OutputChecker(doctest)
 
-    if AVAILABLE and Z3CPT_AVAILABLE:
+    # This package will setup z3c.pt support for testing by default.
+    # The Z3CPT_AVAILABLE option allows to run z3c.form test from a 
+    # custom setup which doesn't use z3c.pt. But do we really need this?
+    # I guess not or is there a reason to support this?
+    if Z3CPT_AVAILABLE:
         setups = (testing.setUpZPT, testing.setUpZ3CPT)
     else:
         setups = (testing.setUpZPT, )

Modified: z3c.form/trunk/src/z3c/form/compatibility.py
===================================================================
--- z3c.form/trunk/src/z3c/form/compatibility.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/compatibility.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -16,11 +16,9 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
-import datetime
-import decimal
+
 import sys
 import types
-import zope.interface
 
 def addHooks():
     try:

Modified: z3c.form/trunk/src/z3c/form/contentprovider.py
===================================================================
--- z3c.form/trunk/src/z3c/form/contentprovider.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/contentprovider.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -1,7 +1,6 @@
 import zope.component
 import zope.interface
 import zope.location
-import zope.schema.interfaces
 from z3c.form.error import MultipleErrors
 from zope.contentprovider.interfaces import IContentProvider
 

Modified: z3c.form/trunk/src/z3c/form/contentprovider.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/contentprovider.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/contentprovider.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -266,11 +266,12 @@
 We have a small template as part of this example::
   
   >>> import os
-  >>> from z3c.form import ptcompat as viewpagetemplatefile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
+  >>> from zope.browserpage.viewpagetemplatefile import BoundPageTemplate
   >>> from z3c.form import tests
   >>> def personTemplate(form):
-  ...     form.template = viewpagetemplatefile.bind_template(
-  ...         viewpagetemplatefile.ViewPageTemplateFile(
+  ...     form.template = BoundPageTemplate(
+  ...         ViewPageTemplateFile(
   ...             'simple_edit_with_providers.pt', 
   ...             os.path.dirname(tests.__file__)), form)
   >>> personTemplate(personForm)

Modified: z3c.form/trunk/src/z3c/form/error.py
===================================================================
--- z3c.form/trunk/src/z3c/form/error.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/error.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -20,11 +20,11 @@
 import zope.component
 import zope.interface
 import zope.schema
+from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
 from zope.pagetemplate.interfaces import IPageTemplate
 
 import z3c.form
 from z3c.form import interfaces, util, value
-from z3c.form import ptcompat
 from z3c.form.i18n import MessageFactory as _
 
 ErrorViewMessage = value.StaticValueCreator(
@@ -131,8 +131,7 @@
     template = None
 
     def __init__(self, filename, contentType='text/html'):
-        self.template = ptcompat.ViewPageTemplateFile(
-            filename, content_type=contentType)
+        self.template = ViewPageTemplateFile(filename, content_type=contentType)
 
     def __call__(self, errorView, request):
         return self.template

Modified: z3c.form/trunk/src/z3c/form/error.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/error.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/error.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -129,11 +129,11 @@
 needs, sometimes one wishes to register a custom view to have more complex
 views. In this example we wish to register a custom error message:
 
-  >>> from z3c.form import ptcompat as viewpagetemplatefile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
   >>> from z3c.form import tests
 
   >>> class NegativeAgeView(error.ErrorViewSnippet):
-  ...     template = viewpagetemplatefile.ViewPageTemplateFile(
+  ...     template = ViewPageTemplateFile(
   ...         'custom_error.pt', os.path.dirname(tests.__file__))
 
 We now need to assert the special discriminators specific to this view:

Added: z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt	                        (rev 0)
+++ z3c.form/trunk/src/z3c/form/form-chameleon-issue-repeat-addons.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -0,0 +1,1687 @@
+=====
+Forms
+=====
+
+The purpose of this package is to make development of forms as simple
+as possible, while still providing all the hooks to do customization
+at any level as required by our real-world use cases. Thus, once the
+system is set up with all its default registrations, it should be
+trivial to develop a new form.
+
+The strategy of this document is to provide the most common, and thus
+simplest, case first and then demonstrate the available customization
+options. In order to not overwhelm you with our set of well-chosen defaults,
+all the default component registrations have been made prior to doing those
+examples:
+
+  >>> from z3c.form import testing
+  >>> testing.setupFormDefaults()
+
+Note, since version 2.4.2 the IFomrLayer doesn't provide IBrowserRequest
+anymore. This is usefull if you like to use z3c.form components for other
+requets then the IBrowserRequest.
+
+  >>> from zope.publisher.interfaces.browser import IBrowserRequest
+  >>> import z3c.form.interfaces
+  >>> z3c.form.interfaces.IFormLayer.isOrExtends(IBrowserRequest)
+  False
+
+Before we can start writing forms, we must have the content to work with:
+
+  >>> import zope.interface
+  >>> import zope.schema
+  >>> class IPerson(zope.interface.Interface):
+  ...
+  ...     id = zope.schema.TextLine(
+  ...         title=u'ID',
+  ...         readonly=True,
+  ...         required=True)
+  ...
+  ...     name = zope.schema.TextLine(
+  ...         title=u'Name',
+  ...         required=True)
+  ...
+  ...     gender = zope.schema.Choice(
+  ...         title=u'Gender',
+  ...         values=('male', 'female'),
+  ...         required=False)
+  ...
+  ...     age = zope.schema.Int(
+  ...         title=u'Age',
+  ...         description=u"The person's age.",
+  ...         min=0,
+  ...         default=20,
+  ...         required=False)
+  ...
+  ...     @zope.interface.invariant
+  ...     def ensureIdAndNameNotEqual(person):
+  ...         if person.id == person.name:
+  ...             raise zope.interface.Invalid(
+  ...                 "The id and name cannot be the same.")
+
+  >>> from zope.schema.fieldproperty import FieldProperty
+  >>> class Person(object):
+  ...     zope.interface.implements(IPerson)
+  ...     id = FieldProperty(IPerson['id'])
+  ...     name = FieldProperty(IPerson['name'])
+  ...     gender = FieldProperty(IPerson['gender'])
+  ...     age = FieldProperty(IPerson['age'])
+  ...
+  ...     def __init__(self, id, name, gender=None, age=None):
+  ...         self.id = id
+  ...         self.name = name
+  ...         if gender:
+  ...             self.gender = gender
+  ...         if age:
+  ...             self.age = age
+  ...
+  ...     def __repr__(self):
+  ...         return '<%s %r>' % (self.__class__.__name__, self.name)
+
+Okay, that should suffice for now.
+
+What's next? Well, first things first. Let's create an add form for the
+person. Since practice showed that the ``IAdding`` interface is overkill for
+most projects, the default add form of ``z3c.form`` requires you to define the
+creation and adding mechanism.
+
+**Note**:
+
+  If it is not done, ``NotImplementedError[s]`` are raised:
+
+    >>> from z3c.form.testing import TestRequest
+    >>> from z3c.form import form, field
+
+    >>> abstract = form.AddForm(None, TestRequest())
+
+    >>> abstract.create({})
+    Traceback (most recent call last):
+    ...
+    NotImplementedError
+
+    >>> abstract.add(1)
+    Traceback (most recent call last):
+    ...
+    NotImplementedError
+
+    >>> abstract.nextURL()
+    Traceback (most recent call last):
+    ...
+    NotImplementedError
+
+
+Thus let's now create a working add form:
+
+  >>> class PersonAddForm(form.AddForm):
+  ...
+  ...     fields = field.Fields(IPerson)
+  ...
+  ...     def create(self, data):
+  ...         return Person(**data)
+  ...
+  ...     def add(self, object):
+  ...         self.context[object.id] = object
+  ...
+  ...     def nextURL(self):
+  ...         return 'index.html'
+
+This is as simple as it gets. We explicitly define the pieces that
+are custom to every situation and let the default setup of the
+framework do the rest. This is intentionally similar to
+``zope.formlib``, because we really like the simplicity of
+``zope.formlib``'s way of dealing with the common use cases.
+
+Let's try to add a new person object to the root folder (which
+was created during test setup).  For this add form, of course, the
+context is now the root folder:
+
+  >>> request = TestRequest()
+  >>> addForm = PersonAddForm(root, request)
+
+Since forms are not necessarily pages -- in fact often they are not --
+they must not have a ``__call__`` method that does all the processing
+and rendering at once. Instead, we use the update/render
+pattern. Thus, we first call the ``update()`` method.
+
+  >>> addForm.update()
+
+Actually a lot of things happen during this stage. Let us step through it one
+by one pointing out the effects.
+
+
+Find a widget manager and update it
+-----------------------------------
+
+The default widget manager knows to look for the ``fields`` attribute in the
+form, since it implements ``IFieldsForm``:
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IFieldsForm.providedBy(addForm)
+  True
+
+The widget manager is then stored in the ``widgets`` attribute as promised by
+the ``IForm`` interface:
+
+  >>> addForm.widgets
+  <z3c.form.field.FieldWidgets object at ...>
+
+The widget manager will have four widgets, one for each field:
+
+  >>> addForm.widgets.keys()
+  ['id', 'name', 'gender', 'age']
+
+When the widget manager updates itself, several sub-tasks are processed. The
+manager goes through each field, trying to create a fully representative
+widget for the field.
+
+Field Availability
+~~~~~~~~~~~~~~~~~~
+
+Just because a field is requested in the field manager, does not mean that a
+widget has to be created for the field. There are cases when a field
+declaration might be ignored. The following reasons come to mind:
+
+* No widget is created if the data are not accessible in the content.
+* A custom widget manager has been registered to specifically ignore a field.
+
+In our simple example, all fields will be converted to widgets.
+
+Widget Creation
+~~~~~~~~~~~~~~~
+
+During the widget creation process, several pieces of information are
+transferred from the field to the widget:
+
+  >>> age = addForm.widgets['age']
+
+  # field.title -> age.label
+
+  >>> age.label
+  u'Age'
+
+  # field.required -> age.required
+
+  >>> age.required
+  False
+
+All these values can be overridden at later stages of the updating
+process.
+
+Widget Value
+~~~~~~~~~~~~
+
+The next step is to determine the value that should be displayed by the
+widget. This value could come from three places (looked up in this order):
+
+1. The field's default value.
+2. The content object that the form is representing.
+3. The request in case a form has not been submitted or an error occurred.
+
+Since we are currently building an add form and not an edit form,
+there is no content object to represent, so the second step is not
+applicable. The third step is also not applicable as we do not have
+anything in the request. Therefore, the value should be the field's
+default value, or be empty. In this case the field provides a default
+value:
+
+  >>> age.value
+  u'20'
+
+While the default of the age field is actually the integer ``20``, the
+widget has converted the value to the output-ready string ``'20'``
+using a data converter.
+
+Widget Mode
+~~~~~~~~~~~
+
+Now the widget manager looks at the field to determine the widget mode -- in
+other words whether the widget is a display or edit widget. In this case all
+fields are input fields:
+
+  >>> age.mode
+  'input'
+
+Deciding which mode to use, however, might not be a trivial operation. It
+might depend on several factors (items listed later override earlier ones):
+
+* The global ``mode`` flag of the widget manager
+* The permission to the content's data value
+* The ``readonly`` flag in the schema field
+* The ``mode`` flag in the field
+
+
+Widget Attribute Values
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As mentioned before, several widget attributes are optionally overridden when
+the widget updates itself:
+
+* label
+* required
+* mode
+
+Since we have no customization components registered, all of those fields will
+remain as set before.
+
+
+Find an action manager, update and execute it
+---------------------------------------------
+
+After all widgets have been instantiated and the ``update()`` method has been
+called successfully, the actions are set up. By default, the form machinery
+uses the button declaration on the form to create its actions. For the add
+form, an add button is defined by default, so that we did not need to create
+our own. Thus, there should be one action:
+
+  >>> len(addForm.actions)
+  1
+
+The add button is an action and a widget at the same time:
+
+  >>> addAction = addForm.actions['add']
+  >>> addAction.title
+  u'Add'
+  >>> addAction.value
+  u'Add'
+
+After everything is set up, all pressed buttons are executed. Once a submitted
+action is detected, a special action handler adapter is used to determine the
+actions to take. Since the add button has not been pressed yet, no action
+occurred.
+
+
+Rendering the form
+------------------
+
+Once the update is complete we can render the form. Since we have not
+specified a template yet, we have to do this now. We have prepared a small and
+very simple template as part of this example:
+
+  >>> import os
+  >>> from zope.browserpage.viewpagetemplatefile import BoundPageTemplate
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
+  >>> from z3c.form import tests
+  >>> def addTemplate(form):
+  ...     form.template = BoundPageTemplate(
+  ...         ViewPageTemplateFile(
+  ...             'simple_edit.pt', os.path.dirname(tests.__file__)), form)
+  >>> addTemplate(addForm)
+
+Let's now render the page:
+
+  >>> print addForm.render()
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <form action=".">
+        <div class="row">
+          <label for="form-widgets-id">ID</label>
+          <input type="text" id="form-widgets-id"
+                 name="form.widgets.id"
+                 class="text-widget required textline-field"
+                 value="" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-name">Name</label>
+          <input type="text" id="form-widgets-name" name="form.widgets.name"
+                 class="text-widget required textline-field"
+                 value="" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-gender">Gender</label>
+          <select id="form-widgets-gender" name="form.widgets.gender:list"
+                  class="select-widget choice-field" size="1">
+            <option id="form-widgets-gender-novalue"
+                    value="--NOVALUE--">no value</option>
+            <option id="form-widgets-gender-0" value="male">male</option>
+            <option id="form-widgets-gender-1" value="female">female</option>
+          </select>
+          <input name="form.widgets.gender-empty-marker" type="hidden"
+                 value="1" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-age">Age</label>
+          <input type="text" id="form-widgets-age" name="form.widgets.age"
+                 class="text-widget int-field" value="20" />
+        </div>
+        <div class="action">
+          <input type="submit" id="form-buttons-add" name="form.buttons.add"
+                 class="submit-widget button-field" value="Add" />
+        </div>
+      </form>
+    </body>
+  </html>
+
+The update()/render() cycle is what happens when the form is called, i.e.
+when it is published:
+
+  >>> print addForm()
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <form action=".">
+        <div class="row">
+          <label for="form-widgets-id">ID</label>
+          <input type="text" id="form-widgets-id"
+                 name="form.widgets.id"
+                 class="text-widget required textline-field"
+                 value="" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-name">Name</label>
+          <input type="text" id="form-widgets-name" name="form.widgets.name"
+                 class="text-widget required textline-field"
+                 value="" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-gender">Gender</label>
+          <select id="form-widgets-gender" name="form.widgets.gender:list"
+                  class="select-widget choice-field" size="1">
+            <option id="form-widgets-gender-novalue"
+                    value="--NOVALUE--">no value</option>
+            <option id="form-widgets-gender-0" value="male">male</option>
+            <option id="form-widgets-gender-1" value="female">female</option>
+          </select>
+          <input name="form.widgets.gender-empty-marker" type="hidden"
+                 value="1" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-age">Age</label>
+          <input type="text" id="form-widgets-age" name="form.widgets.age"
+                 class="text-widget int-field" value="20" />
+        </div>
+        <div class="action">
+          <input type="submit" id="form-buttons-add" name="form.buttons.add"
+                 class="submit-widget button-field" value="Add" />
+        </div>
+      </form>
+    </body>
+  </html>  
+
+Note that we don't actually call render if the response has been set to a 3xx
+type status code (e.g. a redirect or not modified response), since the browser
+would not render it anyway:
+
+  >>> request.response.setStatus(304)
+  >>> print addForm()
+  
+Let's go back to a normal status to continue the test.
+
+  >>> request.response.setStatus(200)
+
+Submitting an add form successfully
+-----------------------------------
+
+Initially the root folder of the application is empty:
+
+  >>> sorted(root)
+  []
+
+Let's now fill the request with all the right values so that upon submitting
+the form with the "Add" button, the person should be added to the root folder:
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.id': u'srichter',
+  ...     'form.widgets.name': u'Stephan Richter',
+  ...     'form.widgets.gender': ['male'],
+  ...     'form.widgets.age': u'20',
+  ...     'form.buttons.add': u'Add'}
+  ...     )
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addForm.update()
+
+  >>> sorted(root)
+  [u'srichter']
+  >>> stephan = root[u'srichter']
+  >>> stephan.id
+  u'srichter'
+  >>> stephan.name
+  u'Stephan Richter'
+  >>> stephan.gender
+  'male'
+  >>> stephan.age
+  20
+
+
+Submitting an add form with invalid data
+----------------------------------------
+
+Next we try to submit the add form with the required name missing. Thus, the
+add form should not complete with the addition, but return with the add form
+pointing out the error.
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.id': u'srichter',
+  ...     'form.widgets.gender': ['male'],
+  ...     'form.widgets.age': u'23',
+  ...     'form.buttons.add': u'Add'}
+  ...     )
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addForm.update()
+
+The widget manager and the widget causing the error should have an error
+message:
+
+  >>> [(error.widget.__name__, error) for error in addForm.widgets.errors]
+  [('name', <ErrorViewSnippet for RequiredMissing>)]
+
+  >>> addForm.widgets['name'].error
+  <ErrorViewSnippet for RequiredMissing>
+
+Let's now render the form:
+
+  >>> addTemplate(addForm)
+  >>> print addForm.render()
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <i>There were some errors.</i>
+      <ul>
+        <li>
+          Name: <div class="error">Required input is missing.</div>
+        </li>
+      </ul>
+      <form action=".">
+        <div class="row">
+          <label for="form-widgets-id">ID</label>
+          <input type="text" id="form-widgets-id"
+                 name="form.widgets.id"
+                 class="text-widget required textline-field"
+                 value="srichter" />
+        </div>
+        <div class="row">
+          <b><div class="error">Required input is missing.</div>
+          </b><label for="form-widgets-name">Name</label>
+          <input type="text" id="form-widgets-name" name="form.widgets.name"
+                 class="text-widget required textline-field" value="" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-gender">Gender</label>
+          <select id="form-widgets-gender" name="form.widgets.gender:list"
+                  class="select-widget choice-field" size="1">
+            <option id="form-widgets-gender-novalue"
+                    value="--NOVALUE--">no value</option>
+            <option id="form-widgets-gender-0" value="male"
+                    selected="selected">male</option>
+            <option id="form-widgets-gender-1" value="female">female</option>
+          </select>
+          <input name="form.widgets.gender-empty-marker" type="hidden"
+                 value="1" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-age">Age</label>
+          <input type="text" id="form-widgets-age" name="form.widgets.age"
+                 class="text-widget int-field" value="23" />
+        </div>
+        <div class="action">
+          <input type="submit" id="form-buttons-add" name="form.buttons.add"
+                 class="submit-widget button-field" value="Add" />
+        </div>
+      </form>
+    </body>
+  </html>
+
+Note that the values of the field are now extracted from the request.
+
+Another way to receive an error is by not fulfilling the invariants of the
+schema. In our case, the id and name cannot be the same. So let's provoke the
+error now:
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.id': u'Stephan',
+  ...     'form.widgets.name': u'Stephan',
+  ...     'form.widgets.gender': ['male'],
+  ...     'form.widgets.age': u'23',
+  ...     'form.buttons.add': u'Add'}
+  ...     )
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addTemplate(addForm)
+  >>> addForm.update()
+
+and see how the form looks like:
+
+  >>> print addForm.render() # doctest: +NOPARSE_MARKUP
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <i>There were some errors.</i>
+      <ul>
+        <li>
+          <div class="error">The id and name cannot be the same.</div>
+        </li>
+      </ul>
+      ...
+    </body>
+  </html>
+
+Let's try to provide a negative age, which is not possible either:
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.id': u'srichter',
+  ...     'form.widgets.gender': ['male'],
+  ...     'form.widgets.age': u'-5',
+  ...     'form.buttons.add': u'Add'}
+  ...     )
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addForm.update()
+
+  >>> [(view.widget.label, view) for view in addForm.widgets.errors]
+  [(u'Name', <ErrorViewSnippet for RequiredMissing>),
+   (u'Age', <ErrorViewSnippet for TooSmall>)]
+
+But the error message for a negative age is too generic:
+
+  >>> print addForm.widgets['age'].error.render()
+  <div class="error">Value is too small</div>
+
+It would be better to say that negative values are disallowed. So let's
+register a new error view snippet for the ``TooSmall`` error:
+
+  >>> from z3c.form import error
+
+  >>> class TooSmallView(error.ErrorViewSnippet):
+  ...     zope.component.adapts(
+  ...         zope.schema.interfaces.TooSmall, None, None, None, None, None)
+  ...
+  ...     def update(self):
+  ...         super(TooSmallView, self).update()
+  ...         if self.field.min == 0:
+  ...             self.message = u'The value cannot be a negative number.'
+
+  >>> zope.component.provideAdapter(TooSmallView)
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addForm.update()
+  >>> print addForm.widgets['age'].error.render()
+  <div class="error">The value cannot be a negative number.</div>
+
+Note: The ``adapts()`` declaration might look strange. An error view
+snippet is actually a multiadapter that adapts a combination of 6
+objects -- error, request, widget, field, form, content. By specifying
+only the error, we tell the system that we do not care about the other
+discriminators, which then can be anything. We could also have used
+``zope.interface.Interface`` instead, which would be equivalent.
+
+
+Additional Form Attributes and API
+----------------------------------
+
+Since we are talking about HTML forms here, add and edit forms support all
+relevant FORM element attributes as attributes on the class.
+
+  >>> addForm.method
+  'post'
+  >>> addForm.enctype
+  'multipart/form-data'
+  >>> addForm.acceptCharset
+  >>> addForm.accept
+
+The ``action`` attribute is computed. By default it is the current URL:
+
+  >>> addForm.action
+  'http://127.0.0.1'
+
+The name is also computed. By default it takes the prefix and removes any
+trailing ".".
+
+  >>> addForm.name
+  'form'
+
+The id is computed from the name, replacing dots with hyphens. Let's set
+the prefix to something containing more than one final dot and check how
+it works.
+
+  >>> addForm.prefix = 'person.form.add.'
+  >>> addForm.id
+  'person-form-add'
+
+The template can then use those attributes, if it likes to.
+
+In the examples previously we set the template manually. If no
+template is specified, the system tries to find an adapter. Without
+any special configuration, there is no adapter, so rendering the form
+fails:
+
+  >>> addForm.template = None
+  >>> addForm.render()
+  Traceback (most recent call last):
+  ...
+  ComponentLookupError: ((...), <InterfaceClass ...IPageTemplate>, u'')
+
+The form module provides a simple component to create adapter
+factories from templates:
+
+  >>> factory = form.FormTemplateFactory(
+  ...     testing.getPath('../tests/simple_edit.pt'), form=PersonAddForm)
+
+Let's register our new template-based adapter factory:
+
+  >>> zope.component.provideAdapter(factory)
+
+Now the factory will be used to provide a template:
+
+  >>> print addForm.render() # doctest: +NOPARSE_MARKUP
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  </html>
+
+Since a form can also be used as a page itself, it is callable. When
+you call it will invoke both the ``update()`` and ``render()``
+methods:
+
+  >>> print addForm() # doctest: +NOPARSE_MARKUP
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml">
+  ...
+  </html>
+
+The form also provides a label for rendering a required info. This required
+info depends by default on the given requiredInfo label and if at least one
+field is required:
+
+  >>> addForm.requiredInfo
+  u'<span class="required">*</span>&ndash; required'
+
+If we set the labelRequired to None, we do not get a requiredInfo label:
+
+  >>> addForm.labelRequired = None
+  >>> addForm.requiredInfo is None
+  True
+
+
+Changing Widget Attribute Values
+--------------------------------
+
+It frequently happens that a customer comes along and wants to
+slightly or totally change some of the text shown in forms or make
+optional fields required. It does not make sense to always have to
+adjust the schema or implement a custom schema for these use
+cases. With the z3c.form framework all attributes -- for which it is
+sensible to replace a value without touching the code -- are
+customizable via an attribute value adapter.
+
+To demonstrate this feature, let's change the label of the name widget
+from "Name" to "Full Name":
+
+  >>> from z3c.form import widget
+  >>> NameLabel = widget.StaticWidgetAttribute(
+  ...     u'Full Name', field=IPerson['name'])
+  >>> zope.component.provideAdapter(NameLabel, name='label')
+
+When the form renders, the label has now changed:
+
+  >>> addForm = PersonAddForm(root, TestRequest())
+  >>> addTemplate(addForm)
+  >>> addForm.update()
+  >>> print testing.render(addForm, './/xmlns:div[2][@class="row"]')
+  <div class="row">
+    <label for="form-widgets-name">Full Name</label>
+    <input class="text-widget required textline-field"
+           id="form-widgets-name" name="form.widgets.name" type="text" value="">
+  </div>
+
+
+Adding a "Cancel" button
+------------------------
+
+Let's say a client requests that all add forms should have a "Cancel"
+button. When the button is pressed, the user is forwarded to the next URL of
+the add form. As always, the goal is to not touch the core implementation of
+the code, but make those changes externally.
+
+Adding a button/action is a little bit more involved than changing a value,
+because you have to insert the additional action and customize the action
+handler. Based on your needs of flexibility, multiple approaches could be
+chosen. Here we demonstrate the simplest one.
+
+The first step is to create a custom action manager that always inserts a
+cancel action:
+
+  >>> from z3c.form import button
+  >>> class AddActions(button.ButtonActions):
+  ...     zope.component.adapts(
+  ...         interfaces.IAddForm,
+  ...         zope.interface.Interface,
+  ...         zope.interface.Interface)
+  ...
+  ...     def update(self):
+  ...         self.form.buttons = button.Buttons(
+  ...             self.form.buttons,
+  ...             button.Button('cancel', u'Cancel'))
+  ...         super(AddActions, self).update()
+
+After registering the new action manager,
+
+  >>> zope.component.provideAdapter(AddActions)
+
+the add form should display a cancel button:
+
+  >>> addForm.update()
+  >>> print testing.render(addForm, './/xmlns:div[@class="action"]')
+  <div class="action">
+    <input type="submit" id="form-buttons-add" name="form.buttons.add"
+           class="submit-widget button-field" value="Add" />
+  </div>
+  <div class="action">
+    <input type="submit" id="form-buttons-cancel" name="form.buttons.cancel"
+           class="submit-widget button-field" value="Cancel" />
+  </div>
+
+But showing the button does not mean it does anything. So we also need a
+custom action handler to handle the cancel action:
+
+  >>> class AddActionHandler(button.ButtonActionHandler):
+  ...     zope.component.adapts(
+  ...         interfaces.IAddForm,
+  ...         zope.interface.Interface,
+  ...         zope.interface.Interface,
+  ...         button.ButtonAction)
+  ...
+  ...     def __call__(self):
+  ...         if self.action.name == 'form.buttons.cancel':
+  ...            self.form._finishedAdd = True
+  ...            return
+  ...         super(AddActionHandler, self).__call__()
+
+After registering the action handler,
+
+  >>> zope.component.provideAdapter(AddActionHandler)
+
+we can press the cancel button and we will be forwarded:
+
+  >>> request = TestRequest(form={'form.buttons.cancel': u'Cancel'})
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addTemplate(addForm)
+  >>> addForm.update()
+  >>> addForm.render()
+  ''
+
+  >>> request.response.getStatus()
+  302
+  >>> request.response.getHeader('Location')
+  'index.html'
+
+Eventually, we might have action managers and handlers that are much more
+powerful and some of the manual labor in this example would become
+unnecessary.
+
+
+Creating an Edit Form
+---------------------
+
+Now that we have exhaustively covered the customization possibilities of add
+forms, let's create an edit form. Edit forms are even simpler than add forms,
+since all actions are completely automatic:
+
+  >>> class PersonEditForm(form.EditForm):
+  ...
+  ...     fields = field.Fields(IPerson)
+
+We can use the created person from the successful addition above.
+
+  >>> editForm = PersonEditForm(root[u'srichter'], TestRequest())
+
+After adding a template, we can look at the form:
+
+  >>> addTemplate(editForm)
+  >>> editForm.update()
+  >>> print editForm.render()
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <form action=".">
+        <div class="row">
+            <label for="form-widgets-id">ID</label>
+            <span id="form-widgets-id"
+                  class="text-widget required textline-field">srichter</span>
+        </div>
+        <div class="row">
+          <label for="form-widgets-name">Full Name</label>
+          <input type="text" id="form-widgets-name" name="form.widgets.name"
+                 class="text-widget required textline-field"
+                 value="Stephan Richter" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-gender">Gender</label>
+          <select id="form-widgets-gender" name="form.widgets.gender:list"
+                  class="select-widget choice-field" size="1">
+            <option id="form-widgets-gender-novalue"
+                    value="--NOVALUE--">no value</option>
+            <option id="form-widgets-gender-0" value="male"
+                    selected="selected">male</option>
+            <option id="form-widgets-gender-1" value="female">female</option>
+          </select>
+          <input name="form.widgets.gender-empty-marker" type="hidden"
+                 value="1" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-age">Age</label>
+          <input type="text" id="form-widgets-age" name="form.widgets.age"
+                 class="text-widget int-field" value="20" />
+        </div>
+        <div class="action">
+          <input type="submit" id="form-buttons-apply" name="form.buttons.apply"
+                 class="submit-widget button-field" value="Apply" />
+        </div>
+      </form>
+    </body>
+  </html>
+
+As you can see, the data are being pulled in from the context for the edit
+form. Next we will look at the behavior when submitting the form.
+
+
+Failure Upon Submission of Edit Form
+------------------------------------
+
+Let's now submit the form having some invalid data.
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.name': u'Claudia Richter',
+  ...     'form.widgets.gender': ['female'],
+  ...     'form.widgets.age': u'-1',
+  ...     'form.buttons.apply': u'Apply'}
+  ...     )
+
+  >>> editForm = PersonEditForm(root[u'srichter'], request)
+  >>> addTemplate(editForm)
+  >>> editForm.update()
+  >>> print editForm.render()
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <i>There were some errors.</i>
+      <ul>
+        <li>
+          Age: <div class="error">The value cannot be a negative number.</div>
+        </li>
+      </ul>
+      <form action=".">
+        <div class="row">
+            <label for="form-widgets-id">ID</label>
+            <span id="form-widgets-id"
+                  class="text-widget required textline-field">srichter</span>
+        </div>
+        <div class="row">
+          <label for="form-widgets-name">Full Name</label>
+          <input type="text" id="form-widgets-name" name="form.widgets.name"
+                 class="text-widget required textline-field"
+                 value="Claudia Richter" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-gender">Gender</label>
+          <select id="form-widgets-gender" name="form.widgets.gender:list"
+                  class="select-widget choice-field" size="1">
+            <option id="form-widgets-gender-novalue"
+                    value="--NOVALUE--">no value</option>
+            <option id="form-widgets-gender-0" value="male">male</option>
+            <option id="form-widgets-gender-1" value="female"
+                    selected="selected">female</option>
+          </select>
+          <input name="form.widgets.gender-empty-marker" type="hidden"
+                 value="1" />
+        </div>
+        <div class="row">
+          <b><div class="error">The value cannot be a negative number.</div>
+          </b><label for="form-widgets-age">Age</label>
+          <input type="text" id="form-widgets-age" name="form.widgets.age"
+                 class="text-widget int-field" value="-1" />
+        </div>
+        <div class="action">
+          <input type="submit" id="form-buttons-apply" name="form.buttons.apply"
+                 class="submit-widget button-field" value="Apply" />
+        </div>
+      </form>
+    </body>
+  </html>
+
+
+Successfully Editing Content
+----------------------------
+
+Let's now resubmit the form with valid data, so the data should be updated.
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.name': u'Claudia Richter',
+  ...     'form.widgets.gender': ['female'],
+  ...     'form.widgets.age': u'27',
+  ...     'form.buttons.apply': u'Apply'}
+  ...     )
+
+  >>> editForm = PersonEditForm(root[u'srichter'], request)
+  >>> addTemplate(editForm)
+  >>> editForm.update()
+  >>> print testing.render(editForm, './/xmlns:i')
+  <i>Data successfully updated.</i>
+
+  >>> stephan = root[u'srichter']
+  >>> stephan.name
+  u'Claudia Richter'
+  >>> stephan.gender
+  'female'
+  >>> stephan.age
+  27
+
+When an edit form is successfully committed, a detailed object-modified event
+is sent out telling the system about the changes. To see the error, let's
+create an event subscriber for object-modified events:
+
+  >>> eventlog = []
+  >>> import zope.lifecycleevent
+  >>> @zope.component.adapter(zope.lifecycleevent.ObjectModifiedEvent)
+  ... def logEvent(event):
+  ...     eventlog.append(event)
+  >>> zope.component.provideHandler(logEvent)
+
+Let's now submit the form again, successfully changing the age:
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.name': u'Claudia Richter',
+  ...     'form.widgets.gender': ['female'],
+  ...     'form.widgets.age': u'29',
+  ...     'form.buttons.apply': u'Apply'}
+  ...     )
+
+  >>> editForm = PersonEditForm(root[u'srichter'], request)
+  >>> addTemplate(editForm)
+  >>> editForm.update()
+
+We can now look at the event:
+
+  >>> event = eventlog[-1]
+  >>> event
+  <zope...ObjectModifiedEvent object at ...>
+
+  >>> attrs = event.descriptions[0]
+  >>> attrs.interface
+  <InterfaceClass __builtin__.IPerson>
+  >>> attrs.attributes
+  ('age',)
+
+
+Successful Action with No Changes
+---------------------------------
+
+When submitting the form without any changes, the form will tell you so.
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.name': u'Claudia Richter',
+  ...     'form.widgets.gender': ['female'],
+  ...     'form.widgets.age': u'29',
+  ...     'form.buttons.apply': u'Apply'}
+  ...     )
+
+  >>> editForm = PersonEditForm(root[u'srichter'], request)
+  >>> addTemplate(editForm)
+  >>> editForm.update()
+  >>> print testing.render(editForm, './/xmlns:i')
+  <i>No changes were applied.</i>
+
+
+Changing Status Messages
+------------------------
+
+Depending on the project, it is often desirable to change the status messages
+to fit the application. In ``zope.formlib`` this was hard to do, since the
+messages were buried within fairly complex methods that one did not want to
+touch. In this package all those messages are exposed as form attributes.
+
+There are three messages for the edit form:
+
+* ``formErrorsMessage`` -- Indicates that an error occurred while
+  applying the changes. This message is also available for the add form.
+
+* ``successMessage`` -- The form data was successfully applied.
+
+* ``noChangesMessage`` -- No changes were found in the form data.
+
+Let's now change the ``noChangesMessage``:
+
+  >>> editForm.noChangesMessage = u'No changes were detected in the form data.'
+  >>> editForm.update()
+  >>> print testing.render(editForm, './/xmlns:i')
+  <i>No changes were detected in the form data.</i>
+
+When even more flexibility is required within a project, one could also
+implement these messages as properties looking up an attribute value. However,
+we have found this to be a rare case.
+
+
+Creating Edit Forms for Dictionaries
+------------------------------------
+
+Sometimes it is not desirable to edit a class instance that implements the
+fields, but other types of object. A good example is the need to modify a
+simple dictionary, where the field names are the keys. To do that, a special
+data manager for dictionaries is available:
+
+  >>> from z3c.form import datamanager
+  >>> zope.component.provideAdapter(datamanager.DictionaryField)
+
+The only step the developer has to complete is to re-implement the form's
+``getContent()`` method to return the dictionary:
+
+  >>> personDict = {'id': u'rineichen', 'name': u'Roger Ineichen',
+  ...               'gender': None, 'age': None}
+  >>> class PersonDictEditForm(PersonEditForm):
+  ...     def getContent(self):
+  ...         return personDict
+
+We can now use the form as usual:
+
+  >>> editForm = PersonDictEditForm(None, TestRequest())
+  >>> addTemplate(editForm)
+  >>> editForm.update()
+  >>> print editForm.render()
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <form action=".">
+        <div class="row">
+          <label for="form-widgets-id">ID</label>
+          <span id="form-widgets-id"
+                class="text-widget required textline-field">rineichen</span>
+        </div>
+        <div class="row">
+          <label for="form-widgets-name">Full Name</label>
+          <input type="text" id="form-widgets-name"
+                 name="form.widgets.name"
+                 class="text-widget required textline-field"
+         value="Roger Ineichen" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-gender">Gender</label>
+          <select id="form-widgets-gender" name="form.widgets.gender:list"
+                  class="select-widget choice-field" size="1">
+            <option id="form-widgets-gender-novalue"
+                    value="--NOVALUE--" selected="selected">no value</option>
+            <option id="form-widgets-gender-0" value="male">male</option>
+            <option id="form-widgets-gender-1" value="female">female</option>
+          </select>
+          <input name="form.widgets.gender-empty-marker" type="hidden"
+                 value="1" />
+        </div>
+        <div class="row">
+          <label for="form-widgets-age">Age</label>
+          <input type="text" id="form-widgets-age"
+                 name="form.widgets.age" class="text-widget int-field"
+                 value="20" />
+        </div>
+        <div class="action">
+          <input type="submit" id="form-buttons-apply"
+                 name="form.buttons.apply" class="submit-widget button-field"
+                 value="Apply" />
+        </div>
+      </form>
+    </body>
+  </html>
+
+Note that the name displayed in the form is identical to the one in the
+dictionary. Let's now submit a form to ensure that the data are also written to
+the dictionary:
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.name': u'Jesse Ineichen',
+  ...     'form.widgets.gender': ['male'],
+  ...     'form.widgets.age': u'5',
+  ...     'form.buttons.apply': u'Apply'}
+  ...     )
+  >>> editForm = PersonDictEditForm(None, request)
+  >>> editForm.update()
+
+  >>> len(personDict)
+  4
+  >>> personDict['age']
+  5
+  >>> personDict['gender']
+  'male'
+  >>> personDict['id']
+  u'rineichen'
+  >>> personDict['name']
+  u'Jesse Ineichen'
+
+
+Creating a Display Form
+-----------------------
+
+Creating a display form is simple; just instantiate, update and render it:
+
+  >>> class PersonDisplayForm(form.DisplayForm):
+  ...     fields = field.Fields(IPerson)
+  ...     template = ViewPageTemplateFile(
+  ...         'simple_display.pt', os.path.dirname(tests.__file__))
+
+  >>> display = PersonDisplayForm(stephan, TestRequest())
+  >>> display.update()
+  >>> print display.render()
+  Traceback (most recent call last):
+  ...
+  KeyError: '__conform__'
+  <BLANKLINE>
+   - Expression: "repeat/value/end"
+   - Filename:   ...select_display.pt
+   - Location:   (23:32)
+  <BLANKLINE>
+   - Source:     ... tal:block condition="not:repeat/value/end">, </tal:block
+  ...
+
+Simple Form Customization
+-------------------------
+
+The form exposes several of the widget manager's attributes as attributes on
+the form. They are: ``mode``, ``ignoreContext``, ``ignoreRequest``, and
+``ignoreReadonly``.
+
+Here are the values for the display form we just created:
+
+  >>> display.mode
+  'display'
+  >>> display.ignoreContext
+  False
+  >>> display.ignoreRequest
+  True
+  >>> display.ignoreReadonly
+  False
+
+These values should be equal to the ones of the widget manager:
+
+  >>> display.widgets.mode
+  'display'
+  >>> display.widgets.ignoreContext
+  False
+  >>> display.widgets.ignoreRequest
+  True
+  >>> display.widgets.ignoreReadonly
+  False
+
+Now, if we change those values before updating the widgets, ...
+
+  >>> display.mode = interfaces.INPUT_MODE
+  >>> display.ignoreContext = True
+  >>> display.ignoreRequest = False
+  >>> display.ignoreReadonly = True
+
+... the widget manager will have the same values after updating the widgets:
+
+  >>> display.updateWidgets()
+
+  >>> display.widgets.mode
+  'input'
+  >>> display.widgets.ignoreContext
+  True
+  >>> display.widgets.ignoreRequest
+  False
+  >>> display.widgets.ignoreReadonly
+  True
+
+
+Extending Forms
+---------------
+
+One very common use case is to extend forms. For example, you would like to
+use the edit form and its defined "Apply" button, but add another button
+yourself. Unfortunately, just inheriting the form is not enough, because the
+new button and handler declarations will override the inherited ones. Let me
+demonstrate the problem:
+
+  >>> class BaseForm(form.Form):
+  ...     fields = field.Fields(IPerson).select('name')
+  ...
+  ...     @button.buttonAndHandler(u'Apply')
+  ...     def handleApply(self, action):
+  ...         print 'success'
+
+  >>> BaseForm.fields.keys()
+  ['name']
+  >>> BaseForm.buttons.keys()
+  ['apply']
+  >>> BaseForm.handlers
+  <Handlers [<Handler for <Button 'apply' u'Apply'>>]>
+
+Let's now derive a form from the base form:
+
+  >>> class DerivedForm(BaseForm):
+  ...     fields = field.Fields(IPerson).select('gender')
+  ...
+  ...     @button.buttonAndHandler(u'Cancel')
+  ...     def handleCancel(self, action):
+  ...         print 'cancel'
+
+  >>> DerivedForm.fields.keys()
+  ['gender']
+  >>> DerivedForm.buttons.keys()
+  ['cancel']
+  >>> DerivedForm.handlers
+  <Handlers [<Handler for <Button 'cancel' u'Cancel'>>]>
+
+The obvious method to "inherit" the base form's information is to copy it
+over:
+
+  >>> class DerivedForm(BaseForm):
+  ...     fields = BaseForm.fields.copy()
+  ...     buttons = BaseForm.buttons.copy()
+  ...     handlers = BaseForm.handlers.copy()
+  ...
+  ...     fields += field.Fields(IPerson).select('gender')
+  ...
+  ...     @button.buttonAndHandler(u'Cancel')
+  ...     def handleCancel(self, action):
+  ...         print 'cancel'
+
+  >>> DerivedForm.fields.keys()
+  ['name', 'gender']
+  >>> DerivedForm.buttons.keys()
+  ['apply', 'cancel']
+  >>> DerivedForm.handlers
+  <Handlers
+      [<Handler for <Button 'apply' u'Apply'>>,
+       <Handler for <Button 'cancel' u'Cancel'>>]>
+
+But this is pretty clumsy. Instead, the ``form`` module provides a helper
+method that will do the extending for you:
+
+  >>> class DerivedForm(BaseForm):
+  ...     form.extends(BaseForm)
+  ...
+  ...     fields += field.Fields(IPerson).select('gender')
+  ...
+  ...     @button.buttonAndHandler(u'Cancel')
+  ...     def handleCancel(self, action):
+  ...         print 'cancel'
+
+  >>> DerivedForm.fields.keys()
+  ['name', 'gender']
+  >>> DerivedForm.buttons.keys()
+  ['apply', 'cancel']
+  >>> DerivedForm.handlers
+  <Handlers
+      [<Handler for <Button 'apply' u'Apply'>>,
+       <Handler for <Button 'cancel' u'Cancel'>>]>
+
+If you, for example do not want to extend the buttons, you can turn that off:
+
+  >>> class DerivedForm(BaseForm):
+  ...     form.extends(BaseForm, ignoreButtons=True)
+  ...
+  ...     fields += field.Fields(IPerson).select('gender')
+  ...
+  ...     @button.buttonAndHandler(u'Cancel')
+  ...     def handleCancel(self, action):
+  ...         print 'cancel'
+
+  >>> DerivedForm.fields.keys()
+  ['name', 'gender']
+  >>> DerivedForm.buttons.keys()
+  ['cancel']
+  >>> DerivedForm.handlers
+  <Handlers
+      [<Handler for <Button 'apply' u'Apply'>>,
+       <Handler for <Button 'cancel' u'Cancel'>>]>
+
+If you, for example do not want to extend the handlers, you can turn that off:
+
+  >>> class DerivedForm(BaseForm):
+  ...     form.extends(BaseForm, ignoreHandlers=True)
+  ...
+  ...     fields += field.Fields(IPerson).select('gender')
+  ...
+  ...     @button.buttonAndHandler(u'Cancel')
+  ...     def handleCancel(self, action):
+  ...         print 'cancel'
+
+  >>> DerivedForm.fields.keys()
+  ['name', 'gender']
+  >>> DerivedForm.buttons.keys()
+  ['apply', 'cancel']
+  >>> DerivedForm.handlers
+  <Handlers [<Handler for <Button 'cancel' u'Cancel'>>]>
+
+
+Custom widget factories
+-----------------------
+
+Another important part of a form is that we can use custom widgets. We can do
+this in a form by defining a widget factory for a field. We can get the field
+from the fields collection e.g. ``fields['foo']``. This means, we can define
+new widget factories by defining ``fields['foo'].widgetFactory = MyWidget``.
+Let's show a sample and define a custom widget:
+
+  >>> from z3c.form.browser import text
+  >>> class MyWidget(text.TextWidget):
+  ...     """My new widget."""
+  ...     klass = u'MyCSS'
+
+Now we can define a field widget factory:
+
+  >>> def MyFieldWidget(field, request):
+  ...     """IFieldWidget factory for MyWidget."""
+  ...     return widget.FieldWidget(field, MyWidget(request))
+
+We register the ``MyWidget`` in a form like:
+
+  >>> class MyEditForm(form.EditForm):
+  ...
+  ...     fields = field.Fields(IPerson)
+  ...     fields['name'].widgetFactory = MyFieldWidget
+
+We can see that the custom widget gets used in the rendered form:
+
+  >>> myEdit = MyEditForm(root[u'srichter'], TestRequest())
+  >>> addTemplate(myEdit)
+  >>> myEdit.update()
+  >>> print testing.render(myEdit, './/xmlns:input[@id="form-widgets-name"]')
+  <input type="text" id="form-widgets-name"
+         name="form.widgets.name" class="MyCSS required textline-field"
+         value="Claudia Richter" />
+
+
+Hidden fields
+-------------
+
+Another important part of a form is that we can generate hidden widgets. We can
+do this in a form by defining a widget mode. We can do this by override the
+setUpWidgets method.
+
+  >>> class HiddenFieldEditForm(form.EditForm):
+  ...
+  ...     fields = field.Fields(IPerson)
+  ...     fields['name'].widgetFactory = MyFieldWidget
+  ...
+  ...     def updateWidgets(self):
+  ...         super(HiddenFieldEditForm, self).updateWidgets()
+  ...         self.widgets['age'].mode = interfaces.HIDDEN_MODE
+
+We can see that the widget gets rendered as hidden:
+
+  >>> hiddenEdit = HiddenFieldEditForm(root[u'srichter'], TestRequest())
+  >>> addTemplate(hiddenEdit)
+  >>> hiddenEdit.update()
+  >>> print testing.render(hiddenEdit, './/xmlns:input[@id="form-widgets-age"]')
+  <input type="hidden" id="form-widgets-age"
+         name="form.widgets.age" class="hidden-widget"
+         value="29" />
+
+
+Actions with Errors
+-------------------
+
+Even though the data might be validated correctly, it sometimes happens that
+data turns out to be invalid while the action is executed. In those cases a
+special action execution error can be raised that wraps the original error.
+
+  >>> class PersonAddForm(form.AddForm):
+  ...
+  ...     fields = field.Fields(IPerson).select('id')
+  ...
+  ...     @button.buttonAndHandler(u'Check')
+  ...     def handleCheck(self, action):
+  ...         data, errors = self.extractData()
+  ...         if data['id'] in self.getContent():
+  ...             raise interfaces.WidgetActionExecutionError(
+  ...                 'id', zope.interface.Invalid('Id already exists'))
+
+In this case the action execution error is specific to a widget. The framework
+will attach a proper error view to the widget and the widget manager:
+
+  >>> request = TestRequest(form={
+  ...     'form.widgets.id': u'srichter',
+  ...     'form.buttons.check': u'Check'}
+  ...     )
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addForm.update()
+
+  >>> addForm.widgets.errors
+  (<InvalidErrorViewSnippet for Invalid>,)
+  >>> addForm.widgets['id'].error
+  <InvalidErrorViewSnippet for Invalid>
+  >>> addForm.status
+  u'There were some errors.'
+
+If the error is non-widget specific, then we can simply use the generic action
+execution error:
+
+  >>> class PersonAddForm(form.AddForm):
+  ...
+  ...     fields = field.Fields(IPerson).select('id')
+  ...
+  ...     @button.buttonAndHandler(u'Check')
+  ...     def handleCheck(self, action):
+  ...         raise interfaces.ActionExecutionError(
+  ...             zope.interface.Invalid('Some problem occurred.'))
+
+Let's have a look at the result:
+
+  >>> addForm = PersonAddForm(root, request)
+  >>> addForm.update()
+
+  >>> addForm.widgets.errors
+  (<InvalidErrorViewSnippet for Invalid>,)
+  >>> addForm.status
+  u'There were some errors.'
+
+**Note**:
+
+  The action execution errors are connected to the form via an event
+  listener called ``handlerActionError``. This event listener listens for
+  ``IActionErrorEvent`` events. If the event is called for an action associated
+  with a form, the listener does its work as seen above. If the action is not
+  coupled to a form, then event listener does nothing:
+
+    >>> from z3c.form import action
+
+    >>> cancel = action.Action(request, u'Cancel')
+    >>> event = action.ActionErrorOccurred(cancel, ValueError(3))
+
+    >>> form.handleActionError(event)
+
+
+Applying Changes
+----------------
+
+When applying the data of a form to a content component, the function
+``applyChanges()`` is called. It simply iterates through the fields of the
+form and uses the data managers to store the values. The output of the
+function is a list of changes:
+
+  >>> roger = Person(u'roger', u'Roger')
+  >>> roger
+  <Person u'Roger'>
+
+  >>> class BaseForm(form.Form):
+  ...     fields = field.Fields(IPerson).select('name')
+  >>> myForm = BaseForm(roger, TestRequest())
+
+  >>> form.applyChanges(myForm, roger, {'name': u'Roger Ineichen'})
+  {<InterfaceClass __builtin__.IPerson>: ['name']}
+
+  >>> roger
+  <Person u'Roger Ineichen'>
+
+When a field is missing from the data, it is simply skipped:
+
+  >>> form.applyChanges(myForm, roger, {})
+  {}
+
+If the new and old value are identical, storing the data is skipped as well:
+
+  >>> form.applyChanges(myForm, roger, {'name': u'Roger Ineichen'})
+  {}
+
+In some cases the data converter for a field-widget pair returns the
+``NOT_CHANGED`` value. In this case, the field is skipped as well:
+
+  >>> form.applyChanges(myForm, roger, {'name': interfaces.NOT_CHANGED})
+  {}
+
+  >>> roger
+  <Person u'Roger Ineichen'>
+
+
+Refreshing actions
+------------------
+
+Sometimes, it's useful to update actions again after executing them,
+because some conditions could have changed. For example, imagine
+we have a sequence edit form that has a delete button. We don't
+want to show delete button when the sequence is empty. The button
+condition would handle this, but what if the sequence becomes empty
+as a result of execution of the delete action that was available?
+In that case we want to refresh our actions to new conditions to make
+our delete button not visible anymore. The ``refreshActions`` form
+variable is intended to handle this case.
+
+Let's create a simple form with an action that clears our context
+sequence.
+
+  >>> class SequenceForm(form.Form):
+  ...
+  ...     @button.buttonAndHandler(u'Empty', condition=lambda form:bool(form.context))
+  ...     def handleEmpty(self, action):
+  ...         self.context[:] = []
+  ...         self.refreshActions = True
+
+First, let's illustrate simple cases, when no button is pressed.
+The button will be available when context is not empty.
+
+  >>> context = [1, 2, 3, 4]
+  >>> request = TestRequest()
+  >>> myForm = SequenceForm(context, request)
+  >>> myForm.update()
+  >>> addTemplate(myForm)
+  >>> print testing.render(myForm, './/xmlns:div[@class="action"]')
+  <div class="action">
+    <input type="submit" id="form-buttons-empty" name="form.buttons.empty"
+           class="submit-widget button-field" value="Empty" />
+  </div>
+
+The button will not be available when the context is empty.
+
+  >>> context = []
+  >>> request = TestRequest()
+  >>> myForm = SequenceForm(context, request)
+  >>> myForm.update()
+  >>> addTemplate(myForm)
+  >>> print testing.render(myForm, './/xmlns:form')
+  <form action=".">
+  </form>
+
+Now, the most interesting case when context is not empty, but becomes
+empty as a result of pressing the "empty" button. We set the
+``refreshActions`` flag in the action handler, so our actions should
+be updated to new conditions.
+
+  >>> context = [1, 2, 3, 4, 5]
+  >>> request = TestRequest(form={
+  ...     'form.buttons.empty': u'Empty'}
+  ...     )
+  >>> myForm = SequenceForm(context, request)
+  >>> myForm.update()
+  >>> addTemplate(myForm)
+  >>> print testing.render(myForm, './/xmlns:form')
+  <form action=".">
+  </form>
+
+Integration tests
+-----------------
+
+Identifying the different forms can be important if it comes to layout
+template lookup. Let's ensure that we support the right interfaces for the
+different forms.
+
+
+Form
+~~~~
+
+  >>> from zope.interface.verify import verifyObject
+  >>> from z3c.form import interfaces
+  >>> obj = form.Form(None, None)
+  >>> verifyObject(interfaces.IForm, obj)
+  True
+
+  >>> interfaces.IForm.providedBy(obj)
+  True
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IDisplayForm.providedBy(obj)
+  False
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IEditForm.providedBy(obj)
+  False
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IAddForm.providedBy(obj)
+  False
+
+
+DisplayForm
+~~~~~~~~~~~
+
+  >>> from z3c.form import interfaces
+  >>> obj = form.DisplayForm(None, None)
+  >>> verifyObject(interfaces.IDisplayForm, obj)
+  True
+
+  >>> interfaces.IForm.providedBy(obj)
+  True
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IDisplayForm.providedBy(obj)
+  True
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IEditForm.providedBy(obj)
+  False
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IAddForm.providedBy(obj)
+  False
+
+
+EditForm
+~~~~~~~~
+
+  >>> from z3c.form import interfaces
+  >>> obj = form.EditForm(None, None)
+  >>> verifyObject(interfaces.IEditForm, obj)
+  True
+
+  >>> interfaces.IForm.providedBy(obj)
+  True
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IDisplayForm.providedBy(obj)
+  False
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IEditForm.providedBy(obj)
+  True
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IAddForm.providedBy(obj)
+  False
+
+
+AddForm
+~~~~~~~
+
+  >>> from z3c.form import interfaces
+  >>> obj = form.AddForm(None, None)
+  >>> verifyObject(interfaces.IAddForm, obj)
+  True
+
+  >>> interfaces.IForm.providedBy(obj)
+  True
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IDisplayForm.providedBy(obj)
+  False
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IEditForm.providedBy(obj)
+  False
+
+  >>> from z3c.form import interfaces
+  >>> interfaces.IAddForm.providedBy(obj)
+  True

Modified: z3c.form/trunk/src/z3c/form/form.py
===================================================================
--- z3c.form/trunk/src/z3c/form/form.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/form.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -21,13 +21,13 @@
 import zope.component
 import zope.event
 import zope.lifecycleevent
+from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
 from zope.publisher import browser
 from zope.pagetemplate.interfaces import IPageTemplate
 from zope.schema.fieldproperty import FieldProperty
 
 from z3c.form import button, field, interfaces, util
 from z3c.form.i18n import MessageFactory as _
-from z3c.form import ptcompat
 
 
 def applyChanges(form, content, data):
@@ -303,8 +303,7 @@
 
     def __init__(self, filename, contentType='text/html', form=None,
         request=None):
-        self.template = ptcompat.ViewPageTemplateFile(
-            filename, content_type=contentType)
+        self.template = ViewPageTemplateFile(filename, content_type=contentType)
         zope.component.adapter(
             util.getSpecification(form),
             util.getSpecification(request))(self)

Modified: z3c.form/trunk/src/z3c/form/form.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/form.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/form.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -298,11 +298,12 @@
 very simple template as part of this example:
 
   >>> import os
-  >>> from z3c.form import ptcompat as viewpagetemplatefile
+  >>> from zope.browserpage.viewpagetemplatefile import BoundPageTemplate
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
   >>> from z3c.form import tests
   >>> def addTemplate(form):
-  ...     form.template = viewpagetemplatefile.bind_template(
-  ...         viewpagetemplatefile.ViewPageTemplateFile(
+  ...     form.template = BoundPageTemplate(
+  ...         ViewPageTemplateFile(
   ...             'simple_edit.pt', os.path.dirname(tests.__file__)), form)
   >>> addTemplate(addForm)
 
@@ -1147,7 +1148,7 @@
 
   >>> class PersonDisplayForm(form.DisplayForm):
   ...     fields = field.Fields(IPerson)
-  ...     template = viewpagetemplatefile.ViewPageTemplateFile(
+  ...     template = ViewPageTemplateFile(
   ...         'simple_display.pt', os.path.dirname(tests.__file__))
 
   >>> display = PersonDisplayForm(stephan, TestRequest())

Modified: z3c.form/trunk/src/z3c/form/group.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/group.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/group.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -60,7 +60,7 @@
 one of the base classes. The groups are specified in a simple tuple:
 
   >>> import os
-  >>> from z3c.form.ptcompat import ViewPageTemplateFile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
   >>> from z3c.form import form, tests
 
   >>> class RegistrationAddForm(group.GroupForm, form.AddForm):

Modified: z3c.form/trunk/src/z3c/form/object.py
===================================================================
--- z3c.form/trunk/src/z3c/form/object.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/object.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -21,6 +21,7 @@
 import zope.schema
 import zope.event
 import zope.lifecycleevent
+from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
 from zope.security.proxy import removeSecurityProxy
 from zope.pagetemplate.interfaces import IPageTemplate
 
@@ -29,7 +30,6 @@
 from z3c.form import form, interfaces, util, widget
 from z3c.form.field import Fields
 from z3c.form.error import MultipleErrors
-from z3c.form.ptcompat import ViewPageTemplateFile
 
 def getIfName(iface):
     return iface.__module__+'.'+iface.__name__
@@ -316,8 +316,7 @@
     def __init__(self, filename, contentType='text/html',
                  context=None, request=None, view=None,
                  field=None, widget=None, schema=None):
-        self.template = ViewPageTemplateFile(
-            filename, content_type=contentType)
+        self.template = ViewPageTemplateFile(filename, content_type=contentType)
         zope.component.adapter(
             util.getSpecification(context),
             util.getSpecification(request),

Deleted: z3c.form/trunk/src/z3c/form/ptcompat.py
===================================================================
--- z3c.form/trunk/src/z3c/form/ptcompat.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/ptcompat.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -1,38 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2007 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""z3c.pt optional compatibility
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-
-#import utilities from z3c.ptcompat when available and set AVAILABLE flag
-
-AVAILABLE = False
-Z3CPT_AVAILABLE = False
-
-try:
-    from z3c.ptcompat import ViewPageTemplateFile
-    from z3c.ptcompat import bind_template
-    AVAILABLE = True
-except ImportError:
-    from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
-    from zope.app.pagetemplate.viewpagetemplatefile import BoundPageTemplate \
-         as bind_template
-
-try:
-    import z3c.pt
-    Z3CPT_AVAILABLE = True
-except ImportError:
-    pass

Modified: z3c.form/trunk/src/z3c/form/subform.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/subform.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/subform.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -68,7 +68,7 @@
 is the template, which should not render a form-tag:
 
   >>> import os
-  >>> from z3c.form.ptcompat import ViewPageTemplateFile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
   >>> from z3c.form import form, field, tests
 
   >>> templatePath = os.path.dirname(tests.__file__)

Modified: z3c.form/trunk/src/z3c/form/testing.py
===================================================================
--- z3c.form/trunk/src/z3c/form/testing.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/testing.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -33,8 +33,6 @@
 from z3c.form import contentprovider
 from z3c.form.browser import radio, select, text, textarea
 
-from z3c.form.ptcompat import AVAILABLE
-
 import lxml.html
 import lxml.doctestcompare
 
@@ -182,16 +180,14 @@
     test.globs = {'root': setup.placefulSetUp(True)}
 
 def setUpZPT(suite):
-    if AVAILABLE:
-        import z3c.ptcompat
-        z3c.ptcompat.config.disable()
     setUp(suite)
 
 def setUpZ3CPT(suite):
+    import z3c.pt
     import z3c.ptcompat
-    z3c.ptcompat.config.enable()
     setUp(suite)
     zope.configuration.xmlconfig.XMLConfig('configure.zcml', z3c.pt)()
+    zope.configuration.xmlconfig.XMLConfig('configure.zcml', z3c.ptcompat)()
 
 def setupFormDefaults():
     # Validator adapters

Modified: z3c.form/trunk/src/z3c/form/tests/test_doc.py
===================================================================
--- z3c.form/trunk/src/z3c/form/tests/test_doc.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/tests/test_doc.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -23,13 +23,22 @@
 
 from z3c.form import testing
 from z3c.form import outputchecker
-from z3c.form.ptcompat import AVAILABLE, Z3CPT_AVAILABLE
 
+# This package will allways provide z3c.pt for it's test setup.
+# The Z3CPT_AVAILABLE hickup is usefull if someone will run the z3c.form tests
+# in his own project and doesn't use z3c.pt.
+try:
+    import z3c.pt
+    import z3c.ptcompat
+    Z3CPT_AVAILABLE = True
+except ImportError:
+    Z3CPT_AVAILABLE = False
 
+
 def test_suite():
     checker = outputchecker.OutputChecker(doctest)
 
-    if AVAILABLE and Z3CPT_AVAILABLE:
+    if Z3CPT_AVAILABLE:
         setups = (testing.setUpZPT, testing.setUpZ3CPT)
     else:
         setups = (testing.setUpZPT, )
@@ -121,12 +130,6 @@
                  ])
             ),
         doctest.DocFileSuite(
-            '../form.txt',
-            setUp=setUp, tearDown=testing.tearDown,
-            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
-            checker=checker,
-            ),
-        doctest.DocFileSuite(
             '../group.txt',
             setUp=setUp, tearDown=testing.tearDown,
             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
@@ -158,4 +161,42 @@
             ))
         for setUp in setups)
 
+    tests = tuple(tests) + ((
+
+        # the form.txt test will include the select_display widget which uses
+        # another pattern for iterate the repeat dict.
+        
+        # z3c.pt and chameleon are not compatible right now. Traversing the
+        # repeat wwrapper is not done the same way. ZPT uses the following
+        # pattern
+        # <tal:block condition="not:repeat/value/end">, </tal:block>
+        #
+        # chameleon only supports python style traversin:
+        # <tal:block condition="not:python:repeat['value'].end">, </tal:block>
+        # 
+        # this is not a show stopper at all but I hope chameleon will support
+        # the path travers style for repeat in the future.
+        doctest.DocFileSuite(
+            '../form.txt',
+            setUp=testing.setUpZPT, tearDown=testing.tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            checker=checker,
+            ),
+        
+        # see line: 1157 for the error
+        # run this test with setUp=testing.setUpZPT to see that this
+        # test is not failing with ZPT
+        # remove this test file and let the form.txt test run with both setup
+        # after fixing the chameleon issue
+        doctest.DocFileSuite(
+            '../form-chameleon-issue-repeat-addons.txt',
+            setUp=testing.setUpZ3CPT, tearDown=testing.tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            checker=checker,
+            ),
+        ))
+
+
+
+
     return unittest.TestSuite(itertools.chain(*tests))

Modified: z3c.form/trunk/src/z3c/form/widget.py
===================================================================
--- z3c.form/trunk/src/z3c/form/widget.py	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/widget.py	2011-10-29 20:58:58 UTC (rev 123178)
@@ -1,465 +1,464 @@
-##############################################################################
-#
-# Copyright (c) 2007 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Widget Framework Implementation
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-
-import zope.interface
-import zope.component
-import zope.location
-import zope.schema.interfaces
-from zope.pagetemplate.interfaces import IPageTemplate
-from zope.i18n import translate
-from zope.schema.fieldproperty import FieldProperty
-
-from z3c.form import interfaces, util, value
-from z3c.form import ptcompat as viewpagetemplatefile
-
-PLACEHOLDER = object()
-
-StaticWidgetAttribute = value.StaticValueCreator(
-    discriminators = ('context', 'request', 'view', 'field', 'widget')
-    )
-ComputedWidgetAttribute = value.ComputedValueCreator(
-    discriminators = ('context', 'request', 'view', 'field', 'widget')
-    )
-
-
-class Widget(zope.location.Location):
-    """Widget base class."""
-
-    zope.interface.implements(interfaces.IWidget)
-
-    # widget specific attributes
-    name = FieldProperty(interfaces.IWidget['name'])
-    label = FieldProperty(interfaces.IWidget['label'])
-    mode = FieldProperty(interfaces.IWidget['mode'])
-    required = FieldProperty(interfaces.IWidget['required'])
-    error = FieldProperty(interfaces.IWidget['error'])
-    value = FieldProperty(interfaces.IWidget['value'])
-    template = None
-    ignoreRequest = FieldProperty(interfaces.IWidget['ignoreRequest'])
-    setErrors = FieldProperty(interfaces.IWidget['setErrors'])
-
-    # The following attributes are for convenience. They are declared in
-    # extensions to the simple widget.
-
-    # See ``interfaces.IContextAware``
-    context = None
-    ignoreContext = False
-    # See ``interfaces.IFormAware``
-    form = None
-    # See ``interfaces.IFieldWidget``
-    field = None
-
-    # Internal attributes
-    _adapterValueAttributes = ('label', 'name', 'required', 'title')
-
-    def __init__(self, request):
-        self.request = request
-
-    def update(self):
-        """See z3c.form.interfaces.IWidget."""
-        # Step 1: Determine the value.
-        value = interfaces.NO_VALUE
-        lookForDefault = False
-        # Step 1.1: If possible, get a value from the request
-        if not self.ignoreRequest:
-            #at this turn we do not need errors to be set on widgets
-            #errors will be set when extract gets called from form.extractData
-            self.setErrors = False
-            widget_value = self.extract()
-            if widget_value is not interfaces.NO_VALUE:
-                # Once we found the value in the request, it takes precendence
-                # over everything and nothing else has to be done.
-                self.value = widget_value
-                value = PLACEHOLDER
-        # Step 1.2: If we have a widget with a field and we have no value yet,
-        #           we have some more possible locations to get the value
-        if (interfaces.IFieldWidget.providedBy(self) and
-            value is interfaces.NO_VALUE and
-            value is not PLACEHOLDER):
-            # Step 1.2.1: If the widget knows about its context and the
-            #              context is to be used to extract a value, get
-            #              it now via a data manager.
-            if (interfaces.IContextAware.providedBy(self) and
-                not self.ignoreContext):
-                value = zope.component.getMultiAdapter(
-                    (self.context, self.field),
-                    interfaces.IDataManager).query()
-            # Step 1.2.2: If we still do not have a value, we can always use
-            #             the default value of the field, id set
-            # NOTE: It should check field.default is not missing_value, but
-            # that requires fixing zope.schema first
-            if ((value is self.field.missing_value or
-                 value is interfaces.NO_VALUE) and
-                self.field.default is not None):
-                value = self.field.default
-                lookForDefault = True
-        # Step 1.3: If we still have not found a value, then we try to get it
-        #           from an attribute value
-        if value is interfaces.NO_VALUE or lookForDefault:
-            adapter = zope.component.queryMultiAdapter(
-                (self.context, self.request, self.form, self.field, self),
-                interfaces.IValue, name='default')
-            if adapter:
-                value = adapter.get()
-        # Step 1.4: Convert the value to one that the widget can understand
-        if value not in (interfaces.NO_VALUE, PLACEHOLDER):
-            converter = interfaces.IDataConverter(self)
-            self.value = converter.toWidgetValue(value)
-        # Step 2: Update selected attributes
-        for attrName in self._adapterValueAttributes:
-            # only allow to set values for known attributes
-            if hasattr(self, attrName):
-                value = zope.component.queryMultiAdapter(
-                    (self.context, self.request, self.form, self.field, self),
-                    interfaces.IValue, name=attrName)
-                if value is not None:
-                    setattr(self, attrName, value.get())
-
-    def render(self):
-        """See z3c.form.interfaces.IWidget."""
-        template = self.template
-        if template is None:
-            template = zope.component.getMultiAdapter(
-                (self.context, self.request, self.form, self.field, self),
-                IPageTemplate, name=self.mode)
-        return template(self)
-
-    def extract(self, default=interfaces.NO_VALUE):
-        """See z3c.form.interfaces.IWidget."""
-        return self.request.get(self.name, default)
-
-    def __repr__(self):
-        return '<%s %r>' % (self.__class__.__name__, self.name)
-
-
-class SequenceWidget(Widget):
-    """Term based sequence widget base.
-
-    The sequence widget is used for select items from a sequence. Don't get
-    confused, this widget does support to choose one or more values from a
-    sequence. The word sequence is not used for the schema field, it's used
-    for the values where this widget can choose from.
-
-    This widget base class is used for build single or sequence values based
-    on a sequence which is in most use case a collection. e.g.
-    IList of IChoice for sequence values or IChoice for single values.
-
-    See also the MultiWidget for build sequence values based on none collection
-    based values. e.g. IList of ITextLine
-    """
-
-    zope.interface.implements(interfaces.ISequenceWidget)
-
-    value = ()
-    terms = None
-
-    noValueToken = '--NOVALUE--'
-
-    @property
-    def displayValue(self):
-        value = []
-        for token in self.value:
-            # Ignore no value entries. They are in the request only.
-            if token == self.noValueToken:
-                continue
-            term = self.terms.getTermByToken(token)
-            if zope.schema.interfaces.ITitledTokenizedTerm.providedBy(term):
-                value.append(translate(
-                    term.title, context=self.request, default=term.title))
-            else:
-                value.append(term.value)
-        return value
-
-    def updateTerms(self):
-        if self.terms is None:
-            self.terms = zope.component.getMultiAdapter(
-                (self.context, self.request, self.form, self.field, self),
-                interfaces.ITerms)
-        return self.terms
-
-    def update(self):
-        """See z3c.form.interfaces.IWidget."""
-        # Create terms first, since we need them for the generic update.
-        self.updateTerms()
-        super(SequenceWidget, self).update()
-
-    def extract(self, default=interfaces.NO_VALUE):
-        """See z3c.form.interfaces.IWidget."""
-        if (self.name not in self.request and
-            self.name+'-empty-marker' in self.request):
-            return []
-        value = self.request.get(self.name, default)
-        if value != default:
-            if not isinstance(value, (tuple, list)):
-                value = (value,)
-            # do some kind of validation, at least only use existing values
-            for token in value:
-                if token == self.noValueToken:
-                    continue
-                try:
-                    self.terms.getTermByToken(token)
-                except LookupError:
-                    return default
-        return value
-
-
-class MultiWidget(Widget):
-    """None Term based sequence widget base.
-
-    The multi widget is used for ITuple or IList if no other widget is defined.
-
-    Some IList or ITuple are using another specialized widget if they can
-    choose from a collection. e.g. a IList of IChoice. The base class of such
-    widget is the ISequenceWidget.
-
-    This widget can handle none collection based sequences and offers add or
-    remove values to or from the sequence. Each sequence value get rendered by
-    it's own relevant widget. e.g. IList of ITextLine or ITuple of IInt
-
-    Each widget get rendered within a sequence value. This means each internal
-    widget will repressent one value from the mutli widget value. Based on the
-    nature of this (sub) widget setup the internal widget do not have a real
-    context and can't get binded to it. This makes it impossible to use a
-    sequence of collection where the collection needs a context. But that
-    should not be a problem since sequence of collection will use the
-    SequenceWidget as base.
-    """
-
-    zope.interface.implements(interfaces.IMultiWidget)
-
-    allowAdding = True
-    allowRemoving = True
-
-    widgets = None
-    _value = None
-    _widgets_updated = False
-
-    _mode = FieldProperty(interfaces.IWidget['mode'])
-
-    def __init__(self, request):
-        super(MultiWidget, self).__init__(request)
-        self.widgets = []
-        self._value = []
-
-    @property
-    def counterName(self):
-        return '%s.count' % self.name
-
-    @property
-    def counterMarker(self):
-        # this get called in render from the template and contains always the
-        # right amount of widgets we use.
-        return '<input type="hidden" name="%s" value="%d" />' % (
-            self.counterName, len(self.widgets))
-
-    @apply
-    def mode():
-        """This sets the subwidgets modes."""
-        def get(self):
-            return self._mode
-        def set(self, mode):
-            self._mode = mode
-            # ensure that we apply the new mode to the widgets
-            for w in self.widgets:
-                w.mode = mode
-        return property(get, set)
-
-    def getWidget(self, idx):
-        """Setup widget based on index id with or without value."""
-        valueType = self.field.value_type
-        widget = zope.component.getMultiAdapter((valueType, self.request),
-            interfaces.IFieldWidget)
-        self.setName(widget, idx)
-        widget.mode = self.mode
-        #set widget.form (objectwidget needs this)
-        if interfaces.IFormAware.providedBy(self):
-            widget.form = self.form
-            zope.interface.alsoProvides(
-                widget, interfaces.IFormAware)
-        widget.update()
-        return widget
-
-    def setName(self, widget, idx):
-        widget.name = '%s.%i' % (self.name, idx)
-        widget.id = '%s-%i' % (self.id, idx)
-
-    def appendAddingWidget(self):
-        """Simply append a new empty widget with correct (counter) name."""
-        # since we start with idx 0 (zero) we can use the len as next idx
-        idx = len(self.widgets)
-        widget = self.getWidget(idx)
-        self.widgets.append(widget)
-
-    def applyValue(self, widget, value=interfaces.NO_VALUE):
-        """Validate and apply value to given widget.
-
-        This method gets called on any multi widget value change and is
-        responsible for validating the given value and setup an error message.
-
-        This is internal apply value and validation process is needed because
-        nothing outside this multi widget does know something about our
-        internal sub widgets.
-        """
-        if value is not interfaces.NO_VALUE:
-            try:
-                # convert widget value to field value
-                converter = interfaces.IDataConverter(widget)
-                fvalue = converter.toFieldValue(value)
-                # validate field value
-                zope.component.getMultiAdapter(
-                    (self.context,
-                     self.request,
-                     self.form,
-                     getattr(widget, 'field', None),
-                     widget),
-                    interfaces.IValidator).validate(fvalue)
-                # convert field value to widget value
-                widget.value = converter.toWidgetValue(fvalue)
-            except (zope.schema.ValidationError, ValueError), error:
-                # on exception, setup the widget error message
-                view = zope.component.getMultiAdapter(
-                    (error, self.request, widget, widget.field,
-                     self.form, self.context), interfaces.IErrorViewSnippet)
-                view.update()
-                widget.error = view
-                # set the wrong value as value
-                widget.value = value
-
-    def updateWidgets(self):
-        """Setup internal widgets based on the value_type for each value item.
-        """
-        oldLen = len(self.widgets)
-        # Ensure at least min_length widgets are shown
-        if (zope.schema.interfaces.IMinMaxLen.providedBy(self.field) and
-            self.mode == interfaces.INPUT_MODE and self.allowAdding and
-            oldLen < self.field.min_length):
-            oldLen = self.field.min_length
-        self.widgets = []
-        idx = 0
-        if self.value:
-            for v in self.value:
-                widget = self.getWidget(idx)
-                self.applyValue(widget, v)
-                self.widgets.append(widget)
-                idx += 1
-        missing = oldLen - len(self.widgets)
-        if missing > 0:
-            # add previous existing new added widgtes
-            for i in xrange(missing):
-                widget = self.getWidget(idx)
-                self.widgets.append(widget)
-                idx += 1
-        self._widgets_updated = True
-
-    def updateAllowAddRemove(self):
-        """Update the allowAdding/allowRemoving attributes
-        basing on field constraints and current number of widgets
-        """
-        if not zope.schema.interfaces.IMinMaxLen.providedBy(self.field):
-            return
-        max_length = self.field.max_length
-        min_length = self.field.min_length
-        num_items = len(self.widgets)
-        self.allowAdding = bool((not max_length) or (num_items < max_length))
-        self.allowRemoving = bool(num_items and (num_items > min_length))
-
-    @apply
-    def value():
-        """This invokes updateWidgets on any value change e.g. update/extract."""
-        def get(self):
-            return self._value
-        def set(self, value):
-            self._value = value
-            # ensure that we apply our new values to the widgets
-            self.updateWidgets()
-        return property(get, set)
-
-    def update(self):
-        """See z3c.form.interfaces.IWidget."""
-        # Ensure that updateWidgets is called.
-        super(MultiWidget, self).update()
-        if not self._widgets_updated:
-            self.updateWidgets()
-
-    def extract(self, default=interfaces.NO_VALUE):
-        # This method is responsible to get the widgets value based on the
-        # request and nothing else.
-        # We have to setup the widgets for extract their values, because we
-        # don't know how to do this for every field without the right widgets.
-        # Later we will setup the widgets based on this values. This is needed
-        # because we probably set a new value in the form for our multi widget
-        # which whould generate a different set of widgets.
-        if self.request.get(self.counterName) is None:
-            # counter marker not found
-            return interfaces.NO_VALUE
-        counter = int(self.request.get(self.counterName, 0))
-        values = []
-        append = values.append
-        # extract value for existing widgets
-        for idx in range(counter):
-            widget = self.getWidget(idx)
-            append(widget.value)
-        return values
-
-
-def FieldWidget(field, widget):
-    """Set the field for the widget."""
-    widget.field = field
-    if not interfaces.IFieldWidget.providedBy(widget):
-        zope.interface.alsoProvides(widget, interfaces.IFieldWidget)
-    # Initial values are set. They can be overridden while updating the widget
-    # itself later on.
-    widget.name = field.__name__
-    widget.id = field.__name__.replace('.', '-')
-    widget.label = field.title
-    widget.required = field.required
-    return widget
-
-
-class WidgetTemplateFactory(object):
-    """Widget template factory."""
-
-    def __init__(self, filename, contentType='text/html',
-                 context=None, request=None, view=None,
-                 field=None, widget=None):
-        self.template = viewpagetemplatefile.ViewPageTemplateFile(
-            filename, content_type=contentType)
-        zope.component.adapter(
-            util.getSpecification(context),
-            util.getSpecification(request),
-            util.getSpecification(view),
-            util.getSpecification(field),
-            util.getSpecification(widget))(self)
-        zope.interface.implementer(IPageTemplate)(self)
-
-    def __call__(self, context, request, view, field, widget):
-        return self.template
-
-
-class WidgetEvent(object):
-    zope.interface.implements(interfaces.IWidgetEvent)
-
-    def __init__(self, widget):
-        self.widget = widget
-
-    def __repr__(self):
-        return '<%s %r>' %(self.__class__.__name__, self.widget)
-
-class AfterWidgetUpdateEvent(WidgetEvent):
-    zope.interface.implementsOnly(interfaces.IAfterWidgetUpdateEvent)
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Widget Framework Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.location
+import zope.schema.interfaces
+from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
+from zope.i18n import translate
+from zope.pagetemplate.interfaces import IPageTemplate
+from zope.schema.fieldproperty import FieldProperty
+
+from z3c.form import interfaces, util, value
+
+PLACEHOLDER = object()
+
+StaticWidgetAttribute = value.StaticValueCreator(
+    discriminators = ('context', 'request', 'view', 'field', 'widget')
+    )
+ComputedWidgetAttribute = value.ComputedValueCreator(
+    discriminators = ('context', 'request', 'view', 'field', 'widget')
+    )
+
+
+class Widget(zope.location.Location):
+    """Widget base class."""
+
+    zope.interface.implements(interfaces.IWidget)
+
+    # widget specific attributes
+    name = FieldProperty(interfaces.IWidget['name'])
+    label = FieldProperty(interfaces.IWidget['label'])
+    mode = FieldProperty(interfaces.IWidget['mode'])
+    required = FieldProperty(interfaces.IWidget['required'])
+    error = FieldProperty(interfaces.IWidget['error'])
+    value = FieldProperty(interfaces.IWidget['value'])
+    template = None
+    ignoreRequest = FieldProperty(interfaces.IWidget['ignoreRequest'])
+    setErrors = FieldProperty(interfaces.IWidget['setErrors'])
+
+    # The following attributes are for convenience. They are declared in
+    # extensions to the simple widget.
+
+    # See ``interfaces.IContextAware``
+    context = None
+    ignoreContext = False
+    # See ``interfaces.IFormAware``
+    form = None
+    # See ``interfaces.IFieldWidget``
+    field = None
+
+    # Internal attributes
+    _adapterValueAttributes = ('label', 'name', 'required', 'title')
+
+    def __init__(self, request):
+        self.request = request
+
+    def update(self):
+        """See z3c.form.interfaces.IWidget."""
+        # Step 1: Determine the value.
+        value = interfaces.NO_VALUE
+        lookForDefault = False
+        # Step 1.1: If possible, get a value from the request
+        if not self.ignoreRequest:
+            #at this turn we do not need errors to be set on widgets
+            #errors will be set when extract gets called from form.extractData
+            self.setErrors = False
+            widget_value = self.extract()
+            if widget_value is not interfaces.NO_VALUE:
+                # Once we found the value in the request, it takes precendence
+                # over everything and nothing else has to be done.
+                self.value = widget_value
+                value = PLACEHOLDER
+        # Step 1.2: If we have a widget with a field and we have no value yet,
+        #           we have some more possible locations to get the value
+        if (interfaces.IFieldWidget.providedBy(self) and
+            value is interfaces.NO_VALUE and
+            value is not PLACEHOLDER):
+            # Step 1.2.1: If the widget knows about its context and the
+            #              context is to be used to extract a value, get
+            #              it now via a data manager.
+            if (interfaces.IContextAware.providedBy(self) and
+                not self.ignoreContext):
+                value = zope.component.getMultiAdapter(
+                    (self.context, self.field),
+                    interfaces.IDataManager).query()
+            # Step 1.2.2: If we still do not have a value, we can always use
+            #             the default value of the field, id set
+            # NOTE: It should check field.default is not missing_value, but
+            # that requires fixing zope.schema first
+            if ((value is self.field.missing_value or
+                 value is interfaces.NO_VALUE) and
+                self.field.default is not None):
+                value = self.field.default
+                lookForDefault = True
+        # Step 1.3: If we still have not found a value, then we try to get it
+        #           from an attribute value
+        if value is interfaces.NO_VALUE or lookForDefault:
+            adapter = zope.component.queryMultiAdapter(
+                (self.context, self.request, self.form, self.field, self),
+                interfaces.IValue, name='default')
+            if adapter:
+                value = adapter.get()
+        # Step 1.4: Convert the value to one that the widget can understand
+        if value not in (interfaces.NO_VALUE, PLACEHOLDER):
+            converter = interfaces.IDataConverter(self)
+            self.value = converter.toWidgetValue(value)
+        # Step 2: Update selected attributes
+        for attrName in self._adapterValueAttributes:
+            # only allow to set values for known attributes
+            if hasattr(self, attrName):
+                value = zope.component.queryMultiAdapter(
+                    (self.context, self.request, self.form, self.field, self),
+                    interfaces.IValue, name=attrName)
+                if value is not None:
+                    setattr(self, attrName, value.get())
+
+    def render(self):
+        """See z3c.form.interfaces.IWidget."""
+        template = self.template
+        if template is None:
+            template = zope.component.getMultiAdapter(
+                (self.context, self.request, self.form, self.field, self),
+                IPageTemplate, name=self.mode)
+        return template(self)
+
+    def extract(self, default=interfaces.NO_VALUE):
+        """See z3c.form.interfaces.IWidget."""
+        return self.request.get(self.name, default)
+
+    def __repr__(self):
+        return '<%s %r>' % (self.__class__.__name__, self.name)
+
+
+class SequenceWidget(Widget):
+    """Term based sequence widget base.
+
+    The sequence widget is used for select items from a sequence. Don't get
+    confused, this widget does support to choose one or more values from a
+    sequence. The word sequence is not used for the schema field, it's used
+    for the values where this widget can choose from.
+
+    This widget base class is used for build single or sequence values based
+    on a sequence which is in most use case a collection. e.g.
+    IList of IChoice for sequence values or IChoice for single values.
+
+    See also the MultiWidget for build sequence values based on none collection
+    based values. e.g. IList of ITextLine
+    """
+
+    zope.interface.implements(interfaces.ISequenceWidget)
+
+    value = ()
+    terms = None
+
+    noValueToken = '--NOVALUE--'
+
+    @property
+    def displayValue(self):
+        value = []
+        for token in self.value:
+            # Ignore no value entries. They are in the request only.
+            if token == self.noValueToken:
+                continue
+            term = self.terms.getTermByToken(token)
+            if zope.schema.interfaces.ITitledTokenizedTerm.providedBy(term):
+                value.append(translate(
+                    term.title, context=self.request, default=term.title))
+            else:
+                value.append(term.value)
+        return value
+
+    def updateTerms(self):
+        if self.terms is None:
+            self.terms = zope.component.getMultiAdapter(
+                (self.context, self.request, self.form, self.field, self),
+                interfaces.ITerms)
+        return self.terms
+
+    def update(self):
+        """See z3c.form.interfaces.IWidget."""
+        # Create terms first, since we need them for the generic update.
+        self.updateTerms()
+        super(SequenceWidget, self).update()
+
+    def extract(self, default=interfaces.NO_VALUE):
+        """See z3c.form.interfaces.IWidget."""
+        if (self.name not in self.request and
+            self.name+'-empty-marker' in self.request):
+            return []
+        value = self.request.get(self.name, default)
+        if value != default:
+            if not isinstance(value, (tuple, list)):
+                value = (value,)
+            # do some kind of validation, at least only use existing values
+            for token in value:
+                if token == self.noValueToken:
+                    continue
+                try:
+                    self.terms.getTermByToken(token)
+                except LookupError:
+                    return default
+        return value
+
+
+class MultiWidget(Widget):
+    """None Term based sequence widget base.
+
+    The multi widget is used for ITuple or IList if no other widget is defined.
+
+    Some IList or ITuple are using another specialized widget if they can
+    choose from a collection. e.g. a IList of IChoice. The base class of such
+    widget is the ISequenceWidget.
+
+    This widget can handle none collection based sequences and offers add or
+    remove values to or from the sequence. Each sequence value get rendered by
+    it's own relevant widget. e.g. IList of ITextLine or ITuple of IInt
+
+    Each widget get rendered within a sequence value. This means each internal
+    widget will repressent one value from the mutli widget value. Based on the
+    nature of this (sub) widget setup the internal widget do not have a real
+    context and can't get binded to it. This makes it impossible to use a
+    sequence of collection where the collection needs a context. But that
+    should not be a problem since sequence of collection will use the
+    SequenceWidget as base.
+    """
+
+    zope.interface.implements(interfaces.IMultiWidget)
+
+    allowAdding = True
+    allowRemoving = True
+
+    widgets = None
+    _value = None
+    _widgets_updated = False
+
+    _mode = FieldProperty(interfaces.IWidget['mode'])
+
+    def __init__(self, request):
+        super(MultiWidget, self).__init__(request)
+        self.widgets = []
+        self._value = []
+
+    @property
+    def counterName(self):
+        return '%s.count' % self.name
+
+    @property
+    def counterMarker(self):
+        # this get called in render from the template and contains always the
+        # right amount of widgets we use.
+        return '<input type="hidden" name="%s" value="%d" />' % (
+            self.counterName, len(self.widgets))
+
+    @apply
+    def mode():
+        """This sets the subwidgets modes."""
+        def get(self):
+            return self._mode
+        def set(self, mode):
+            self._mode = mode
+            # ensure that we apply the new mode to the widgets
+            for w in self.widgets:
+                w.mode = mode
+        return property(get, set)
+
+    def getWidget(self, idx):
+        """Setup widget based on index id with or without value."""
+        valueType = self.field.value_type
+        widget = zope.component.getMultiAdapter((valueType, self.request),
+            interfaces.IFieldWidget)
+        self.setName(widget, idx)
+        widget.mode = self.mode
+        #set widget.form (objectwidget needs this)
+        if interfaces.IFormAware.providedBy(self):
+            widget.form = self.form
+            zope.interface.alsoProvides(
+                widget, interfaces.IFormAware)
+        widget.update()
+        return widget
+
+    def setName(self, widget, idx):
+        widget.name = '%s.%i' % (self.name, idx)
+        widget.id = '%s-%i' % (self.id, idx)
+
+    def appendAddingWidget(self):
+        """Simply append a new empty widget with correct (counter) name."""
+        # since we start with idx 0 (zero) we can use the len as next idx
+        idx = len(self.widgets)
+        widget = self.getWidget(idx)
+        self.widgets.append(widget)
+
+    def applyValue(self, widget, value=interfaces.NO_VALUE):
+        """Validate and apply value to given widget.
+
+        This method gets called on any multi widget value change and is
+        responsible for validating the given value and setup an error message.
+
+        This is internal apply value and validation process is needed because
+        nothing outside this multi widget does know something about our
+        internal sub widgets.
+        """
+        if value is not interfaces.NO_VALUE:
+            try:
+                # convert widget value to field value
+                converter = interfaces.IDataConverter(widget)
+                fvalue = converter.toFieldValue(value)
+                # validate field value
+                zope.component.getMultiAdapter(
+                    (self.context,
+                     self.request,
+                     self.form,
+                     getattr(widget, 'field', None),
+                     widget),
+                    interfaces.IValidator).validate(fvalue)
+                # convert field value to widget value
+                widget.value = converter.toWidgetValue(fvalue)
+            except (zope.schema.ValidationError, ValueError), error:
+                # on exception, setup the widget error message
+                view = zope.component.getMultiAdapter(
+                    (error, self.request, widget, widget.field,
+                     self.form, self.context), interfaces.IErrorViewSnippet)
+                view.update()
+                widget.error = view
+                # set the wrong value as value
+                widget.value = value
+
+    def updateWidgets(self):
+        """Setup internal widgets based on the value_type for each value item.
+        """
+        oldLen = len(self.widgets)
+        # Ensure at least min_length widgets are shown
+        if (zope.schema.interfaces.IMinMaxLen.providedBy(self.field) and
+            self.mode == interfaces.INPUT_MODE and self.allowAdding and
+            oldLen < self.field.min_length):
+            oldLen = self.field.min_length
+        self.widgets = []
+        idx = 0
+        if self.value:
+            for v in self.value:
+                widget = self.getWidget(idx)
+                self.applyValue(widget, v)
+                self.widgets.append(widget)
+                idx += 1
+        missing = oldLen - len(self.widgets)
+        if missing > 0:
+            # add previous existing new added widgtes
+            for i in xrange(missing):
+                widget = self.getWidget(idx)
+                self.widgets.append(widget)
+                idx += 1
+        self._widgets_updated = True
+
+    def updateAllowAddRemove(self):
+        """Update the allowAdding/allowRemoving attributes
+        basing on field constraints and current number of widgets
+        """
+        if not zope.schema.interfaces.IMinMaxLen.providedBy(self.field):
+            return
+        max_length = self.field.max_length
+        min_length = self.field.min_length
+        num_items = len(self.widgets)
+        self.allowAdding = bool((not max_length) or (num_items < max_length))
+        self.allowRemoving = bool(num_items and (num_items > min_length))
+
+    @apply
+    def value():
+        """This invokes updateWidgets on any value change e.g. update/extract."""
+        def get(self):
+            return self._value
+        def set(self, value):
+            self._value = value
+            # ensure that we apply our new values to the widgets
+            self.updateWidgets()
+        return property(get, set)
+
+    def update(self):
+        """See z3c.form.interfaces.IWidget."""
+        # Ensure that updateWidgets is called.
+        super(MultiWidget, self).update()
+        if not self._widgets_updated:
+            self.updateWidgets()
+
+    def extract(self, default=interfaces.NO_VALUE):
+        # This method is responsible to get the widgets value based on the
+        # request and nothing else.
+        # We have to setup the widgets for extract their values, because we
+        # don't know how to do this for every field without the right widgets.
+        # Later we will setup the widgets based on this values. This is needed
+        # because we probably set a new value in the form for our multi widget
+        # which whould generate a different set of widgets.
+        if self.request.get(self.counterName) is None:
+            # counter marker not found
+            return interfaces.NO_VALUE
+        counter = int(self.request.get(self.counterName, 0))
+        values = []
+        append = values.append
+        # extract value for existing widgets
+        for idx in range(counter):
+            widget = self.getWidget(idx)
+            append(widget.value)
+        return values
+
+
+def FieldWidget(field, widget):
+    """Set the field for the widget."""
+    widget.field = field
+    if not interfaces.IFieldWidget.providedBy(widget):
+        zope.interface.alsoProvides(widget, interfaces.IFieldWidget)
+    # Initial values are set. They can be overridden while updating the widget
+    # itself later on.
+    widget.name = field.__name__
+    widget.id = field.__name__.replace('.', '-')
+    widget.label = field.title
+    widget.required = field.required
+    return widget
+
+
+class WidgetTemplateFactory(object):
+    """Widget template factory."""
+
+    def __init__(self, filename, contentType='text/html',
+                 context=None, request=None, view=None,
+                 field=None, widget=None):
+        self.template = ViewPageTemplateFile(filename, content_type=contentType)
+        zope.component.adapter(
+            util.getSpecification(context),
+            util.getSpecification(request),
+            util.getSpecification(view),
+            util.getSpecification(field),
+            util.getSpecification(widget))(self)
+        zope.interface.implementer(IPageTemplate)(self)
+
+    def __call__(self, context, request, view, field, widget):
+        return self.template
+
+
+class WidgetEvent(object):
+    zope.interface.implements(interfaces.IWidgetEvent)
+
+    def __init__(self, widget):
+        self.widget = widget
+
+    def __repr__(self):
+        return '<%s %r>' %(self.__class__.__name__, self.widget)
+
+class AfterWidgetUpdateEvent(WidgetEvent):
+    zope.interface.implementsOnly(interfaces.IAfterWidgetUpdateEvent)

Modified: z3c.form/trunk/src/z3c/form/zcml.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/zcml.txt	2011-10-28 21:07:56 UTC (rev 123177)
+++ z3c.form/trunk/src/z3c/form/zcml.txt	2011-10-29 20:58:58 UTC (rev 123178)
@@ -74,7 +74,7 @@
 
 and check it:
 
-  >>> from z3c.form.ptcompat import ViewPageTemplateFile
+  >>> from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
   >>> isinstance(template, ViewPageTemplateFile)
   True
 



More information about the checkins mailing list