[Zope3-checkins] CVS: Zope3/src/zope/app/browser/form - editview.py:1.28.2.1 vocabularywidget.py:1.46.10.1 widget.py:1.43.2.1

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


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

Modified Files:
      Tag: garrett-widgets-branch
	editview.py vocabularywidget.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/browser/form/editview.py 1.28 => 1.28.2.1 ===
--- Zope3/src/zope/app/browser/form/editview.py:1.28	Sun Jul 13 02:47:16 2003
+++ Zope3/src/zope/app/browser/form/editview.py	Tue Jul 22 09:00:33 2003
@@ -62,7 +62,6 @@
 
     def __init__(self, context, request):
         super(EditView, self).__init__(context, request)
-
         self._setUpWidgets()
 
     def _setUpWidgets(self):
@@ -71,7 +70,8 @@
             adapted = ContextWrapper(adapted, self.context, name='(adapted)')
         self.adapted = adapted
         setUpEditWidgets(self, self.schema, names=self.fieldNames,
-                         content=self.adapted)
+                         content=self.adapted,
+                         ignore_unspecified=Update in self.request)
 
     def setPrefix(self, prefix):
         for widget in self.widgets():
@@ -105,7 +105,7 @@
                 self.errors = errors
                 status = u"An error occured."
             else:
