[Zope3-checkins] SVN: Zope3/trunk/ Implemented browser:form directive. Now you can use forms without having

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun Feb 27 07:18:55 EST 2005


Log message for revision 29321:
  Implemented browser:form directive. Now you can use forms without having 
  to have an object that supports the schema edited.
  
  See Zope3/src/zope/app/form/browser/form.txt
  
  

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/doc/TODO.txt
  U   Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml
  U   Zope3/trunk/src/zope/app/form/browser/configure.zcml
  A   Zope3/trunk/src/zope/app/form/browser/form.txt
  A   Zope3/trunk/src/zope/app/form/browser/formview.py
  U   Zope3/trunk/src/zope/app/form/browser/meta.zcml
  U   Zope3/trunk/src/zope/app/form/browser/metaconfigure.py
  U   Zope3/trunk/src/zope/app/form/browser/metadirectives.py
  A   Zope3/trunk/src/zope/app/form/browser/tests/test_form.py

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/doc/CHANGES.txt	2005-02-27 12:18:55 UTC (rev 29321)
@@ -10,6 +10,12 @@
 
     New features
 
+      - Developed a generic browser:form directive. It is pretty much the same
+        as the browser:editform directive, except that the data is not stored
+        on some context or adapted context but sent as a dictionary to special
+        method (by default). By default these methods are named `getData()` and
+        `setData(data)`.
+ 
       - When raising the Unauthorized exception, the security checker
         now passes the object in question in addition to the attribute
         name and missing permission.  This should make debugging easier.

Modified: Zope3/trunk/doc/TODO.txt
===================================================================
--- Zope3/trunk/doc/TODO.txt	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/doc/TODO.txt	2005-02-27 12:18:55 UTC (rev 29321)
@@ -9,8 +9,6 @@
 
 - Fix final issues relating the new PAS and related UIs
 
-- Develop a generic browser:form directive
-
 - Support for iterable sources
 
 - Issue 292: Add factory to browser:resource directive 

Modified: Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml	2005-02-27 12:18:55 UTC (rev 29321)
@@ -231,32 +231,6 @@
         />
   </configure>
 
-  <!-- Widgets and Forms -->
-  <bookchapter
-      id="form"
-      title="Widgets and Forms"
-      />
-  <configure package="zope.app.form.browser">
-    <bookchapter
-        id="bwidget"
-        title="Basic Widgets"
-        doc_path="README.txt"
-        parent="form"
-        />
-    <bookchapter
-        id="swidget"
-        title="Source Widgets"
-        doc_path="source.txt"
-        parent="form"
-        />
-    <bookchapter
-        id="awidget"
-        title="Advanced Widgets"
-        doc_path="widgets.txt"
-        parent="form"
-        />
-  </configure>
-
   <!-- Workflow -->
   <configure package="zope.wfmc">
     <bookchapter

Modified: Zope3/trunk/src/zope/app/form/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/configure.zcml	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/form/browser/configure.zcml	2005-02-27 12:18:55 UTC (rev 29321)
@@ -1,6 +1,6 @@
 <configure
-  xmlns="http://namespaces.zope.org/zope"
-  xmlns:browser="http://namespaces.zope.org/browser">
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser">
 
   <!-- Form Macros -->
 
@@ -418,6 +418,41 @@
       factory=".source.SourceListInputWidget"
       permission="zope.Public"
       />
-     
 
+  <!-- Register the form documentation with the apidoc tool -->   
+  <configure
+      xmlns:apidoc="http://namespaces.zope.org/apidoc"
+      xmlns:zcml="http://namespaces.zope.org/zcml"
+      zcml:condition="have apidoc">
+
+    <apidoc:bookchapter
+        id="form"
+        title="Widgets and Forms"
+        />
+    <apidoc:bookchapter
+        id="bwidget"
+        title="Basic Widgets"
+        doc_path="README.txt"
+        parent="form"
+        />
+    <apidoc:bookchapter
+        id="swidget"
+        title="Source Widgets"
+        doc_path="source.txt"
+        parent="form"
+        />
+    <apidoc:bookchapter
+        id="awidget"
+        title="Advanced Widgets"
+        doc_path="widgets.txt"
+        parent="form"
+        />
+    <apidoc:bookchapter
+        id="formdirective"
+        title="The browser:form Directive"
+        doc_path="form.txt"
+        parent="form"
+        />
+  </configure>
+  
 </configure>

