[Checkins] SVN: z3c.form/trunk/src/z3c/form/ a bunch of adjustments to make multiwidget and objectwidget happier together

Adam Groszer agroszer at gmail.com
Mon Dec 1 11:50:04 EST 2008


Log message for revision 93492:
  a bunch of adjustments to make multiwidget and objectwidget happier together
  

Changed:
  U   z3c.form/trunk/src/z3c/form/browser/multi.py
  U   z3c.form/trunk/src/z3c/form/browser/multi.txt
  U   z3c.form/trunk/src/z3c/form/browser/object.txt
  A   z3c.form/trunk/src/z3c/form/browser/objectmulti.txt
  U   z3c.form/trunk/src/z3c/form/browser/tests.py
  U   z3c.form/trunk/src/z3c/form/converter.py
  U   z3c.form/trunk/src/z3c/form/object.py

-=-
Modified: z3c.form/trunk/src/z3c/form/browser/multi.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/multi.py	2008-12-01 16:36:39 UTC (rev 93491)
+++ z3c.form/trunk/src/z3c/form/browser/multi.py	2008-12-01 16:50:04 UTC (rev 93492)
@@ -60,15 +60,8 @@
 
     @button.buttonAndHandler(_('Remove'), name='remove')
     def handleRemove(self, action):
-        ids = []
-        append = ids.append
-        counter = int(self.request.get(self.counterName, 0))
-        for widget in self.widgets:
-            name = '%s.remove' % (widget.name)
-            if name in self.request:
-                append(widget.id)
         self.widgets = [widget for widget in self.widgets
-                        if widget.id not in ids]
+                        if ('%s.remove' % (widget.name)) not in self.request]
 
 @zope.interface.implementer(interfaces.IFieldWidget)
 def multiFieldWidgetFactory(field, request):

Modified: z3c.form/trunk/src/z3c/form/browser/multi.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/multi.txt	2008-12-01 16:36:39 UTC (rev 93491)
+++ z3c.form/trunk/src/z3c/form/browser/multi.txt	2008-12-01 16:50:04 UTC (rev 93492)
@@ -194,6 +194,10 @@
   ...                                    'widget.name.1':u'43',
   ...                                    'widget.buttons.add':'Add'})
   >>> widget.update()
+
+  >>> widget.extract()
+  [u'42', u'43']
+
   >>> print widget.render()
   <div class="multi-widget">
       <div id="widget-id-0-row" class="row">
@@ -274,6 +278,10 @@
   ...                                    'widget.name.1':u'43',
   ...                                    'widget.name.2':u'44'})
   >>> widget.update()
+
+  >>> widget.extract()
+  [u'42', u'43', u'44']
+
   >>> print widget.render()
   <div class="multi-widget">
       <div id="widget-id-0-row" class="row">
@@ -358,6 +366,12 @@
   ...                                    'widget.name.1.remove':u'1',
   ...                                    'widget.buttons.remove':'Remove'})
   >>> widget.update()
+
+This is good so, because the Remove is an widget-internal submit action
+
+  >>> widget.extract()
+  [u'42', u'43', u'44']
+
   >>> print widget.render()
   <div class="multi-widget">
       <div id="widget-id-0-row" class="row">
@@ -423,6 +437,10 @@
   ...                                    'widget.name.0':u'42',
   ...                                    'widget.name.1':u'bad'})
   >>> widget.update()
+
+  >>> widget.extract()
+  [u'42', u'bad']
+
   >>> print widget.render()
   <div class="multi-widget">
       <div id="widget-id-0-row" class="row">

Modified: z3c.form/trunk/src/z3c/form/browser/object.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/object.txt	2008-12-01 16:36:39 UTC (rev 93491)
+++ z3c.form/trunk/src/z3c/form/browser/object.txt	2008-12-01 16:50:04 UTC (rev 93492)
@@ -38,7 +38,10 @@
   >>> from z3c.form.error import MultipleErrorViewSnippet
   >>> zope.component.provideAdapter(MultipleErrorViewSnippet)
 
+  >>> import z3c.form.object
+  >>> zope.component.provideAdapter(z3c.form.object.ObjectConverter)
 
+
 As for all widgets, the objectwidget must provide the new ``IWidget``
 interface:
 
@@ -150,7 +153,7 @@
 
 
   >>> widget.ignoreContext = False
-  >>> widget.value = v
+  >>> widget.value = dict(foofield=42, barfield=666)
 
   >>> widget.update()
 
@@ -250,7 +253,25 @@
   'ThisMustStayTheSame'
 
 