-                setUpEditWidgets(self, self.schema, force=1,
+                setUpEditWidgets(self, self.schema, ignore_unspecified=True,
                                  names=self.fieldNames)
                 if changed:
                     status = "Updated %s" % datetime.utcnow()


=== Zope3/src/zope/app/browser/form/vocabularywidget.py 1.46 => 1.46.10.1 ===
--- Zope3/src/zope/app/browser/form/vocabularywidget.py:1.46	Wed Jun 25 17:18:52 2003
+++ Zope3/src/zope/app/browser/form/vocabularywidget.py	Tue Jul 22 09:00:33 2003
@@ -34,6 +34,7 @@
 from zope.schema.interfaces import IIterableVocabularyQuery
 from zope.schema.interfaces import ValidationError
 
+from zope.security.proxy import trustedRemoveSecurityProxy
 
 # These widget factories delegate to the vocabulary on the field.
 
@@ -85,9 +86,10 @@
 # Helper functions for the factories:
 
 def _get_vocabulary_widget(field, request, viewname):
-    view = getView(field.vocabulary, "field-%s-widget" % viewname, request)
-    view.setField(field)
-    return view
+      view = getView(field.vocabulary, "field-%s-widget" % viewname, request)
+      view = trustedRemoveSecurityProxy(view)
+      view.setField(field)
+      return view
 
 def _get_vocabulary_edit_widget(field, request, modifier=''):
     if modifier:
@@ -185,7 +187,7 @@
 
     def __call__(self):
         if self._data is None:
-            if self.haveData():
+            if self.hasData():
                 data = self.getData(True)
             else:
                 data = self._getDefault()
@@ -203,36 +205,33 @@
             try:
                 term = self.context.vocabulary.getTermByToken(token)
             except LookupError:
-                raise WidgetInputError(
-                    self.context.__name__,
-                    self.context.title,
+                raise WidgetInputError(self, 
                     "token %r not found in vocabulary" % token)
             else:
                 L.append(term.value)
         return L
 
-    _have_field_data = False
+    _has_field_data = False
 
     def getData(self, optional=False):
         data = self._compute_data()
         field = self.context
         if data is None:
             if field.required and not optional:
-                raise MissingInputError(field.__name__, field.title,
-                                        'the field is required')
+                raise MissingInputError(self)
             return self._getDefault()
         elif not optional:
             try:
                 field.validate(data)
             except ValidationError, v:
-                raise WidgetInputError(self.context.__name__, self.title, v)
+                raise WidgetInputError(self, v[0])
         return data
 
     def _emptyMarker(self):
         return "<input name='%s' type='hidden' value='1' />" % (
             self.empty_marker_name)
 
-    def haveData(self):
+    def hasData(self):
         return (self.name in self.request.form or
                 self.empty_marker_name in self.request.form)
 
@@ -245,9 +244,10 @@
             "It may be inherited from the mix-in classes SingleDataHelper\n"
             "or MultiDataHelper (from zope.app.browser.form.vocabularywidget)")
 
-    def _showData(self):
+    def getUnconvertedData(self):
         raise NotImplementedError(
-            "vocabulary-based widgets don't use the _showData() method")
+            "vocabulary-based widgets don't use the " \
+            "getUnconvertedData() method")
 
     def _convert(self, value):
         raise NotImplementedError(
@@ -490,7 +490,7 @@
     This widget can be used when the number of selections isn't going
     to be very large.
     """
-    implements(implementedBy(widget.SingleItemsWidget))
+    implements(implementedBy(widget.SingleItemsWidget), )
     propertyNames = VocabularyEditWidgetBase.propertyNames + ['firstItem']
     firstItem = False
 
@@ -662,10 +662,7 @@
             try:
                 term = self.vocabulary.getTermByToken(token)
             except LookupError:
-                # XXX unsure what to pass to exception constructor
-                raise WidgetInputError(
-                    "(query view for %s)" % self.context,
-                    "(query view for %s)" % self.context,
+                raise WidgetInputError(self.widget, 
                     "token %r not in vocabulary" % token)
             else:
                 self.query_selections.append(term.value)


=== Zope3/src/zope/app/browser/form/widget.py 1.43 => 1.43.2.1 ===
--- Zope3/src/zope/app/browser/form/widget.py:1.43	Tue Jul 15 12:08:49 2003
+++ Zope3/src/zope/app/browser/form/widget.py	Tue Jul 22 09:00:33 2003
@@ -54,7 +54,7 @@
     'field.foo'
     >>> widget.title
     u'Foo'
-    >>> int(widget.haveData())
+    >>> int(widget.hasData())
     1
     >>> widget.getData()
     u'hello\\r\\nworld'
@@ -64,27 +64,21 @@
     1
     >>> widget.setData('Hey\\nfolks')
     >>> widget.getData()
-    u'hello\\r\\nworld'
+    'Hey\\nfolks'
     >>> widget.error is None
     1
 
-    >>> widget.setPrefix('test')
-    >>> widget.name
-    'test.foo'
-    >>> widget.error is None
-    1
-    >>> int(widget.haveData())
-    0
-    >>> widget.getData()
+    >>> widget.setData(field.missing_value)
+    >>> class SampleContent:
+    ...    foo = 'bar'
+    >>> widget.applyChanges(SampleContent())
     Traceback (most recent call last):
     ...
-    MissingInputError: ('foo', u'Foo', u'Input is required')
-    >>> widget.error is not None
-    1
+    MissingInputError: ('field.foo', u'Input is required')
     >>> field.required = False
-    >>> int(widget.required)
+    >>> widget.required
     0
-    >>> widget.getData() is None
+    >>> widget.applyChanges(SampleContent())
     1
     >>> widget.error is None
     1
@@ -94,7 +88,7 @@
 
     >>> setUp()
     >>> print widget.label()
-    <label for="test.foo">Foo</label>
+    <label for="field.foo">Foo</label>
     >>> tearDown()
     
     """
@@ -108,43 +102,108 @@
     type = 'text'
     cssClass = ''
     extra = ''
-    _missing = None
+    _missing = ''
     error = None
+    
+    # marker object for field values that are unspecified - i.e. are
+    # not in the form (this is different from _missing)
+    unspecified = object()
+
+
+    def hasData(self):
+        return self._data != self._data_marker or \
+            self.name in self.request.form
+
+    def getData(self):
+        """Returns the current 'data' for the widget.
+
+        If setData was called, returns the value specified in the call.
+
+        If setData was not called, attempts to return a validated and
+        converted value using user-specified data in the form.
+
+        If the widget's field is not in the form, returns this object's
+        'unspecified' attribute.
+
+        Raises ConversionError if the user-specified value cannot be
+        converted to a legal value for the field.
 
-    def haveData(self):
-        if self.name in self.request.form:
-            return self._convert(self.request[self.name]) != self._missing
-        return False
+        Raises WidgetInputError if the user-specified value violates
+        one of the field's validation rules.
+
+        If an error is raised during this method, the error is stored
+        in this object's error attribute.
+        """
 
-    def getData(self, optional=0):
         field = self.context
-        value = self.request.form.get(self.name, self) # self used as marker
         self.error = None
-        if value is self:
-            # No user input
-            if field.required and not optional:
-                self.error = MissingInputError(
-                        field.__name__, field.title,  RequiredMissing
-                        )
-                raise self.error
-            return field.default
 
-        value = self._convert(value)
-        if value is not None and not optional:
+        # always return values set by setData
+        if self._data != self._data_marker:
+            return self._data
+
+        value = self.request.form.get(self.name, self.unspecified)
+        if value is self.unspecified:
+            return self.unspecified
+
+        # try to convert the value
+        try:
+            value = self._convert(value)
+        except ConversionError, e:
+            self.error = e
+            raise self.error
+
+        # validate value
+        if value != field.missing_value:
             try:
                 field.validate(value)
-            except ValidationError, v:
-                self.error = WidgetInputError(self.context.__name__,
-                                       self.title, v)
+            except ValidationError, e:
+                self.error = WidgetInputError(self, e[0])
                 raise self.error
+
         return value
 
+    def getUnconvertedData(self):
+        """Returns the current data for the widget, unconverted.
+
+        Unconverted data is in a format suitable for use as the 
+        widget's 'value' in an HTML form.
+
+        If setData was called, returns the value specified in the call
+        in a format suitable for HTML forms.
+
+        If setData was not called, returns the value specified by the user,
+        validated by the widget, in a form suitable for HTML forms.
+
+        If the form does not contain a value (i.e. getData returns
+        this object's unspecified attribute), returns self._getDefault().
+
+        If a value specified by the user is not valid, returns the
+        invalid value directly specified in the form. Note that self.error
+        will contain the validation error that was raised.
+
+        """
+
+        try:
+            data = self.getData()
+        except WidgetInputError, e:
+            # user specified invalid value - return original value
+            specified = self.request.form.get(self.name, self)
+            assert(specified is not self) # error shouldn't have been raised
+            return specified
+        else:
+            if data is self.unspecified:
+                return self._getDefault()
+            return self._unconvert(data)
+
     def validate(self):
         self.getData()
 
     def applyChanges(self, content):
         field = self.context
-        value = self.getData()
+        value = self.getData()  # validates and converts data
+        if value == field.missing_value and field.required:
+            raise MissingInputError(self)
         change = field.query(content, self) != value
         if change:
             field.set(content, value)
@@ -152,25 +211,14 @@
 
     def _convert(self, value):
         if value == self._missing:
-            return None
+            return self.context.missing_value
         return value
 
     def _unconvert(self, value):
-        if value is None:
-            return ''
+        if value == self.context.missing_value:
+            return self._missing
         return value
 
-    def _showData(self):
-        if self._data is None:
-            if self.haveData():
-                data = self.getData(optional=1)
-            else:
-                data = self._getDefault()
-        else:
-            data = self._data
-
-        return self._unconvert(data)
-
     def _getDefault(self):
         # Return the default value for this widget;
         # may be overridden by subclasses.
@@ -181,7 +229,7 @@
                              type = self.getValue('type'),
                              name = self.name,
                              id = self.name,
-                             value = self._showData(),
+                             value = self.getUnconvertedData(),
                              cssClass = self.getValue('cssClass'),
                              extra = self.getValue('extra'))
 