Added: Zope3/trunk/src/zope/app/form/browser/form.txt
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/form.txt	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/form/browser/form.txt	2005-02-27 12:18:55 UTC (rev 29321)
@@ -0,0 +1,207 @@
+=============
+Generic Forms
+=============
+
+The `browser:form` allows the developer to use the form and widget framework
+without relying on a particular context object. Instead, it is up to the
+developer to implement two simple methods that handle the retrieval and
+mutation of the data in form of a dictionary.
+
+But I am getting ahead of myself. We first need to define a schema that we
+would like to use for our form:
+
+  >>> import zope.interface
+  >>> import zope.schema
+  >>> class IName(zope.interface.Interface):
+  ...     """The name of a person."""
+  ...
+  ...     first = zope.schema.TextLine(
+  ...         title=u"First Name",
+  ...         required=False)
+  ...
+  ...     last = zope.schema.TextLine(
+  ...         title=u"Last Name",
+  ...         required=True)
+
+Now we are almost ready to create the form view. But first we have to create a
+class that knows how to get and set values for the fields described by the
+schema. The system calls for a class (that will be used as a mixin) that
+implements a method called `getData()` that returns a dictionary mapping field
+names to values and a second method called `setData(data)` that takes a data
+dictionary as argument and handles the data in any way fit. In our case, let's
+store the data in a global attribute called name:
+
+  >>> name = ['John', 'Doe']
+
+  >>> class DataHandler(object):
+  ...
+  ...     def getData(self):
+  ...         global name
+  ...         return {'first': name[0], 'last': name[1]}
+  ...
+  ...     def setData(self, data):
+  ...         global name
+  ...         name[0] = data['first']
+  ...         name[1] = data['last']
+
+
+We now immitate the form-directive's behavior and create the view class:
+
+  >>> from zope.app.form.browser.formview import FormView
+  >>> View = type('View', bases=(DataHandler, FormView), 
+  ...             dict={'schema': IName})
+
+To initialize the view, you still need a context and a request. The context is
+useful because it gives you a location, so that you can look up local
+components to store the data, such as the principal annotations. In our case
+we do not care about the context, so it will be just `None`:
+
+  >>> from zope.publisher.browser import TestRequest
+  >>> request = TestRequest()
+  >>> view = View(None, request)
+
+We can now look at the widgets and see that the data was correctly loaded:
+
+  >>> view.first_widget()
+  u'<input class="textType" id="field.first" name="field.first"
+           size="20" type="text" value="John"  />'
+
+  >>> view.last_widget()
+  u'<input class="textType" id="field.last" name="field.last"
+           size="20" type="text" value="Doe"  />'
+
+Now when a request is sent in, the data is transmitted in the form
+
+  >>> request.form['field.first']   = u'Stephan'
+  >>> request.form['field.last']   = u'Richter'
+  >>> request.form['UPDATE_SUBMIT']   = u''
+
+and as soon as the `update()` method is called
+
+  >>> view.update()
+  ''
+
+the global name variable is changed:
+
+  >>> name
+  [u'Stephan', u'Richter']
+
+And that's pretty much all that there is to it. The rest behaves exactely like
+an edit form, from which the form view was derived. 
+
+Of course you can also completely overwrite the `update()` method like for the
+edit view removing the requirement to implement the `getData()` and
+`setData()` methods.
+
+
+Using the `browser:form` directive
+----------------------------------
+
+Let's now see how the form directive works. The first task is to load the
+necessary meta-configuration:
+
+   >>> from zope.configuration import xmlconfig
+
+   >>> import zope.app.component
+   >>> context = xmlconfig.file('meta.zcml', zope.app.component)
+
+   >>> import zope.app.form.browser
+   >>> context = xmlconfig.file('meta.zcml', zope.app.form.browser, context)
+
+   >>> import zope.app.publisher.browser
+   >>> context = xmlconfig.file('meta.zcml', zope.app.publisher.browser, 
+   ...                          context)
+
+Before we run the directive, make sure that no view exists:
+
+  >>> class IAnything(zope.interface.Interface):
+  ...     pass
+
+  >>> class Anything(object):
+  ...     zope.interface.implements(IAnything)
+
+  >>> from zope.publisher.browser import TestRequest
+
+  >>> from zope.app import zapi
+  >>> zapi.queryMultiAdapter((Anything(), TestRequest()), name='name.html')
+
+Now that we know that the view did not exist before the registration, let's
+execute the form directive:
+
+  >>> import sys
+  >>> sys.modules['form'] = type(
+  ...     'Module', (), 
+  ...     {'DataHandler': DataHandler, 
+  ...      'IAnything': IAnything, 
+  ...      'IName': IName})()
+
+  >>> context = xmlconfig.string('''
+  ...     <configure 
+  ...         xmlns="http://namespaces.zope.org/zope"
+  ...         xmlns:browser="http://namespaces.zope.org/browser"
+  ...         i18n_domain="zope">
+  ...
+  ...       <view
+  ...           type="zope.publisher.interfaces.browser.IBrowserRequest"
+  ...           for="zope.schema.interfaces.ITextLine"
+  ...           provides="zope.app.form.interfaces.IInputWidget"
+  ...           factory="zope.app.form.browser.TextWidget"
+  ...           permission="zope.Public"
+  ...           />
+  ...       
+  ...       <browser:form
+  ...           for="form.IAnything"
+  ...           schema="form.IName"
+  ...           class="form.DataHandler"
+  ...           name="name.html"
+  ...           label="Edit the name"
+  ...           fields="first last"
+  ...           permission="zope.Public" />
+  ...
+  ...     </configure>
+  ...    ''', context)
+
+and try to look up the view again:
+
+  >>> zapi.queryMultiAdapter(
+  ...     (Anything(), TestRequest()), name='name.html') #doctest:+ELLIPSIS
+  <zope.app.pagetemplate.simpleviewclass.SimpleViewClass from edit.pt ...>
+
+
+Now, if I do not specify my own template, and my class does not overwrite the
+`update()` method, then the class *must* implement `getData()` and
+`setData(data)`, otherwise a configuration error is raised:
+
+  >>> class NewDataHandler(object):
+  ...
+  ...     def getData(self):
+  ...         return {}
+
+  >>> sys.modules['form'].NewDataHandler = NewDataHandler
+
+  >>> context = xmlconfig.string('''
+  ...     <configure 
+  ...         xmlns:browser="http://namespaces.zope.org/browser"
+  ...         i18n_domain="zope">
+  ...
+  ...       <browser:form
+  ...           for="form.IAnything"
+  ...           schema="form.IName"
+  ...           class="form.NewDataHandler"
+  ...           name="name.html"
+  ...           label="Edit the name"
+  ...           fields="first last"
+  ...           permission="zope.Public" />
+  ...
+  ...     </configure>
+  ...    ''', context)
+  Traceback (most recent call last):
+  ...
+  ZopeXMLConfigurationError: File "<string>", line 6.6
+        ConfigurationError: You must specify a class that implements 
+                            `getData()` and `setData()`, if you do not 
+                            overwrite `update()`.
+
+Now we need to clean up afterwards.
+
+  >>> del sys.modules['form']
\ No newline at end of file