-HMMMM.... do we to test error handling here?
+  >>> converter = interfaces.IDataConverter(widget)
+
+  >>> value = converter.toFieldValue(wv)
+  >>> value
+  <z3c.form.testing.MySubObject object at ...>
+  >>> value.foofield
+  2
+  >>> value.barfield
+  999
+
+This is a different object:
+
+  >>> value.__marker__
+  Traceback (most recent call last):
+  ...
+  AttributeError: 'MySubObject' object has no attribute '__marker__'
+
+
+HMMMM.... do we have to test error handling here?
 I'm tempted to leave it out as no widgets seem to do this.
 
 #Error handling is next. Let's use the value "bad" (an invalid integer literal)
@@ -326,8 +347,6 @@
 We have to provide an adapter first:
 
   >>> zope.component.provideAdapter(object.ObjectFieldWidget)
-  >>> import z3c.form.object
-  >>> zope.component.provideAdapter(z3c.form.object.ObjectConverter)
 
 Forms and our objectwidget fire events on add and edit, setup a subscriber
 for those:
@@ -680,7 +699,7 @@
 
   >>> editform.update()
 
-Until we have updated the form:
+Let's check what are ther esults of the update:
 
   >>> root['first'].subobject.foofield
   666
@@ -1066,13 +1085,8 @@
 
   >>> request = TestRequest()
 
-#>>> from pub.dbgpclient import brk; brk('192.168.32.1')
-
   >>> myaddform =  MyAddForm(root, request)
 
-#>>> from pub.dbgpclient import brk; brk('192.168.32.1')
-
-
   >>> myaddform.update()
 
 As usual, the form contains a widget manager with the expected widget