@@ -190,7 +238,7 @@
                              type = 'hidden',
                              name = self.name,
                              id = self.name,
-                             value = self._showData(),
+                             value = self.getUnconvertedData(),
                              cssClass = self.getValue('cssClass'),
                              extra = self.getValue('extra'))
 
@@ -239,7 +287,7 @@
 class DisplayWidget(BrowserWidget):
 
     def __call__(self):
-        return self._showData()
+        return self.getUnconvertedData()
 
 class CheckBoxWidget(BrowserWidget):
     """Checkbox widget
@@ -250,7 +298,7 @@
     >>> request = TestRequest(form={'field.foo.used': u'on',
     ...                             'field.foo': u'on'})
     >>> widget = CheckBoxWidget(field, request)
-    >>> int(widget.haveData())
+    >>> int(widget.hasData())
     1
     >>> int(widget.getData())
     1
@@ -310,7 +358,7 @@
     extra = ''
 
     def __call__(self):
-        data = self._showData()
+        data = self.getUnconvertedData()
         if data:
             kw = {'checked': None}
         else:
@@ -336,30 +384,13 @@
 
     def _unconvert(self, value):
         return value and "on" or ""
-        return value == 'on'
 
-    def haveData(self):
-        return (
+    def hasData(self):
+        return super(CheckBoxWidget, self).hasData() or \
             self.name+".used" in self.request.form
-            or
-            self.name in self.request.form
-            )
-
-    def getData(self, optional=0):
-        # When it's checked, its value is 'on'.
-        # When a checkbox is unchecked, it does not appear in the form data.
-        value = self.request.form.get(self.name, 'off')
-        return value == 'on'
-
-class PossiblyEmptyMeansMissing(BrowserWidget):
 
-    def _convert(self, value):
-        value = super(PossiblyEmptyMeansMissing, self)._convert(value)
-        if not value and getattr(self.context, 'min_length', 1) > 0:
-            return None
-        return value
 
-class TextWidget(PossiblyEmptyMeansMissing, BrowserWidget):
+class TextWidget(BrowserWidget):
     """Text widget.
 
     Single-line text (unicode) input
