[Zope3-checkins] CVS: Zope3/src/zope/app/form - utility.py:1.22.2.1 widget.py:1.7.2.1

Garrett Smith garrett@mojave-corp.com
Tue, 22 Jul 2003 09:01:35 -0400


Update of /cvs-repository/Zope3/src/zope/app/form
In directory cvs.zope.org:/tmp/cvs-serv26193/src/zope/app/form

Modified Files:
      Tag: garrett-widgets-branch
	utility.py widget.py 
Log Message:
CVS: ----------------------------------------------------------------------
CVS: Enter Log.  Lines beginning with `CVS:' are removed automatically
CVS: 
CVS: Committing in .
CVS: 
CVS: Modified Files:
CVS:  Tag: garrett-widgets-branch
CVS: 	src/zope/app/browser/cache/cacheable.py 
CVS: 	src/zope/app/browser/component/interfacewidget.py 
CVS: 	src/zope/app/browser/component/tests/test_interfacewidget.py 
CVS: 	src/zope/app/browser/form/editview.py 
CVS: 	src/zope/app/browser/form/vocabularywidget.py 
CVS: 	src/zope/app/browser/form/widget.py 
CVS: 	src/zope/app/browser/form/tests/test_browserwidget.py 
CVS: 	src/zope/app/browser/form/tests/test_checkboxwidget.py 
CVS: 	src/zope/app/browser/form/tests/test_datetimewidget.py 
CVS: 	src/zope/app/browser/form/tests/test_editview.py 
CVS: 	src/zope/app/browser/form/tests/test_floatwidget.py 
CVS: 	src/zope/app/browser/form/tests/test_intwidget.py 
CVS: 	src/zope/app/browser/form/tests/test_objectwidget.py 
CVS: 	src/zope/app/browser/form/tests/test_sequencewidget.py 
CVS: 	src/zope/app/browser/form/tests/test_vocabularywidget.py 
CVS: 	src/zope/app/browser/security/permissionwidget.py 
CVS: 	src/zope/app/browser/services/field.py 
CVS: 	src/zope/app/browser/services/registration/__init__.py 
CVS: 	src/zope/app/browser/services/tests/test_field_widget.py 
CVS: 	src/zope/app/browser/skins/rotterdam/editingwidgets.py 
CVS: 	src/zope/app/dav/configure.zcml src/zope/app/dav/widget.py 
CVS: 	src/zope/app/form/utility.py src/zope/app/form/widget.py 
CVS: 	src/zope/app/form/tests/test_utility.py 
CVS: 	src/zope/app/form/tests/test_widget_geddon_deprecations.py 
CVS: 	src/zope/app/interfaces/form.py 
CVS: 	src/zope/schema/_bootstrapfields.py 
CVS: ----------------------------------------------------------------------
Several changes related to widgets:

- Changed haveData to hasData.

- Propogated the use of Field.missing_value as an alternative to
  explicit checks for None and '' (empty string).

- Changed the default implementation of hasData (was haveData) to return
  True only if the one of the following was true:

  - setData had been called for the widget. Per IWidget, values passed to
    setData override all other data sources for a widget.

  - There is a value in the request form that corresponds to the widget
    field.

  Prior to this change, hasData would return False if the widget's data
  equaled the field's missing value. E.g., a text widget with an empty
  string would return False, indicating that it has no data, even though
  the empty string, a legitmate value, was submitted in the request.

  This change required a handful of other changes to reflect the new
  logic.

  The bulk of this change effected widget.py. To see how the new logic
  implementation of hasData effected widgets, see test_browserwidget.py
  and test_utility.py.

- Modified the update logic for objects to skip fields that are not
  present in the request form. Prior to this change, unspecified values
  (i.e. values not in the request form) would cause default values to be
  set for corresponding object fields.

- Exposed missing_value in initializer for Field - developers can now
  specify a missing value for a schema field.

- Changed the default implementation of field validation. Prior to this
  change, validation failed if a required value was missing. Now validation 
  is limited to validating non-missing values and the check for required
  values is performed elsewhere.

- Text fields have a missing_value of '' (empty string) instead of None.

- Widget related error classes have been revamped:

  - WidgetInputError, the base class for all widget related errors, now
    provides two attributes: widget, the widget associated with the
    error, and error_msg, a string describing the error,

  - WidgetInputError implements 'args' so it can be rendered like other
    errors.

  - MissingInputError uses a standard error message.

  - All uses of WidgetInputError and its subclasses have been updated
    to use the new class.

- Deleted class zope.app.browser.form.widget.PossiblyEmptyMeansMissing.
  This capability is now handled by setting a field's missing_value to '' 
  (empty string).

- Changed widget's _showData:

  - Renamed to getUnconvertedData to clarify the method meaning and signal
    its use as a public method.

  - Positioned the method as a complement to getData, which returns data
    in its converted form. getUnconvertedData returns data in its
    unconvereted form using _unconvert.

- Renamed the 'force' argument used in various setUpWidget methods in 
  utility.py to 'ignore_unspecified'. This argument is set to True to
  ensure that fields that are not in a form are not used to update their
  corresponding widget. This argument should be true during object
  updates (see editview.py) to ensure that unspecified fields are not
  updated.

- Removed the 'strict' argument from applyWidgetChanges since no one was
  using it and there's no clear application for it.
  



=== Zope3/src/zope/app/form/utility.py 1.22 => 1.22.2.1 ===
--- Zope3/src/zope/app/form/utility.py:1.22	Sun Jul 13 02:47:21 2003
+++ Zope3/src/zope/app/form/utility.py	Tue Jul 22 09:00:59 2003
@@ -38,9 +38,11 @@
 from zope.component import getView, getDefaultViewName
 from zope.schema import getFieldsInOrder
 from zope.schema.interfaces import ValidationError
+from zope.schema.errornames import RequiredMissing
 from zope.app.interfaces.form import IWidget
-from zope.app.interfaces.form import WidgetsError, MissingInputError
-from zope.app.interfaces.form import InputErrors
+from zope.app.interfaces.form import WidgetsError
+from zope.app.interfaces.form import MissingInputError
+from zope.app.interfaces.form import WidgetInputError
 from zope.component.interfaces import IViewFactory
 
 def _fieldlist(names, schema):
@@ -89,20 +91,23 @@
         return getattr(self.__widget, name)
 
 def setUpWidget(view, name, field, value=None, prefix=None,
-                force=False, vname=None, context=None):
+                ignore_unspecified=False, vname=None, context=None):
     """Set up a single view widget
 
     The widget will be an attribute of the view. If there is already
     an attribute of the given name, it must be a widget and it will be
-    initialized with the given value if not None.
+    initialized with the given value 
 
     If there isn't already a view attribute of the given name, then a
     widget will be created and assigned to the attribute.
+
+    If ignore_unspecified is True, widget values that aren't in the request
+    form (i.e. are not specified) will be ignored, preserving the field's
+    current value.
     """
     # Has a (custom) widget already been defined?
 
     wname = name+'_widget'
-
     widget = getattr(view, wname, None)
     installold = False
     if widget is None:
@@ -161,53 +166,54 @@
     if prefix:
         widget.setPrefix(prefix)
 
-    # XXX
-    # Only set data if the widget doesn't have any itself already from the
-    # request. This is a problem for something like a checkbox, where it
-    # always claims to have data, becuase when there is no name in the request
-    # for it, its value is False.
-    # This is only a problem when force is False.
-    #
-    # It took me a while to work out what 'force' means in all these methods.
-    # Perhaps it should be renamed 'preserveExistingData', and have the
-    # opposite meaning.
-    if value is not None and (force or not widget.haveData()):
+    if not ignore_unspecified and not widget.hasData():
         widget.setData(value)
 
 
-def setUpWidgets(view, schema, prefix=None, force=False,
+def setUpWidgets(view, schema, prefix=None, ignore_unspecified=False,
                  initial={}, names=None, context=None):
-    """Set up widgets for the fields defined by a schema
+    """Set up widgets for the fields defined by a schema.
 
+    Calls setUpWidget for each field in the specified schema. names can
+    contain a sequence of field names to use instead of those defined
+    in schema.
+
+    initial can be used to provide the intial widget values, keyed by
+    the field name.
+
+    If ignore_unspecified is True, values for fields that are not in the 
+    request form (i.e. are 'unspecified' in the form submission) are not 
+    used to update the field's widget data.
     """
     for (name, field) in _fieldlist(names, schema):
         setUpWidget(view, name, field, initial.get(name),
-                    prefix=prefix, force=force, context=context)
+                    prefix=prefix, ignore_unspecified=ignore_unspecified, 
+                    context=context)
 
 
-def setUpEditWidgets(view, schema, content=None, prefix=None, force=False,
-                     names=None, context=None):
+def setUpEditWidgets(view, schema, content=None, prefix=None, 
+                     ignore_unspecified=False, names=None, context=None):
     """Set up widgets for the fields defined by a schema
 
     Initial data is provided by content object attributes.
     No initial data is provided if the content lacks a named
     attribute, or if the named attribute value is None.
     """
-    _setUpWidgets(view, schema, content, prefix, force,
+    _setUpWidgets(view, schema, content, prefix, ignore_unspecified,
                   names, context, 'display', 'edit')
 
-def setUpDisplayWidgets(view, schema, content=None, prefix=None, force=False,
-                        names=None, context=None):
+def setUpDisplayWidgets(view, schema, content=None, prefix=None, 
+                        ignore_unspecified=False, names=None, context=None):
     """Set up widgets for the fields defined by a schema
 
     Initial data is provided by content object attributes.
     No initial data is provided if the content lacks a named
     attribute, or if the named attribute value is None.
     """
-    _setUpWidgets(view, schema, content, prefix, force,
+    _setUpWidgets(view, schema, content, prefix, ignore_unspecified,
                   names, context, 'display', 'display')
 
-def _setUpWidgets(view, schema, content, prefix, force,
+def _setUpWidgets(view, schema, content, prefix, ignore_unspecified,
                   names, context, displayname, editname):
     # Set up widgets for the fields defined by a schema.
     #
@@ -239,23 +245,23 @@
             value = None
 
         setUpWidget(view, name, field, value,
-                    prefix=prefix, force=force, vname=vname, context=context)
+                    prefix=prefix, ignore_unspecified=ignore_unspecified, 
+                    vname=vname, context=context)
 
-def haveWidgetsData(view, schema, names=None):
+def hasWidgetsData(view, schema, names=None):
     """Check if we have any user-entered data defined by a schema
 
     Returns true if any schema field related widget has data
     that was entered by the user.
     """
     for name, field in _fieldlist(names, schema):
-        if  getattr(view, name+'_widget').haveData():
+        if  getattr(view, name+'_widget').hasData():
             return True
 
     return False
 
-def applyWidgetsChanges(view, content, schema, strict=True,
-        names=None, set_missing=True, do_not_raise=False,
-        exclude_readonly=False):
+def applyWidgetsChanges(view, content, schema, names=None, 
+        do_not_raise=False, exclude_readonly=False):
     """Apply changes in widgets to the object."""
     errors = []
 
@@ -264,16 +270,17 @@
         widget = getattr(view, name+'_widget')
         if exclude_readonly and field.readonly:
             continue
-        if widget.haveData():
+
+        # only apply change if widget has data
+        if widget.hasData():
+            error = None
             try:
                 changed = widget.applyChanges(content) or changed
-            except InputErrors, v:
-                errors.append(v)
-        elif strict and field.required:
-            err = MissingInputError(name, widget.title, 'the field is required')
-            errors.append(err)
-        elif set_missing:
-            field.set(content, field.missing_value)
+            except WidgetInputError, v:
+                error = v
+            if error is not None:
+                errors.append(error)
+                widget.error = error
 
     if errors and not do_not_raise:
         raise WidgetsError(*errors)
@@ -321,15 +328,13 @@
         widget = getattr(view, name+'_widget')
         if exclude_readonly and widget.context.readonly:
             continue
-        if widget.haveData():
+        if widget.hasData():
             try:
                 result[name] = widget.getData()
-            except InputErrors, v:
+            except WidgetInputError, v:
                 errors.append(v)
         elif strict and field.required:
-            errors.append(MissingInputError(name, widget.title,
-                                            'the field is required')
-                          )
+            errors.append(MissingInputError(widget))
         elif set_missing:
             result[name] = field.missing_value
 
@@ -421,8 +426,7 @@
                 value = prop
             result[name] = value
         elif strict and field.required:
-            errors.append(MissingInputError(name, name,
-                                            'the field is required'))
+            errors.append(ValidationError('the field is required', name))
         elif set_missing:
             result[name] = field.missing_value
 


=== Zope3/src/zope/app/form/widget.py 1.7 => 1.7.2.1 ===
--- Zope3/src/zope/app/form/widget.py:1.7	Mon Jul 14 11:06:10 2003
+++ Zope3/src/zope/app/form/widget.py	Tue Jul 22 09:00:59 2003
@@ -26,7 +26,8 @@
     implements(IWidget)
 
     _prefix = 'field.'
-    _data = None
+    _data_marker = object()
+    _data = _data_marker
 
     def __init__(self, context, request):
         self.context = context
@@ -50,8 +51,8 @@
     def setData(self, value):
         self._data = value
 
-    def haveData(self):
-        raise TypeError("haveData has not been implemented")
+    def hasData(self):
+        raise TypeError("hasData has not been implemented")
 
     def getData(self):
         raise TypeError("getData has not been implemented")