Added: z3c.form/trunk/src/z3c/form/browser/objectmulti.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/objectmulti.txt	                        (rev 0)
+++ z3c.form/trunk/src/z3c/form/browser/objectmulti.txt	2008-12-01 16:50:04 UTC (rev 93492)
@@ -0,0 +1,936 @@
+===================
+Multi+Object Widget
+===================
+
+The multi widget allows you to add and edit one or more values.
+
+In order to not overwhelm you with our set of well-chosen defaults,
+all the default component registrations have been made prior to doing those
+examples:
+
+  >>> from z3c.form import testing
+  >>> testing.setupFormDefaults()
+
+As for all widgets, the multi widget must provide the new ``IWidget``
+interface:
+
+  >>> from zope.interface.verify import verifyClass
+  >>> from z3c.form import interfaces
+  >>> from z3c.form.browser import multi
+
+  >>> verifyClass(interfaces.IWidget, multi.MultiWidget)
+  True
+
+The widget can be instantiated only using the request:
+
+  >>> from z3c.form.testing import TestRequest
+  >>> request = TestRequest()
+  >>> widget = multi.MultiWidget(request)
+
+Before rendering the widget, one has to set the name and id of the widget:
+
+  >>> widget.id = 'widget-id'
+  >>> widget.name = 'widget.name'
+
+We also need to register the template for at least the widget and request:
+
+  >>> import zope.component
+  >>> from zope.pagetemplate.interfaces import IPageTemplate
+  >>> from z3c.form.testing import getPath
+  >>> from z3c.form.widget import WidgetTemplateFactory
+
+  >>> zope.component.provideAdapter(
+  ...     WidgetTemplateFactory(getPath('multi_input.pt'), 'text/html'),
+  ...     (None, None, None, None, interfaces.IMultiWidget),
+  ...     IPageTemplate, name=interfaces.INPUT_MODE)
+
+For the next test, we need to setup our button handler adapters.
+
+  >>> from z3c.form import button
+  >>> zope.component.provideAdapter(button.ButtonActions)
+  >>> zope.component.provideAdapter(button.ButtonActionHandler)
+  >>> zope.component.provideAdapter(button.ButtonAction,
+  ...     provides=interfaces.IButtonAction)
+
+Our submit buttons will need a template as well:
+
+  >>> zope.component.provideAdapter(
+  ...     WidgetTemplateFactory(getPath('submit_input.pt'), 'text/html'),
+  ...     (None, None, None, None, interfaces.ISubmitWidget),
+  ...     IPageTemplate, name=interfaces.INPUT_MODE)
+
+We can now render the widget:
+
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget">
+    <div class="buttons">
+      <input type="submit" id="widget-buttons-add"
+         name="widget.buttons.add"
+         class="submit-widget button-field" value="Add" />
+      <input type="submit" id="widget-buttons-remove"
+         name="widget.buttons.remove"
+         class="submit-widget button-field" value="Remove" />
+     </div>
+  </div>
+  <input type="hidden" name="widget.name.count" value="0" />
+
+As you can see the widget is empty and doesn't provide values. This is because
+the widget does not know what sub-widgets to display. So let's register a
+`IFieldWidget` adapter and a template for our `IInt` field:
+
+  >>> import z3c.form.interfaces
+  >>> from z3c.form.browser.text import TextFieldWidget
+  >>> zope.component.provideAdapter(TextFieldWidget,
+  ...     (zope.schema.interfaces.IInt, z3c.form.interfaces.IFormLayer))
+
+  >>> zope.component.provideAdapter(
+  ...     WidgetTemplateFactory(getPath('text_input.pt'), 'text/html'),
+  ...     (None, None, None, None, interfaces.ITextWidget),
+  ...     IPageTemplate, name=interfaces.INPUT_MODE)
+
+Let's now update the widget and check it again.
+
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget">
+    <div class="buttons">
+      <input type="submit" id="widget-buttons-add"
+         name="widget.buttons.add"
+         class="submit-widget button-field" value="Add" />
+      <input type="submit" id="widget-buttons-remove"
+         name="widget.buttons.remove"
+         class="submit-widget button-field" value="Remove" />
+     </div>
+  </div>
+  <input type="hidden" name="widget.name.count" value="0" />
+
+It's still the same. Since the widget doesn't provide a field nothing useful
+gets rendered. Now let's define a field for this widget and check it again:
+
+  >>> from z3c.form.widget import FieldWidget
+
+  >>> from z3c.form.testing import IMySubObject
+  >>> from z3c.form.testing import IMySecond
+  >>> from z3c.form.testing import MySubObject
+  >>> from z3c.form.testing import MySecond
+
+  >>> from z3c.form.object import registerFactoryAdapter
+  >>> registerFactoryAdapter(IMySubObject, MySubObject)
+
+  >>> field = zope.schema.List(
+  ...     __name__='foo',
+  ...     value_type=zope.schema.Object(title=u'my object widget',
+  ...                                   schema=IMySubObject),
+  ...     )
+
+  >>> widget = FieldWidget(field, widget)
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget required">
+    <div class="buttons">
+      <input type="submit" id="widget-buttons-add"
+         name="widget.buttons.add"
+         class="submit-widget button-field" value="Add" />
+      <input type="submit" id="widget-buttons-remove"
+         name="widget.buttons.remove"
+         class="submit-widget button-field" value="Remove" />
+     </div>
+  </div>
+  <input type="hidden" name="foo.count" value="0" />
+
+As you can see, there is still no input value. Let's provide some values for
+this widget. Before we can do that, we will need to register a data converter
+for our multi widget and the data converter dispatcher adapter:
+
+  >>> from z3c.form.converter import IntegerDataConverter
+  >>> from z3c.form.converter import FieldWidgetDataConverter
+  >>> from z3c.form.converter import MultiConverter
+  >>> from z3c.form.validator import SimpleFieldValidator
+  >>> zope.component.provideAdapter(IntegerDataConverter)
+  >>> zope.component.provideAdapter(FieldWidgetDataConverter)
+  >>> zope.component.provideAdapter(SimpleFieldValidator)
+  >>> zope.component.provideAdapter(MultiConverter)
+
+Bunch of adapters to get objectwidget work:
+
+  >>> from z3c.form import datamanager
+  >>> zope.component.provideAdapter(datamanager.DictionaryField)
+
+  >>> import z3c.form.browser.object
+  >>> zope.component.provideAdapter(z3c.form.browser.object.ObjectFieldWidget)
+  >>> import z3c.form.object
+  >>> zope.component.provideAdapter(z3c.form.object.ObjectConverter)
+  >>> import z3c.form.error
+  >>> zope.component.provideAdapter(z3c.form.error.ValueErrorViewSnippet)
+  >>> from z3c.form.object import SubformAdapter
+  >>> zope.component.provideAdapter(SubformAdapter)
+
+  >>> from zope.pagetemplate.interfaces import IPageTemplate
+  >>> from z3c.form.testing import getPath
+  >>> from z3c.form.widget import WidgetTemplateFactory
+
+  >>> zope.component.provideAdapter(
+  ...     WidgetTemplateFactory(getPath('object_input.pt'), 'text/html'),
+  ...     (None, None, None, None, interfaces.IObjectWidget),
+  ...     IPageTemplate, name=interfaces.INPUT_MODE)
+
+  >>> zope.component.provideAdapter(
+  ...     WidgetTemplateFactory(getPath('object_display.pt'), 'text/html'),
+  ...     (None, None, None, None, interfaces.IObjectWidget),
+  ...     IPageTemplate, name=interfaces.DISPLAY_MODE)
+
+  >>> widget.update()
+
+  >>> widget.value = [dict(foofield=42, barfield=666),
+  ...     dict(foofield=789, barfield=321)]
+
+  >>> print widget.render()
+  <div class="multi-widget required">
+      <div id="foo-0-row" class="row">
+          <div class="label">
+            <label for="foo-0">
+              <span>my object widget</span>
+              <span class="required">*</span>
+            </label>
+          </div>
+          <div class="widget">
+            <div class="multi-widget-checkbox">
+              <input id="foo-0-remove"
+                     name="foo.0.remove"
+                     class="multi-widget-checkbox checkbox-widget"
+                     type="checkbox" value="1" />
+            </div>
+            <div class="multi-widget-input">
+              <div class="object-widget required">
+                <div class="label">
+                  <label for="foo-0-widgets-foofield">
+                    <span>My foo field</span>
+                    <span class="required">*</span>
+                  </label>
+                </div>
+                <div class="widget">
+                    <input id="foo-0-widgets-foofield"
+                           name="foo.0.widgets.foofield"
+                           class="text-widget required int-field" value="42"
+                           type="text" />
+                </div>
+                <div class="label">
+                  <label for="foo-0-widgets-barfield">
+                    <span>My dear bar</span>
+                  </label>
+                </div>
+                <div class="widget">
+                    <input id="foo-0-widgets-barfield"
+                           name="foo.0.widgets.barfield"
+                           class="text-widget int-field" value="666"
+                           type="text" />
+                </div>
+                <input name="foo.0-empty-marker" type="hidden"
+                       value="1" />
+              </div>
+            </div>
+          </div>
+      </div>
+      <div id="foo-1-row" class="row">
+          <div class="label">
+            <label for="foo-1">
+              <span>my object widget</span>
+              <span class="required">*</span>
+            </label>
+          </div>
+          <div class="widget">
+            <div class="multi-widget-checkbox">
+              <input id="foo-1-remove"
+                     name="foo.1.remove"
+                     class="multi-widget-checkbox checkbox-widget"
+                     type="checkbox" value="1" />
+            </div>
+            <div class="multi-widget-input">
+              <div class="object-widget required">
+                <div class="label">
+                  <label for="foo-1-widgets-foofield">
+                    <span>My foo field</span>
+                    <span class="required">*</span>
+                  </label>
+                </div>
+                <div class="widget">
+                  <input id="foo-1-widgets-foofield"
+                         name="foo.1.widgets.foofield"
+                         class="text-widget required int-field"
+                         value="789" type="text" />
+                </div>
+                <div class="label">
+                  <label for="foo-1-widgets-barfield">
+                    <span>My dear bar</span>
+                  </label>
+                </div>
+                <div class="widget">
+                    <input id="foo-1-widgets-barfield"
+                           name="foo.1.widgets.barfield"
+                           class="text-widget int-field" value="321"
+                           type="text" />
+                </div>
+                <input name="foo.1-empty-marker" type="hidden"
+                       value="1" />
+              </div>
+            </div>
+          </div>
+      </div>
+    <div class="buttons">
+      <input id="widget-buttons-add" name="widget.buttons.add"
+             class="submit-widget button-field" value="Add"
+             type="submit" />
+      <input id="widget-buttons-remove"
+             name="widget.buttons.remove"
+             class="submit-widget button-field" value="Remove"
+             type="submit" />
+    </div>
+  </div>
+  <input type="hidden" name="foo.count" value="2" />
+
+Let's see what we get on value extraction:
+
+  >>> widget.extract()
+  <NOVALUE>
+
+If we now click on the ``Add`` button, we will get a new input field for enter
+a new value:
+
+  >>> widget.request = TestRequest(form={'foo.count':u'2',
+  ...                                    'foo.0.widgets.foofield':u'42',
+  ...                                    'foo.0.widgets.barfield':u'666',
+  ...                                    'foo.0-empty-marker':u'1',
+  ...                                    'foo.1.widgets.foofield':u'789',
+  ...                                    'foo.1.widgets.barfield':u'321',
+  ...                                    'foo.1-empty-marker':u'1',
+  ...                                    'widget.buttons.add':'Add'})
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget required">
+    <div class="row" id="foo-0-row">
+      <div class="label">
+        <label for="foo-0">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget"
+                 id="foo-0-remove"
+                 name="foo.0.remove"
+                 type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-0-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field"
+                     id="foo-0-widgets-foofield"
+                     name="foo.0.widgets.foofield"
+                     type="text" value="42">
+            </div>
+            <div class="label">
+              <label for="foo-0-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field"
+                     id="foo-0-widgets-barfield"
+                     name="foo.0.widgets.barfield"
+                     type="text" value="666">
+            </div>
+            <input name="foo.0-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="foo-1-row">
+      <div class="label">
+        <label for="foo-1">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget"
+                 id="foo-1-remove"
+                 name="foo.1.remove"
+                 type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-1-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field"
+                     id="foo-1-widgets-foofield"
+                     name="foo.1.widgets.foofield"
+                     type="text" value="789">
+            </div>
+            <div class="label">
+              <label for="foo-1-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field"
+                     id="foo-1-widgets-barfield"
+                     name="foo.1.widgets.barfield"
+                     type="text" value="321">
+            </div>
+            <input name="foo.1-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="foo-2-row">
+      <div class="label">
+        <label for="foo-2">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget"
+                 id="foo-2-remove"
+                 name="foo.2.remove"
+                 type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-2-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field"
+                     id="foo-2-widgets-foofield"
+                     name="foo.2.widgets.foofield"
+                     type="text" value="1,111">
+            </div>
+            <div class="label">
+              <label for="foo-2-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field"
+                     id="foo-2-widgets-barfield"
+                     name="foo.2.widgets.barfield"
+                     type="text" value="2,222">
+            </div>
+            <input name="foo.2-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="buttons">
+      <input id="widget-buttons-add" name="widget.buttons.add"
+             class="submit-widget button-field" value="Add"
+             type="submit" />
+      <input id="widget-buttons-remove"
+             name="widget.buttons.remove"
+             class="submit-widget button-field" value="Remove"
+             type="submit" />
+    </div>
+  </div>
+  <input name="foo.count" type="hidden" value="3">
+
+Let's see what we get on value extraction:
+
+  >>> value = widget.extract()
+  >>> value
+  [{'foofield': 42, 'barfield': 666}, {'foofield': 789, 'barfield': 321}]
+  >>> converter = interfaces.IDataConverter(widget)
+
+  >>> value = converter.toFieldValue(value)
+  >>> value
+  [<z3c.form.testing.MySubObject object at ...>,
+  <z3c.form.testing.MySubObject object at ...>]
+
+  >>> value[0].foofield
+  42
+  >>> value[0].barfield
+  666
+
+
+Now let's store the new value:
+
+
+  >>> widget.request = TestRequest(form={'foo.count':u'3',
+  ...                                    'foo.0.widgets.foofield':u'42',
+  ...                                    'foo.0.widgets.barfield':u'666',
+  ...                                    'foo.0-empty-marker':u'1',
+  ...                                    'foo.1.widgets.foofield':u'789',
+  ...                                    'foo.1.widgets.barfield':u'321',
+  ...                                    'foo.1-empty-marker':u'1',
+  ...                                    'foo.2.widgets.foofield':u'46',
+  ...                                    'foo.2.widgets.barfield':u'98',
+  ...                                    'foo.2-empty-marker':u'1',
+  ...                                    })
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget required">
+    <div class="row" id="foo-0-row">
+      <div class="label">
+        <label for="foo-0">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-0-remove" name="foo.0.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-0-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-0-widgets-foofield" name="foo.0.widgets.foofield" type="text" value="42">
+            </div>
+            <div class="label">
+              <label for="foo-0-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-0-widgets-barfield" name="foo.0.widgets.barfield" type="text" value="666">
+            </div>
+            <input name="foo.0-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="foo-1-row">
+      <div class="label">
+        <label for="foo-1">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-1-remove" name="foo.1.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-1-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-1-widgets-foofield" name="foo.1.widgets.foofield" type="text" value="789">
+            </div>
+            <div class="label">
+              <label for="foo-1-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-1-widgets-barfield" name="foo.1.widgets.barfield" type="text" value="321">
+            </div>
+            <input name="foo.1-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="foo-2-row">
+      <div class="label">
+        <label for="foo-2">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-2-remove" name="foo.2.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-2-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-2-widgets-foofield" name="foo.2.widgets.foofield" type="text" value="46">
+            </div>
+            <div class="label">
+              <label for="foo-2-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-2-widgets-barfield" name="foo.2.widgets.barfield" type="text" value="98">
+            </div>
+            <input name="foo.2-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="buttons">
+      <input class="submit-widget button-field" id="widget-buttons-add" name="widget.buttons.add" type="submit" value="Add">
+      <input class="submit-widget button-field" id="widget-buttons-remove" name="widget.buttons.remove" type="submit" value="Remove">
+    </div>
+  </div>
+  <input name="foo.count" type="hidden" value="3">
+
+Let's see what we get on value extraction:
+
+  >>> value = widget.extract()
+  >>> value
+  [{'foofield': 42, 'barfield': 666},
+  {'foofield': 789, 'barfield': 321},
+  {'foofield': 46, 'barfield': 98}]
+  >>> converter = interfaces.IDataConverter(widget)
+
+  >>> value = converter.toFieldValue(value)
+  >>> value
+  [<z3c.form.testing.MySubObject object at ...>,
+  <z3c.form.testing.MySubObject object at ...>]
+
+  >>> value[0].foofield
+  42
+  >>> value[0].barfield
+  666
+
+
+As you can see in the above sample, the new stored value gets rendered as a
+real value and the new adding value input field is gone. Now let's try to
+remove an existing value:
+
+  >>> widget.request = TestRequest(form={'foo.count':u'3',
+  ...                                    'foo.0.widgets.foofield':u'42',
+  ...                                    'foo.0.widgets.barfield':u'666',
+  ...                                    'foo.0-empty-marker':u'1',
+  ...                                    'foo.1.widgets.foofield':u'789',
+  ...                                    'foo.1.widgets.barfield':u'321',
+  ...                                    'foo.1-empty-marker':u'1',
+  ...                                    'foo.2.widgets.foofield':u'46',
+  ...                                    'foo.2.widgets.barfield':u'98',
+  ...                                    'foo.2-empty-marker':u'1',
+  ...                                    'foo.1.remove':u'1',
+  ...                                    'widget.buttons.remove':'Remove'})
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget required">
+    <div class="row" id="foo-0-row">
+      <div class="label">
+        <label for="foo-0">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-0-remove" name="foo.0.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-0-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-0-widgets-foofield" name="foo.0.widgets.foofield" type="text" value="42">
+            </div>
+            <div class="label">
+              <label for="foo-0-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-0-widgets-barfield" name="foo.0.widgets.barfield" type="text" value="666">
+            </div>
+            <input name="foo.0-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="foo-2-row">
+      <div class="label">
+        <label for="foo-2">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-2-remove" name="foo.2.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-2-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-2-widgets-foofield" name="foo.2.widgets.foofield" type="text" value="46">
+            </div>
+            <div class="label">
+              <label for="foo-2-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-2-widgets-barfield" name="foo.2.widgets.barfield" type="text" value="98">
+            </div>
+            <input name="foo.2-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="buttons">
+      <input class="submit-widget button-field" id="widget-buttons-add" name="widget.buttons.add" type="submit" value="Add">
+      <input class="submit-widget button-field" id="widget-buttons-remove" name="widget.buttons.remove" type="submit" value="Remove">
+    </div>
+  </div>
+  <input name="foo.count" type="hidden" value="2">
+
+Let's see what we get on value extraction:
+(this is good so, because Remove is a widget-internal submit)
+
+  >>> value = widget.extract()
+  >>> value
+  [{'foofield': 42, 'barfield': 666},
+  {'foofield': 789, 'barfield': 321},
+  {'foofield': 46, 'barfield': 98}]
+  >>> converter = interfaces.IDataConverter(widget)
+
+  >>> value = converter.toFieldValue(value)
+  >>> value
+  [<z3c.form.testing.MySubObject object at ...>,
+  <z3c.form.testing.MySubObject object at ...>]
+
+  >>> value[0].foofield
+  42
+  >>> value[0].barfield
+  666
+
+
+Error handling is next. Let's use the value "bad" (an invalid integer literal)
+as input for our internal (sub) widget.
+
+  >>> from z3c.form.error import ErrorViewSnippet
+  >>> from z3c.form.error import StandardErrorViewTemplate
+  >>> zope.component.provideAdapter(ErrorViewSnippet)
+  >>> zope.component.provideAdapter(StandardErrorViewTemplate)
+
+  >>> widget.request = TestRequest(form={'foo.count':u'2',
+  ...                                    'foo.0.widgets.foofield':u'42',
+  ...                                    'foo.0.widgets.barfield':u'666',
+  ...                                    'foo.0-empty-marker':u'1',
+  ...                                    'foo.1.widgets.foofield':u'bad',
+  ...                                    'foo.1.widgets.barfield':u'98',
+  ...                                    'foo.1-empty-marker':u'1',
+  ...                                    })
+
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget required">
+    <div class="row" id="foo-0-row">
+      <div class="label">
+        <label for="foo-0">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-0-remove" name="foo.0.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-0-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-0-widgets-foofield" name="foo.0.widgets.foofield" type="text" value="42">
+            </div>
+            <div class="label">
+              <label for="foo-0-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-0-widgets-barfield" name="foo.0.widgets.barfield" type="text" value="666">
+            </div>
+            <input name="foo.0-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="foo-1-row">
+      <div class="label">
+        <label for="foo-1">
+          <span>my object widget</span>
+          <span class="required">*</span>
+        </label>
+      </div>
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-1-remove" name="foo.1.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-1-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="error">
+              <div class="error">The entered value is not a valid integer literal.</div>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-1-widgets-foofield" name="foo.1.widgets.foofield" type="text" value="bad">
+            </div>
+            <div class="label">
+              <label for="foo-1-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-1-widgets-barfield" name="foo.1.widgets.barfield" type="text" value="98">
+            </div>
+            <input name="foo.1-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+      <div class="error">
+        <div class="error">Object is of wrong type.</div>
+      </div>
+    </div>
+    <div class="buttons">
+      <input class="submit-widget button-field" id="widget-buttons-add" name="widget.buttons.add" type="submit" value="Add">
+      <input class="submit-widget button-field" id="widget-buttons-remove" name="widget.buttons.remove" type="submit" value="Remove">
+    </div>
+  </div>
+  <input name="foo.count" type="hidden" value="2">
+
+Let's see what we get on value extraction:
+
+  >>> value = widget.extract()
+  >>> value
+  [{'foofield': 42, 'barfield': 666},
+  {'foofield': u'bad', 'barfield': u'98'}]
+
+
+Label
+-----
+
+There is an option which allows to disable the label for the (sub) widgets.
+You can set the `showLabel` option to `False` which will skip rendering the
+labels. Alternatively you can also register your own template for your layer
+if you like to skip the label rendering for all widgets.
+
+
+  >>> field = zope.schema.List(
+  ...     __name__='foo',
+  ...     value_type=zope.schema.Object(title=u'ignored_title',
+  ...                                   schema=IMySubObject),
+  ...     )
+  >>> request = TestRequest()
+  >>> widget = multi.MultiWidget(request)
+  >>> widget = FieldWidget(field, widget)
+  >>> widget.value = [dict(foofield=42, barfield=666),
+  ...     dict(foofield=789, barfield=321)]
+  >>> widget.showLabel = False
+  >>> widget.update()
+  >>> print widget.render()
+  <div class="multi-widget required">
+    <div class="row" id="foo-0-row">
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-0-remove" name="foo.0.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-0-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-0-widgets-foofield" name="foo.0.widgets.foofield" type="text" value="42">
+            </div>
+            <div class="label">
+              <label for="foo-0-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-0-widgets-barfield" name="foo.0.widgets.barfield" type="text" value="666">
+            </div>
+            <input name="foo.0-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row" id="foo-1-row">
+      <div class="widget">
+        <div class="multi-widget-checkbox">
+          <input class="multi-widget-checkbox checkbox-widget" id="foo-1-remove" name="foo.1.remove" type="checkbox" value="1">
+        </div>
+        <div class="multi-widget-input">
+          <div class="object-widget required">
+            <div class="label">
+              <label for="foo-1-widgets-foofield">
+                <span>My foo field</span>
+                <span class="required">*</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget required int-field" id="foo-1-widgets-foofield" name="foo.1.widgets.foofield" type="text" value="789">
+            </div>
+            <div class="label">
+              <label for="foo-1-widgets-barfield">
+                <span>My dear bar</span>
+              </label>
+            </div>
+            <div class="widget">
+              <input class="text-widget int-field" id="foo-1-widgets-barfield" name="foo.1.widgets.barfield" type="text" value="321">
+            </div>
+            <input name="foo.1-empty-marker" type="hidden" value="1">
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="buttons">
+      <input class="submit-widget button-field" id="widget-buttons-add" name="widget.buttons.add" type="submit" value="Add">
+      <input class="submit-widget button-field" id="widget-buttons-remove" name="widget.buttons.remove" type="submit" value="Remove">
+    </div>
+  </div>
+  <input name="foo.count" type="hidden" value="2">
\ No newline at end of file