@@ -369,7 +400,7 @@
     >>> field = TextLine(__name__='foo', title=u'on')
     >>> request = TestRequest(form={'field.foo': u'Bob'})
     >>> widget = TextWidget(field, request)
-    >>> int(widget.haveData())
+    >>> int(widget.hasData())
     1
     >>> widget.getData()
     u'Bob'
@@ -417,60 +448,61 @@
     displayWidth = 20
     displayMaxWidth = ""
     extra = ''
-    # XXX Alex Limi doesn't like this!
-    # style = "width:100%"
     style = ''
     __values = None
 
     def __init__(self, *args):
         super(TextWidget, self).__init__(*args)
-
-        if self.context.allowed_values is not None:
-            values = list(self.context.allowed_values)
-            values.sort()
+        field = self.context
+        if field.allowed_values is not None:
+            values = []
+            # If field is optional and missing_value isn't in 
+            # allowed_values, add an additional option at top to
+            # represent field.missing_value.
+            if not field.required and \
+                field.missing_value not in field.allowed_values:
+                values.append(field.missing_value)
+            values += list(field.allowed_values)
             self.__values = values
-            if values:
-                self._missing = values[-1]+'x'
-            else:
-                self._missing = ''
 
-    def haveData(self):
-        if super(TextWidget, self).haveData():
-            if (self.request.get(self.name)
-                != self._missing):
-                return True
-        return False
+    def _getSelectableValues(self):
+        return self.__values
 
     def _select(self):
-        selected = self._showData()
-        result = ['<select id="%s" name="%s">'
-                  % (self.name, self.name)]
-
-        values = self.__values
+        result = ['<select id="%s" name="%s">' % (self.name, self.name)]
 