Property changes on: Zope3/trunk/src/zope/app/form/browser/form.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: Zope3/trunk/src/zope/app/form/browser/formview.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/formview.py	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/form/browser/formview.py	2005-02-27 12:18:55 UTC (rev 29321)
@@ -0,0 +1,84 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation 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.
+#
+##############################################################################
+"""Form View Classes
+
+$Id: editview.py 29143 2005-02-14 22:43:16Z srichter $
+"""
+__docformat__ = 'restructuredtext'
+from transaction import get_transaction
+
+from zope.app.form.interfaces import WidgetsError
+
+from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
+from zope.app.form.browser.editview import EditView
+from zope.app.form.browser.submit import Update
+from zope.app.i18n import ZopeMessageIDFactory as _
+
+
+class Data(dict):
+    """Dictionary wrapper to make keys available as attributes."""
+
+    def __getattr__(self, name):
+        return self[name]
+
+    def __setattr__(self, name, value):
+        self[name] = value
+
+
+class FormView(EditView):
+
+    def getData(self):
+        """Get the data for the form.
+
+        This method should return a dictionary mapping field names to values.
+        """
+        NotImplemented, 'Must be implemented by a specific form class'
+
+    def setData(self, data):
+        """Set the data gotten from a form.
+
+        The data will be a dictionary of fieldnames to values.
+        """
+        NotImplemented, 'Must be implemented by a specific form class'
+    
+    def _setUpWidgets(self):
+        self.data = Data(self.getData())
+        setUpEditWidgets(
+            self, self.schema, source=self.data, names=self.fieldNames)
+
+    def update(self):
+        if self.update_status is not None:
+            # We've been called before. Just return the status we previously
+            # computed.
+            return self.update_status
+
+        status = ''
+
+        if Update in self.request:
+            try:
+                changed = applyWidgetsChanges(
+                    self, self.schema, target=self.data, names=self.fieldNames)
+            except WidgetsError, errors:
+                self.errors = errors
+                status = _("An error occured.")
+                get_transaction().abort()
+            else:
+                if changed:
+                    self.setData(self.data)
+                setUpEditWidgets(
+                    self, self.schema, source=self.data,
+                    ignoreStickyValues=True, names=self.fieldNames)
+
+        self.update_status = status
+        return status


