Commits

Chris McDonough  committed 6907452

simplify defaulting

  • Participants
  • Parent commits 7706a1c
  • Branches default_overhaul

Comments (0)

Files changed (10)

File deform/field.py

     def serialize(self, cstruct, readonly=False):
         """ Serialize the cstruct into HTML.  If ``readonly`` is
         ``True``, render a read-only rendering (no input fields)."""
-        if cstruct is colander.default:
-            cstruct = colander.null
         return self.widget.serialize(self, cstruct=cstruct, readonly=readonly)
 
     def deserialize(self, pstruct):
         """ Deserialize the pstruct into a cstruct."""
         return self.widget.deserialize(self, pstruct)
 
-    def render(self, appstruct=colander.default, readonly=False):
+    def render(self, appstruct=colander.null, readonly=False):
         """ Render the field (or form) to HTML using ``appstruct`` as
         a set of default values.  ``appstruct`` is typically a
         dictionary of application values matching the schema used by

File deform/tests/test_field.py

         self.assertEqual(field.serialize('abc'), 'abc')
         self.assertEqual(widget.rendered, 'writable')
 
-    def test_serialize_default(self):
-        from colander import default
+    def test_serialize_null(self):
         from colander import null
         schema = DummySchema()
         field = self._makeOne(schema)
         widget = field.widget = DummyWidget()
-        self.assertEqual(field.serialize(default), null)
+        self.assertEqual(field.serialize(null), null)
         self.assertEqual(widget.rendered, 'writable')
 
     def test_deserialize(self):

File deform/tests/test_functional.py

         self.assertEqual(result, expected)
 
     def test_validate(self):
+        from colander import null
         from deform.exception import ValidationFailure
-        from colander import default
         schema = self._makeSchema()
         form = self._makeForm(schema)
         try:
         self.assertEqual(
             ve.cstruct,
             {
-                'series': {'dates': [], 'name': default},
+                'series': {'dates': [], 'name': null},
                 'cool': 'false',
-                'name': default,
-                'title': default
+                'name': null,
+                'title': null,
              }
             )
         

File deform/tests/test_schema.py

         from deform.schema import FileData
         return FileData()
 
-    def test_deserialize_default(self):
-        from colander import default
-        typ = self._makeOne()
-        node = DummySchemaNode()
-        result = typ.deserialize(node, default)
-        self.assertEqual(result, default)
-
     def test_deserialize_null(self):
         from colander import null
         typ = self._makeOne()
         result = typ.deserialize(node, null)
         self.assertEqual(result, null)
 
-    def test_deserialize_not_default(self):
+    def test_deserialize_not_null(self):
         typ = self._makeOne()
         node = DummySchemaNode()
         result = typ.deserialize(node, '123')

File deform/tests/test_widget.py

         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], '')
 
+    def test_serialize_None(self):
+        widget = self._makeOne()
+        renderer = DummyRenderer()
+        field = DummyField(None, renderer=renderer)
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], '')
+
     def test_serialize_not_null(self):
         widget = self._makeOne()
         renderer = DummyRenderer()
 
     def test_deserialize_null(self):
         from colander import null
-        from colander import default
         widget = self._makeOne(strip=False)
         field = DummyField()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_emptystring(self):
-        from colander import default
+        from colander import null
         widget = self._makeOne()
         field = DummyField()
         pstruct = ''
         result = widget.deserialize(field, pstruct)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
 class TestDateInputWidget(unittest.TestCase):
     def _makeOne(self, **kw):
         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], '')
 
+    def test_serialize_None(self):
+        widget = self._makeOne()
+        renderer = DummyRenderer()
+        field = DummyField(None, renderer=renderer)
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], '')
+
     def test_serialize_not_null(self):
         widget = self._makeOne()
         renderer = DummyRenderer()
         self.assertEqual(renderer.kw['cstruct'], cstruct)
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_emptystring(self):
-        from colander import default
+        from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, '')
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
 class TestHiddenWidget(unittest.TestCase):
     def _makeOne(self, **kw):
         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], '')
 
+    def test_serialize_None(self):
+        widget = self._makeOne()
+        renderer = DummyRenderer()
+        field = DummyField(None, renderer=renderer)
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], '')
+
     def test_serialize_not_null(self):
         widget = self._makeOne()
         renderer = DummyRenderer()
         self.assertEqual(result, 'abc')
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         widget = self._makeOne(strip=False)
         field = DummyField()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
 class TestPasswordWidget(TestTextInputWidget):
     def _makeOne(self, **kw):
         from deform.widget import CheckboxWidget
         return CheckboxWidget(**kw)
 
-    def test_serialize_null(self):
-        from colander import null
-        renderer = DummyRenderer()
-        schema = DummySchema()
-        field = DummyField(schema, renderer)
-        widget = self._makeOne()
-        widget.serialize(field, null)
-        self.assertEqual(renderer.template, widget.template)
-        self.assertEqual(renderer.kw['field'], field)
-        self.assertEqual(renderer.kw['cstruct'], 'false')
-
     def test_serialize_not_null(self):
         renderer = DummyRenderer()
         schema = DummySchema()
         self.assertEqual(renderer.kw['cstruct'], cstruct)
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_other(self):
         widget = self._makeOne()
         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], '')
 
+    def test_serialize_None(self):
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer)
+        widget = self._makeOne()
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], '')
+
     def test_serialize_null_alternate_null_value(self):
         from colander import null
         renderer = DummyRenderer()
         self.assertEqual(renderer.kw['cstruct'], cstruct)
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_null_value(self):
-        from colander import default
+        from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, '')
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_other(self):
         widget = self._makeOne()
         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], ())
 
+    def test_serialize_None(self):
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer)
+        widget = self._makeOne()
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], ())
+
     def test_serialize_not_null(self):
         renderer = DummyRenderer()
         schema = DummySchema()
         self.assertEqual(renderer.kw['cstruct'], cstruct)
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_single_string(self):
         # If only one checkbox was checked:  DAMN HTTP forms!
         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], '')
 
+    def test_serialize_None(self):
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer)
+        widget = self._makeOne()
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], '')
+
     def test_serialize_true(self):
         renderer = DummyRenderer()
         schema = DummySchema()
         self.assertEqual(renderer.kw['cstruct'], True)
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_empty(self):
-        from colander import default
+        from colander import null
         widget = self._makeOne()
         field = DummyField()
         result = widget.deserialize(field, {'value':'',
                                             'confirm':''})
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
         self.assertEqual(field.error, None)
 
     def test_deserialize_nonmatching(self):
         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], {})
 
+    def test_serialize_None(self):
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer)
+        tmpstore = DummyTmpStore()
+        widget = self._makeOne(tmpstore)
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], {})
+
     def test_serialize_uid_not_in_tmpstore(self):
         renderer = DummyRenderer()
         schema = DummySchema()
         self.assertEqual(tmpstore['uid'], existing)
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         schema = DummySchema()
         field = DummyField(schema)
         tmpstore = DummyTmpStore()
         widget = self._makeOne(tmpstore)
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_no_file_selected_no_previous_file(self):
-        from colander import default
+        from colander import null
         schema = DummySchema()
         field = DummyField(schema)
         tmpstore = DummyTmpStore()
         widget = self._makeOne(tmpstore)
         result = widget.deserialize(field, {})
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_no_file_selected_with_previous_file(self):
         schema = DummySchema()
         self.assertEqual(result, 'abc')
 
     def test_deserialize_no_file_selected_with_previous_file_missing(self):
-        from colander import default
+        from colander import null
         schema = DummySchema()
         field = DummyField(schema)
         tmpstore = DummyTmpStore()
         widget = self._makeOne(tmpstore)
         result = widget.deserialize(field, {'uid':'uid'})
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_file_selected_no_previous_file(self):
         schema = DummySchema()
         self.assertEqual(result, '01-2-3')
 
     def test_deserialize_null(self):
-        from colander import default
         from colander import null
         schema = DummySchema()
         field = DummyField(schema, None)
         widget = self._makeOne()
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_incomplete(self):
         schema = DummySchema()
         self.assertEqual(renderer.kw['field'], field)
         self.assertEqual(renderer.kw['cstruct'], {})
 
+    def test_serialize_None(self):
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer)
+        widget = self._makeOne()
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], {})
+
     def test_serialize_not_null(self):
         renderer = DummyRenderer()
         schema = DummySchema()
         self.assertEqual(renderer.kw['cstruct'], [])
         self.assertEqual(renderer.template, widget.template)
 
+    def test_serialize_None(self):
+        renderer = DummyRenderer('abc')
+        schema = DummySchema()
+        field = DummyField(schema, renderer)
+        inner = DummyField()
+        field.children=[inner]
+        widget = self._makeOne()
+        result = widget.serialize(field, None)
+        self.assertEqual(result, 'abc')
+        self.assertEqual(len(renderer.kw['subfields']), 0)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], [])
+        self.assertEqual(renderer.template, widget.template)
+
     def test_serialize_null_render_initial_item(self):
         from colander import null
         renderer = DummyRenderer('abc')
 
     def test_deserialize_null(self):
         from colander import null
-        from colander import default
         widget = self._makeOne(strip=False)
         schema = DummySchema()
         schema.required = False
         field = DummyField(schema=schema)
         result = widget.deserialize(field, null)
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_deserialize_emptystring(self):
-        from colander import default
+        from colander import null
         widget = self._makeOne(strip=False)
         schema = DummySchema()
         schema.required = False
         field = DummyField(schema=schema)
         result = widget.deserialize(field, '')
-        self.assertEqual(result, default)
+        self.assertEqual(result, null)
 
     def test_handle_error_outermost_has_msg(self):
         widget = self._makeOne()

File deform/widget.py

 import StringIO
 import urllib
 
-from colander import default
 from colander import Invalid
 from colander import null
 
         raise NotImplementedError
 
     def handle_error(self, field, error):
+        """
+        The ``handle_error`` method of a widget must:
+
+        - Set the ``error`` attribute of the ``field`` object it is
+          passed, if the ``error`` attribute has not already been set.
+
+        - Call the ``handle_error`` method of each subfield which also
+          has an error (as per the ``error`` argument's ``children``
+          attribute).
+        """
         if field.error is None:
             field.error = error
         # XXX exponential time
     mask_placeholder = "_"
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = ''
         template = readonly and self.readonly_template or self.template
         return field.renderer(template, field=field, cstruct=cstruct)
 
     def deserialize(self, field, pstruct):
         if pstruct is null:
-            return default
+            return null
         if self.strip:
             pstruct = pstruct.strip()
         if not pstruct:
-            return default
+            return null
         return pstruct
 
 class DateInputWidget(Widget):
     size = None
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = ''
         template = readonly and self.readonly_template or self.template
         return field.renderer(template, field=field, cstruct=cstruct)
 
     def deserialize(self, field, pstruct):
         if pstruct in ('', null):
-            return default
+            return null
         return pstruct
 
 class TextAreaWidget(TextInputWidget):
     hidden = True
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = ''
         return field.renderer(self.template, field=field, cstruct=cstruct)
 
     def deserialize(self, field, pstruct):
         if pstruct is null:
-            return default
+            return null
         return pstruct
 
 class CheckboxWidget(Widget):
     readonly_template = 'readonly/checkbox'
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
-            cstruct = self.false_val
         template = readonly and self.readonly_template or self.template
         return field.renderer(template, field=field, cstruct=cstruct)
 
     null_value
         The value which represents the null value.  When the null
         value is encountered during serialization, the
-        :attr:`colander.default` sentinel is returned to the caller.
+        :attr:`colander.null` sentinel is returned to the caller.
         Default: ``''`` (the empty string).
 
     template
     values = ()
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = self.null_value
         template = readonly and self.readonly_template or self.template
         return field.renderer(template, field=field, cstruct=cstruct)
 
     def deserialize(self, field, pstruct):
         if pstruct in (null, self.null_value):
-            return default
+            return null
         return pstruct
 
 class RadioChoiceWidget(SelectWidget):
     readonly_template = 'readonly/checkbox_choice'
     values = ()
 
-
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = ()
         template = readonly and self.readonly_template or self.template
         return field.renderer(template, field=field, cstruct=cstruct)
 
     def deserialize(self, field, pstruct):
         if pstruct is null:
-            return default
+            return null
         if isinstance(pstruct, basestring):
             return (pstruct,)
         return tuple(pstruct)
     mask_placeholder = "_"
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = ''
         confirm = getattr(field, 'confirm', '')
         template = readonly and self.readonly_template or self.template
 
     def deserialize(self, field, pstruct):
         if pstruct is null:
-            return default
+            return null
         value = pstruct.get('value') or ''
         confirm = pstruct.get('confirm') or ''
         field.confirm = confirm
         if (value or confirm) and (value != confirm):
             raise Invalid(field.schema, self.mismatch_message, value)
         if not value:
-            return default
+            return null
         return value
 
 class CheckedPasswordWidget(CheckedInputWidget):
     category = 'structural'
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = {}
         template = readonly and self.readonly_template or self.template
         return field.renderer(template, field=field, cstruct=cstruct,
         return proto
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             if self.render_initial_item:
                 cstruct = [null]
             else:
             [random.choice(string.uppercase+string.digits) for i in range(10)])
 
     def serialize(self, field, cstruct, readonly=False):
-        if cstruct is null:
+        if cstruct in (null, None):
             cstruct = {}
         if cstruct:
             uid = cstruct['uid']
 
     def deserialize(self, field, pstruct):
         if pstruct is null:
-            return default
+            return null
 
         upload = pstruct.get('upload')
         uid = pstruct.get('uid')
             # the upload control had no file selected
             if uid is None:
                 # no previous file exists
-                return default
+                return null
             else:
                 # a previous file should exist
                 data = self.tmpstore.get(uid)
                 # but if it doesn't, don't blow up
                 if data is None:
-                    return default
+                    return null
 
         return data
 
 
     def deserialize(self, field, pstruct):
         if pstruct is null:
-            return default
+            return null
         else:
             if self.assume_y2k:
                 year = pstruct['year']
         
     def deserialize(self, field, pstruct):
         if pstruct is null:
-            return default
+            return null
         if not pstruct.strip():
-            return default
+            return null
         try:
             infile = StringIO.StringIO(pstruct)
             reader = csv.reader(infile)

File deformdemo/app.py

         self.request = request
         self.macros = get_template('templates/main.pt').macros
 
-    def render_form(self, form, appstruct=colander.default, submitted='submit',
+    def render_form(self, form, appstruct=colander.null, submitted='submit',
                     success=None, readonly=False):
 
         captured = None
         form = deform.Form(schema, buttons=('submit',))
         return self.render_form(form)
 
+    @bfg_view(renderer='templates/form.pt', name='nonrequiredfields')
+    @demonstrate('Non-Required Fields')
+    def nonrequiredfields(self):
+        class Schema(colander.Schema):
+            required = colander.SchemaNode(
+                colander.String(),
+                description='Required Field'
+                )
+            notrequired = colander.SchemaNode(
+                colander.String(),
+                missing=u'',
+                description='Unrequired Field')
+        schema = Schema()
+        form = deform.Form(schema, buttons=('submit',))
+        return self.render_form(form)
+
     @bfg_view(renderer='templates/form.pt', name='unicodeeverywhere')
     @demonstrate('Unicode Everywhere')
     def unicodeeverywhere(self):
 
     def deserialize(self, field, pstruct):
         text = self.widget.deserialize(field, pstruct)
-        if text in (colander.null, colander.default):
+        if text is colander.null:
             return text
         if not text.strip():
-            return colander.default
+            return colander.null
         try:
             infile = StringIO.StringIO(text)
             reader = csv.reader(infile)

File deformdemo/tests/test_demo.py

             browser.get_text('css=#captured'),
             u"{'album': u'def', 'song': u'ghi', 'artist': u'abc'}")
 
+class NonRequiredFieldTests(unittest.TestCase):
+    url = "/nonrequiredfields/"
+    def test_render_default(self):
+        browser.open(self.url)
+        self.failIf(browser.is_element_present('css=.errorMsgLbl'))
+        self.failUnless(browser.is_element_present('css=#req-deformField1'))
+        self.assertEqual(browser.get_value('deformField1'), '')
+        self.assertEqual(browser.get_attribute('deformField1@name'), 'required')
+        self.assertEqual(browser.get_value('deformField2'), '')
+        self.assertEqual(browser.get_attribute('deformField2@name'),
+                         'notrequired')
+        self.assertEqual(browser.get_text('css=#captured'), 'None')
+
+    def test_submit_empty(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        browser.click('submit')
+        browser.wait_for_page_to_load("30000")
+        self.failUnless(browser.is_element_present('css=.errorMsgLbl'))
+        self.assertEqual(browser.get_value('deformField1'), '')
+        self.assertEqual(browser.get_value('deformField2'), '')
+        self.assertEqual(browser.get_text('css=#error-deformField1'),
+                         'Required')
+        self.assertEqual(browser.get_text('css=#captured'), 'None')
+
+    def test_submit_success_required_filled_notrequired_empty(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        browser.type('deformField1', 'abc')
+        browser.click('submit')
+        browser.wait_for_page_to_load("30000")
+        self.failIf(browser.is_element_present('css=.errorMsgLbl'))
+        self.assertEqual(browser.get_value('deformField1'), 'abc')
+        self.assertEqual(browser.get_value('deformField2'), '')
+        self.assertEqual(
+            browser.get_text('css=#captured'),
+            u"{'required': u'abc', 'notrequired': u''}")
+
+    def test_submit_success_required_and_notrequired_filled(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        browser.type('deformField1', 'abc')
+        browser.type('deformField2', 'def')
+        browser.click('submit')
+        browser.wait_for_page_to_load("30000")
+        self.failIf(browser.is_element_present('css=.errorMsgLbl'))
+        self.assertEqual(browser.get_value('deformField1'), 'abc')
+        self.assertEqual(browser.get_value('deformField2'), 'def')
+        self.assertEqual(
+            browser.get_text('css=#captured'),
+            u"{'required': u'abc', 'notrequired': u'def'}")
+
 class HiddenFieldWidgetTests(unittest.TestCase):
     url = "/hidden_field/"
     def test_render_default(self):

File docs/serialization.rst

 
     def deserialize(self, field, pstruct):
         if pstruct is colander.null:
-            return colander.default
+            return colander.null
         value = pstruct.get('value') or ''
         confirm = pstruct.get('confirm') or ''
         field.confirm = confirm

File docs/widget.rst

 
 A widget is a bit of code that is willing to:
 
-- serialize a data structure to HTML for display in a form rendering
+- serialize a :term:`cstruct` into HTML for display in a form
+  rendering
 
-- deserialize data obtained from a form post to a data structure
-  suitable for consumption by a :term:`schema node`
+- deserialize data obtained from a form post (a :term:`pstruct`) into
+  a data structure suitable for deserialization by a :term:`schema
+  node` (a :term:`cstruct`).
 
 - handle validation errors
 
-Deform ships with a good number of default widgets.  You hopefully
-needn't create your own widget unless you're trying to do something
-that the default widget set didn't anticipate.  When a default Deform
-widget doesn't do exactly what you want, you can extend Deform by
-creating a new widget that is more suitable for the task.
+Deform ships with a number of built-in widgets.  You hopefully needn't
+create your own widget unless you're trying to do something that the
+built-in widget set didn't anticipate.  However, when a built-in
+Deform widget doesn't do exactly what you want, you can extend Deform
+by creating a new widget that is more suitable for the task.
 
 The Widget Interface
 --------------------
 
 Writing a Deform widget means creating an object that supplies the
-notional Widget interface, which is described in the docstrings of the
-:class:`deform.widget.Widget` class.  The easiest way to create
-something that implements this interface is to create a class which
-inherits directly from the :class:`deform.widget.Widget` class itself.
+notional Widget interface, which is described in the
+:class:`deform.widget.Widget` class documentation.  The easiest way to
+create something that implements this interface is to create a class
+which inherits directly from the :class:`deform.widget.Widget` class
+itself.
 
 The :class:`deform.widget.Widget` class has a concrete implementation
 of a constructor and the ``handle_error`` method as well as default
         def handle_error(self, field, error):
             ...
 
+We describe the ``serialize``, ``deserialize`` and ``handle_error``
+methods below.
+
 The ``serialize`` Method
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
 when a form which uses this widget is serialized without any data; for
 example an "add form".
 
-All widgets *must* check if the value passed as ``cstruct`` is
-``colander.null`` during ``serialize``.  Widgets are responsible for
-handling this eventuality, often by serializing a logically "empty"
-value.
+All widgets *must* check if the value passed as ``cstruct`` is the
+``colander.null`` sentinel value during ``serialize``.  Widgets are
+responsible for handling this eventuality, often by serializing a
+logically "empty" value.
 
 Regardless of how the widget attempts to compute the default value, it
 must still be able to return a rendering when ``cstruct`` is
 it doesn't really much care how the widget creates that Unicode
 object.
 
-Each of the default Deform widgets (the widget implementations in
+Each of the built-in Deform widgets (the widget implementations in
 :mod:`deform.widget`) happens to use a template in order to make it
 easier for people to override how each widget looks when rendered
 without needing to change Deform-internal Python code.  Instead of
 needing to change the Python code related to the widget itself, users
-of the default widgets can often perform enough customization by
-replacing the template associated with the default widget
+of the built-in widgets can often perform enough customization by
+replacing the template associated with the built-in widget
 implementation.  However, this is purely a convenience; templates are
-a widget set implementation detail, not an integral part of the
-core Deform framework.
+largely a built-in widget set implementation detail, not an integral
+part of the core Deform framework.
 
 Note that "scalar" widgets (widgets which represent a single value as
 opposed to a collection of values) are not responsible for providing
 
     from deform.widget import Widget
     from colander import null
-    from colander import default
     import cgi
 
     class MyInputWidget(Widget):
 
         def deserialize(self, field, pstruct):
             if cstruct is null:
-                return default
+                return null
             return pstruct
 
-Note that both the deserialize method of a widget must also deal with
-the ``colander.null`` value (usually by returning `colander.default``,
-which signifies to the schema that the default value should be used if
-it exists).  ``colander.null`` will be passed to the widget when a
-value is missing from the pstruct.
+Note that the ``deserialize`` method of a widget must, like
+``serialize``, deal with the possibility of being handed a
+``colander.null`` value.  ``colander.null`` will be passed to the
+widget when a value is missing from the pstruct. The widget usually
+handles being passed a ``colander.null`` value in ``deserialize`` by
+returning `colander.null``, which signifies to the underlying schema
+that the default value for the schema node should be used if it
+exists.
 
 The only other real constraint of the deserialize method is that the
 ``serialize`` method must be able to reserialize the return value of
-``deserialize``.  One caveat exists: if ``deserialize`` returns
-``colander.default``, the ``serialize`` method needn't deal with it
-explicitly; it's handled at a higher level.
+``deserialize``.
 
+The ``handle_error`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`deform.widget.Widget` class already has a suitable
+implementation; if you subclass from :class:`deform.widget.Widget`,
+overriding the default implementation is not necessary unless you need
+special error-handling behavior.
+
+Here's an implementation of the
+:meth:`deform.widget.Widget.handle_error` method in the MyInputWidget
+class:
+
+.. code-block:: python
+   :linenos:
+
+    from deform.widget import Widget
+    from colander import null
+    import cgi
+
+    class MyInputWidget(Widget):
+        def serialize(self, field, cstruct, readonly=False):
+            if cstruct is null:
+                cstruct = u''
+            return '<input type="text" value="%s">' % cgi.escape(cstruct)
+
+        def deserialize(self, field, pstruct):
+            if cstruct is null:
+                return null
+            return pstruct
+
+        def handle_error(self, field, error):
+            if field.error is None:
+                field.error = error
+            for e in error.children:
+                for num, subfield in enumerate(field.children):
+                    if e.pos == num:
+                        subfield.widget.handle_error(subfield, e)
+
+The ``handle_error`` method of a widget must:
+
+- Set the ``error`` attribute of the ``field`` object it is passed if
+  the ``error`` attribute has not already been set.
+
+- Call the ``handle_error`` methods of any subfields which
+  also have errors.
+
+The ability to override ``handle_error` exists purely for advanced
+tasks, such as presenting all child errors of a field on a parent
+field.  For example:
+
+.. code-block:: python
+   :linenos:
+
+    def handle_error(self, field, error):
+        msgs = []
+        if error.msg:
+            field.error = error
+        else:
+            for e in error.children:
+                msgs.append('line %s: %s' % (e.pos+1, e))
+            field.error = Invalid(field.schema, '\n'.join(msgs))
+
+This implementation does not attach any errors to field children;
+instead it attaches all of the child errors to the field itself for
+review.