-        if not self.context.required or selected is None:
-            result.append('<option value="%s"></option>' % self._missing)
+        field = self.context
+        try:
+            selected = self.getData()
+        except:
+            selected = self.getUnconvertedData()
 
+        values = self._getSelectableValues()
         for value in values:
+            unconverted = self._unconvert(value)
             if value == selected:
-                result.append("<option selected>%s</option>" % value)
+                result.append('<option value="%s" selected>%s</option>' % \
+                   (unconverted, unconverted))
             else:
-                result.append("<option>%s</option>" % value)
+                result.append('<option value="%s">%s</option>' % \
+                   (unconverted, unconverted))
 
         result.append('</select>')
         return '\n\t'.join(result)
 
+
     def __call__(self):
         if self.__values is not None:
             return self._select()
 
         displayMaxWidth = self.getValue('displayMaxWidth') or 0
+
         if displayMaxWidth > 0:
             return renderElement(self.getValue('tag'),
                                  type = self.getValue('type'),
                                  name = self.name,
                                  id = self.name,
-                                 value = self._showData(),
+                                 value = self.getUnconvertedData(),
                                  cssClass = self.getValue('cssClass'),
                                  style = self.style,
                                  size = self.getValue('displayWidth'),
@@ -481,12 +513,13 @@
                                  type = self.getValue('type'),
                                  name = self.name,
                                  id = self.name,
-                                 value = self._showData(),
+                                 value = self.getUnconvertedData(),
                                  cssClass = self.getValue('cssClass'),
                                  style = self.style,
                                  size = self.getValue('displayWidth'),
                                  extra = self.getValue('extra'))
 
+
 class Bytes(BrowserWidget):
 
     def _convert(self, value):
@@ -495,7 +528,7 @@
             try:
                 value = value.encode('ascii')
             except UnicodeError, v:
-                raise ConversionError("Invalid textual data", v)
+                raise ConversionError(self, "Invalid textual data")
 
         return value
 
@@ -509,7 +542,7 @@
     >>> field = BytesLine(__name__='foo', title=u'on')
     >>> request = TestRequest(form={'field.foo': u'Bob'})
     >>> widget = BytesWidget(field, request)
-    >>> int(widget.haveData())
+    >>> int(widget.hasData())
     1
     >>> widget.getData()
     'Bob'
@@ -524,7 +557,9 @@
             try:
                 return int(value)
             except ValueError, v:
-                raise ConversionError("Invalid integer data", v)
+                raise ConversionError(self, "Invalid integer data")
+
+        return self.context.missing_value
 
 
 class FloatWidget(TextWidget):
@@ -535,7 +570,10 @@
             try:
                 return float(value)
             except ValueError, v:
-                raise ConversionError("Invalid floating point data", v)
+                raise ConversionError(self, "Invalid floating point data")
+
+        return self.context.missing_value
+
 
 class DatetimeWidget(TextWidget):
     """Datetime entry widget."""
@@ -546,9 +584,12 @@
             try:
                 return parseDatetimetz(value)
             except (DateTimeError, ValueError, IndexError), v:
-                raise ConversionError("Invalid datetime data", v)
+                raise ConversionError(self, "Invalid datetime data")
 