Property changes on: Zope3/trunk/src/zope/app/form/browser/formview.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/app/form/browser/meta.zcml
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/meta.zcml	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/form/browser/meta.zcml	2005-02-27 12:18:55 UTC (rev 29321)
@@ -34,6 +34,20 @@
 
 
     <meta:complexDirective
+        name="form"
+        schema=".metadirectives.IFormDirective"
+        handler=".metaconfigure.FormDirective"
+        >
+
+      <meta:subdirective
+          name="widget"
+          schema=".metadirectives.IWidgetSubdirective"
+          />
+
+    </meta:complexDirective>
+
+
+    <meta:complexDirective
         name="editform"
         schema=".metadirectives.IEditFormDirective"
         handler=".metaconfigure.EditFormDirective"

Modified: Zope3/trunk/src/zope/app/form/browser/metaconfigure.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/metaconfigure.py	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/form/browser/metaconfigure.py	2005-02-27 12:18:55 UTC (rev 29321)
@@ -34,6 +34,7 @@
 from zope.app.form.interfaces import IInputWidget, IDisplayWidget
 from add import AddView, AddViewFactory
 from editview import EditView, EditViewFactory
+from formview import FormView
 from addwizard import AddWizardView, AddWizardViewFactory
 from editwizard import EditWizardView, EditWizardViewFactory
 from schemadisplay import DisplayView, DisplayViewFactory
@@ -299,6 +300,20 @@
             kw={'menu': self.menu},
         )
 
+class FormDirective(EditFormDirective):
+
+    view = FormView
+
+    def __init__(self, _context, **kwargs):
+        super(FormDirective, self).__init__(_context, **kwargs)
+        attrs = self.class_.__dict__.keys()
+        if 'template' not in kwargs.keys() and 'update' not in attrs and \
+               ('getData' not in attrs or 'setData' not in attrs):
+            raise ConfigurationError(
+                "You must specify a class that implements `getData()` "
+                "and `setData()`, if you do not overwrite `update()`.")
+
+
 class SubeditFormDirective(EditFormDirectiveBase):
 
     default_template = 'subedit.pt'

Modified: Zope3/trunk/src/zope/app/form/browser/metadirectives.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/metadirectives.py	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/form/browser/metadirectives.py	2005-02-27 12:18:55 UTC (rev 29321)
@@ -262,6 +262,24 @@
         value_type=PythonIdentifier()
         )
 
+class IFormDirective(ICommonFormInformation):
+    """
+    Define an automatically generated form.
+
+    The form directive does nto require the data to be stored in its context,
+    but leaves the storing procedure to the to a method.
+    """
+    class_ = GlobalObject(
+        title=u"Class",
+        description=u"""
+        A class to provide the `getData()` and `setData()` methods or
+        completely custom methods to be used by a custom template.
+
+        This class is used as a mix-in class. As a result, it needn't
+        subclass any special classes, such as BrowserView.""",
+        required=True
+        )
+
 class IEditFormDirective(ICommonFormInformation):
     """
     Define an automatically generated edit form

Added: Zope3/trunk/src/zope/app/form/browser/tests/test_form.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_form.py	2005-02-27 12:17:03 UTC (rev 29320)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_form.py	2005-02-27 12:18:55 UTC (rev 29321)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation 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.
+#
+##############################################################################
+"""Tests for the ZCML Documentation Module
+
+$Id: tests.py 29269 2005-02-23 22:22:48Z srichter $
+"""
+import os
+import unittest
+from zope.testing import doctest, doctestunit
+from zope.app.testing import placelesssetup, ztapi
+
+from zope.schema.interfaces import ITextLine
+from zope.app.form.browser import TextWidget
+from zope.app.form.interfaces import IInputWidget
+
+def setUp(test):
+    placelesssetup.setUp()
+    ztapi.browserViewProviding(ITextLine, TextWidget, IInputWidget)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('../form.txt',
+                             setUp=setUp, tearDown=placelesssetup.tearDown,
+                             globs={'pprint': doctestunit.pprint},
+                             optionflags=doctest.NORMALIZE_WHITESPACE),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(default='test_suite')


Property changes on: Zope3/trunk/src/zope/app/form/browser/tests/test_form.py
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Zope3-Checkins mailing list