Property changes on: z3c.form/trunk/src/z3c/form/browser/objectmulti.txt
___________________________________________________________________
Added: svn:keywords
   + Date Author Id Revision
Added: svn:eol-style
   + native

Modified: z3c.form/trunk/src/z3c/form/browser/tests.py
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/tests.py	2008-12-01 16:36:39 UTC (rev 93491)
+++ z3c.form/trunk/src/z3c/form/browser/tests.py	2008-12-01 16:50:04 UTC (rev 93492)
@@ -96,6 +96,11 @@
                      optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
                      checker=checker,
                      ),
+        DocFileSuite('objectmulti.txt',
+                     setUp=setUp, tearDown=testing.tearDown,
+                     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+                     checker=checker,
+                     ),
         DocFileSuite('multi.txt',
                      setUp=setUp, tearDown=testing.tearDown,
                      optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,

Modified: z3c.form/trunk/src/z3c/form/converter.py
===================================================================
--- z3c.form/trunk/src/z3c/form/converter.py	2008-12-01 16:36:39 UTC (rev 93491)
+++ z3c.form/trunk/src/z3c/form/converter.py	2008-12-01 16:50:04 UTC (rev 93492)
@@ -341,13 +341,17 @@
         """Just dispatch it."""
         if value is self.field.missing_value:
             return []
-        # We relay on the default registered widget, this is probably a
+        # We rely on the default registered widget, this is probably a
         # restriction for custom widgets. If so use your own MultiWidget and
         # register your own converter which will get the right widget for the
         # used value_type.
         field = self.field.value_type
         widget = zope.component.getMultiAdapter((field, self.widget.request),
             interfaces.IFieldWidget)
+        if interfaces.IFormAware.providedBy(self.widget):
+            #form property required by objecwidget
+            widget.form = self.widget.form
+            zope.interface.alsoProvides(widget, interfaces.IFormAware)
         converter = zope.component.getMultiAdapter((field, widget),
             interfaces.IDataConverter)
 
@@ -355,13 +359,24 @@
         return [converter.toWidgetValue(v) for v in value]
 
     def toFieldValue(self, value):
-        """See interfaces.IDataConverter"""
-        collectionType = self.field._type
+        """Just dispatch it."""
         if not len(value):
             return self.field.missing_value
-        valueType = self.field.value_type._type
-        values = [valueType(v) for v in value]
+
+        field = self.field.value_type
+        widget = zope.component.getMultiAdapter((field, self.widget.request),
+            interfaces.IFieldWidget)
+        if interfaces.IFormAware.providedBy(self.widget):
+            #form property required by objecwidget
+            widget.form = self.widget.form
+            zope.interface.alsoProvides(widget, interfaces.IFormAware)
+        converter = zope.component.getMultiAdapter((field, widget),
+            interfaces.IDataConverter)
+
+        values = [converter.toFieldValue(v) for v in value]
+
         # convert the field values to a tuple or list
+        collectionType = self.field._type
         return collectionType(values)
 
 

Modified: z3c.form/trunk/src/z3c/form/object.py
===================================================================
--- z3c.form/trunk/src/z3c/form/object.py	2008-12-01 16:36:39 UTC (rev 93491)
+++ z3c.form/trunk/src/z3c/form/object.py	2008-12-01 16:50:04 UTC (rev 93492)
@@ -143,21 +143,25 @@
         return obj
 
     def toFieldValue(self, value):
-        """See interfaces.IDataConverter"""
+        """field value is an Object type, that provides field.schema"""
         if value is interfaces.NOVALUE:
             return self.field.missing_value
 
-        if self.widget.subform.ignoreContext:
+        if self.widget.subform is None:
+            #creepy situation when the widget is hanging in nowhere
             obj = self.createObject(value)
         else:
-            dm = zope.component.getMultiAdapter(
-                (self.widget.context, self.field), interfaces.IDataManager)
-            try:
-                obj = dm.get()
-            except KeyError:
+            if self.widget.subform.ignoreContext:
                 obj = self.createObject(value)
-            except AttributeError:
-                obj = self.createObject(value)
+            else:
+                dm = zope.component.getMultiAdapter(
+                    (self.widget.context, self.field), interfaces.IDataManager)
+                try:
+                    obj = dm.get()
+                except KeyError:
+                    obj = self.createObject(value)
+                except AttributeError:
+                    obj = self.createObject(value)
 
         obj = self.field.schema(obj)
 
@@ -227,11 +231,14 @@
     def value():
         """This invokes updateWidgets on any value change e.g. update/extract."""
         def get(self):
-            return self.extract(setErrors=True)
-            #value = {}
-            #for name in zope.schema.getFieldNames(self.field.schema):
-            #    value[name] = self.subform.widgets[name].value
-            #return value
+            #value (get) cannot raise an exception, then we return insane values
+            try:
+                return self.extract(setErrors=True)
+            except MultipleErrors:
+                value = {}
+                for name in zope.schema.getFieldNames(self.field.schema):
+                    value[name] = self.subform.widgets[name].value
+                return value
         def set(self, value):
             self._value = value
             # ensure that we apply our new values to the widgets
@@ -341,8 +348,11 @@
     """Most basic-default object factory adapter"""
 
     zope.interface.implements(interfaces.IObjectFactory)
-    zope.component.adapts(zope.interface.Interface, interfaces.IFormLayer,
-        interfaces.IForm, interfaces.IWidget)
+    zope.component.adapts(
+        zope.interface.Interface, #context
+        interfaces.IFormLayer,    #request
+        zope.interface.Interface, #form -- but can become None easily (in tests)
+        interfaces.IWidget)       #widget
 
     factory = None
 



More information about the Checkins mailing list