-class TextAreaWidget(PossiblyEmptyMeansMissing, BrowserWidget):
+        return self.context.missing_value
+
+
+class TextAreaWidget(BrowserWidget):
     """TextArea widget.
 
     Multi-line text (unicode) input.
@@ -558,7 +599,7 @@
     >>> field = Text(__name__='foo', title=u'on')
     >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
     >>> widget = TextAreaWidget(field, request)
-    >>> int(widget.haveData())
+    >>> int(widget.hasData())
     1
     >>> widget.getData()
     u'Hello\\nworld!'
@@ -626,7 +667,7 @@
                              rows = self.getValue('height'),
                              cols = self.getValue('width'),
                              style = self.style,
-                             contents = self._showData(),
+                             contents = self.getUnconvertedData(),
                              extra = self.getValue('extra'))
 
 class BytesAreaWidget(Bytes, TextAreaWidget):
@@ -639,7 +680,7 @@
     >>> field = Bytes(__name__='foo', title=u'on')
     >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
     >>> widget = BytesAreaWidget(field, request)
-    >>> int(widget.haveData())
+    >>> int(widget.hasData())
     1
     >>> widget.getData()
     'Hello\\nworld!'
@@ -702,7 +743,7 @@
                                  size = self.getValue('displayWidth'),
                                  extra = self.getValue('extra'))
 
-    def haveData(self):
+    def hasData(self):
         file = self.request.form.get(self.name)
         if file is None:
             return False
@@ -716,25 +757,21 @@
         except AttributeError:
             return False
 
-        seek(0)
-        if read(1):
-            return True
-
-        return False
+        return True
 
     def _convert(self, value):
         try:
             seek = value.seek
             read = value.read
         except AttributeError, e:
-            raise ConversionError('Value is not a file object', e)
+            raise ConversionError(self, 'Value is not a file object')
         else:
             seek(0)
             data = read()
-            if data or getattr(value, 'filename', ''):
+            if data:
                 return data
             else:
-                return None
+                return self.context.missing_value
 
 
 class ItemsWidget(BrowserWidget):
@@ -800,7 +837,7 @@
     size = 5
 
     def __call__(self):
-        renderedItems = self.renderItems(self._showData())
+        renderedItems = self.renderItems(self.getUnconvertedData())
         return renderElement('select',
                               name = self.name,
                               id = self.name,
@@ -825,7 +862,7 @@
     orientation = "vertical"
 
     def __call__(self):
-        rendered_items = self.renderItems(self._showData())
+        rendered_items = self.renderItems(self.getUnconvertedData())
         orientation = self.getValue('orientation')
         if orientation == 'horizontal':
             return "&nbsp;&nbsp;".join(rendered_items)
@@ -877,6 +914,9 @@
     default = []
 
     def _convert(self, value):
+        # XXX-GDS I'm not sure value will ever be None here -- maybe _missing
+        # if value == self._missing:
+        #     return self.context.missing_value
         if value is None:
             return []
         if isinstance(value, ListTypes):
@@ -926,7 +966,7 @@
     size = 5
 
     def __call__(self):
-        rendered_items = self.renderItems(self._showData())
+        rendered_items = self.renderItems(self.getUnconvertedData())
         return renderElement('select',
                               name = self.name,
                               id = self.name,
@@ -950,7 +990,7 @@
     orientation = "vertical"
 
     def __call__(self):
-        rendered_items = self.renderItems(self._showData())
+        rendered_items = self.renderItems(self.getUnconvertedData())
         orientation = self.getValue('orientation')
         if orientation == 'horizontal':
             return "&nbsp;&nbsp;".join(rendered_items)
@@ -1086,6 +1126,7 @@
 
     # XXX applyChanges isn't reporting "change" correctly (we're
     # re-generating the sequence with every edit, and need to be smarter)
+    # XXX-GDS this is no different from BrowserWidget -- delete??
     def applyChanges(self, content):
         field = self.context
         value = self.getData()
@@ -1094,7 +1135,7 @@
             field.set(content, value)
         return change
 
-    def haveData(self):
+    def hasData(self):
         """Is there input data for the field
 
         Return True if there is data and False otherwise.
@@ -1271,13 +1312,13 @@
 
         return changes
 
-    def haveData(self):
+    def hasData(self):
         """Is there input data for the field
 
         Return True if there is data and False otherwise.
         """
         for name, widget in self.getSubWidgets():
-            if widget.haveData():
+            if widget.hasData():
                 return True
         return False