[Checkins] SVN: z3c.form/branches/adamg-objectwidget/src/z3c/form/ reworked to pass dict to the widget

Adam Groszer agroszer at gmail.com
Mon Oct 13 09:26:41 EDT 2008


Log message for revision 92116:
  reworked to pass dict to the widget
  still problems with sub-sub widgets
  

Changed:
  U   z3c.form/branches/adamg-objectwidget/src/z3c/form/browser/object.txt
  U   z3c.form/branches/adamg-objectwidget/src/z3c/form/field.py
  U   z3c.form/branches/adamg-objectwidget/src/z3c/form/object.py

-=-
Modified: z3c.form/branches/adamg-objectwidget/src/z3c/form/browser/object.txt
===================================================================
--- z3c.form/branches/adamg-objectwidget/src/z3c/form/browser/object.txt	2008-10-13 13:18:24 UTC (rev 92115)
+++ z3c.form/branches/adamg-objectwidget/src/z3c/form/browser/object.txt	2008-10-13 13:26:41 UTC (rev 92116)
@@ -16,15 +16,17 @@
   >>> from z3c.form import testing
   >>> testing.setupFormDefaults()
 
+  >>> import zope.component
 
+  >>> from z3c.form import datamanager
+  >>> zope.component.provideAdapter(datamanager.DictionaryField)
 
+
   >>> from z3c.form.testing import IMySubObject
   >>> from z3c.form.testing import IMySecond
   >>> from z3c.form.testing import MySubObject
   >>> from z3c.form.testing import MySecond
 
-  >>> import zope.component
-
   >>> from z3c.form.object import registerFactoryAdapter
   >>> registerFactoryAdapter(IMySubObject, MySubObject)
   >>> registerFactoryAdapter(IMySecond, MySecond)
@@ -223,13 +225,20 @@
     </body>
   </html>
 
-  >>> v = widget.value
+Widget value comes from the request:
+
+  >>> wv = widget.value
+  >>> wv
+  {'foofield': 2, 'barfield': 999}
+
+But our object will not be modified, since there was no "apply"-like control.
+
   >>> v
   <z3c.form.testing.MySubObject object at ...>
   >>> v.foofield
-  2
+  42
   >>> v.barfield
-  999
+  666
 
 The marker must stay (we have to modify the same object):
 
@@ -241,7 +250,7 @@
 Error handling is next. Let's use the value "bad" (an invalid integer literal)
 as input for our internal (sub) widget.
 
-  >>> widget.request = TestRequest(form={'subobject.widgets.foofield':u'2',
+  >>> widget.request = TestRequest(form={'subobject.widgets.foofield':u'55',
   ...                                    'subobject.widgets.barfield':u'bad',
   ...                                    'subobject-empty-marker':u'1'})
 
@@ -261,7 +270,7 @@
           <input class="text-widget required int-field"
           id="subobject-widgets-foofield"
           name="subobject.widgets.foofield"
-          type="text" value="2">
+          type="text" value="55">
         </div>
         <div class="label">
           <label for="subobject-widgets-barfield">
@@ -282,13 +291,26 @@
     </body>
   </html>
 
+Getting the widget value raises the widget errors:
+
   >>> widget.value
   Traceback (most recent call last):
   ...
   MultipleErrors
 
+Our object still must retain the old values:
 
+  >>> v
+  <z3c.form.testing.MySubObject object at ...>
+  >>> v.foofield
+  42
+  >>> v.barfield
+  666
+  >>> v.__marker__
+  'ThisMustStayTheSame'
 
+
+
 In forms
 ========
 
@@ -301,6 +323,28 @@
   >>> 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:
+
+  >>> eventlog = []
+  >>> import zope.lifecycleevent
+  >>> @zope.component.adapter(zope.lifecycleevent.ObjectModifiedEvent)
+  ... def logEvent(event):
+  ...     eventlog.append(event)
+  >>> zope.component.provideHandler(logEvent)
+  >>> @zope.component.adapter(zope.lifecycleevent.ObjectCreatedEvent)
+  ... def logEvent2(event):
+  ...     eventlog.append(event)
+  >>> zope.component.provideHandler(logEvent2)
+
+  >>> def printEvents():
+  ...     for event in eventlog:
+  ...         print str(event)
+  ...         if isinstance(event, zope.lifecycleevent.ObjectModifiedEvent):
+  ...             for attr in event.descriptions:
+  ...                 print attr.interface
+  ...                 print attr.attributes
+
 We define an interface containing a subobject, and an addform for it:
 
   >>> from z3c.form import form, field
@@ -323,6 +367,7 @@
   >>> request = TestRequest()
   >>> myaddform =  MyAddForm(root, request)
 
+
   >>> myaddform.update()
 
 As usual, the form contains a widget manager with the expected widget
@@ -418,6 +463,7 @@
   ...     'form.widgets.subobject-empty-marker':u'1',
   ...     'form.buttons.add':'Add'})
   >>> myaddform.request = request
+
   >>> myaddform.update()
   MyAddForm.create {'subobject': <z3c.form.testing.MySubObject object at ...>,
   'name': u'first'}
@@ -437,6 +483,21 @@
   >>> root['first'].subobject.barfield
   99
 
+Let's see our event log:
+
+  >>> len(eventlog)
+  4
+
+  >>> printEvents()
+  <zope.app.event.objectevent.ObjectCreatedEvent object at ...>
+  <zope.app.event.objectevent.ObjectModifiedEvent object at ...>
+  <InterfaceClass z3c.form.testing.IMySubObject>
+  ('foofield', 'barfield')
+  <zope.app.event.objectevent.ObjectCreatedEvent object at ...>
+  <zope.app.container.contained.ContainerModifiedEvent object at ...>
+
+  >>> eventlog=[]
+
 Let's try to edit that newly added object:
 
   >>> class MyEditForm(form.EditForm):
@@ -521,6 +582,22 @@
   >>> root['first'].subobject.barfield
   55
 
+Let's see our event log:
+
+  >>> len(eventlog)
+  2
+
+  >>> printEvents()
+  <zope.app.event.objectevent.ObjectModifiedEvent object at ...>
+  <InterfaceClass z3c.form.testing.IMySubObject>
+  ('foofield', 'barfield')
+  <zope.app.event.objectevent.ObjectModifiedEvent object at ...>
+  <InterfaceClass z3c.form.testing.IMyObject>
+  ('subobject',)
+
+  >>> eventlog=[]
+
+
 After the update the form says that the values got updated and renders the new
 values:
 
@@ -617,8 +694,14 @@
   ...     'form.buttons.apply':'Apply'})
 
   >>> editform.request = request
+  >>> eventlog=[]
   >>> editform.update()
 
+Eventlog must be clean:
+
+  >>> len(eventlog)
+  0
+
 Watch for the error message in the HTML:
 it has to appear at the field itself and at the top of the form:
 
@@ -777,6 +860,28 @@
   999
 
 
+#Let's make an other error, by typing a newline into name:
+#
+#  >>> request = TestRequest(form={
+#  ...     'form.widgets.subobject.widgets.foofield':u'1',
+#  ...     'form.widgets.subobject.widgets.barfield':u'2',
+#  ...     'form.widgets.name':u'first\nline',
+#  ...     'form.widgets.subobject-empty-marker':u'1',
+#  ...     'form.buttons.apply':'Apply'})
+#
+#  >>> editform.request = request
+#  >>> eventlog=[]
+#  >>> editform.update()
+
+THIS IS BAD:
+
+  #>>> len(eventlog)
+  #0
+  #>>> printEvents()
+  #<zope.app.event.objectevent.ObjectModifiedEvent object at 0x97f688c>
+  #<InterfaceClass z3c.form.testing.IMySubObject>
+  #('foofield', 'barfield')
+
 Simple but often used use-case is the display form:
 
   >>> editform = MyEditForm(root['first'], TestRequest())
@@ -862,7 +967,7 @@
 #
 #  >>> myaddform =  MyAddForm(root, request)
 #
-##>>> from pub.dbgpclient import brk; brk('192.168.32.1')
+#  >>> from pub.dbgpclient import brk; brk('192.168.32.1')
 #
 #
 #  >>> myaddform.update()

Modified: z3c.form/branches/adamg-objectwidget/src/z3c/form/field.py
===================================================================
--- z3c.form/branches/adamg-objectwidget/src/z3c/form/field.py	2008-10-13 13:18:24 UTC (rev 92115)
+++ z3c.form/branches/adamg-objectwidget/src/z3c/form/field.py	2008-10-13 13:26:41 UTC (rev 92116)
@@ -250,7 +250,9 @@
             # Step 5: Set the form
             widget.form = self.form
             # Optimization: set both interfaces here, rather in step 4 and 5: alsoProvides is quite slow
-            zope.interface.alsoProvides(widget, interfaces.IContextAware, interfaces.IFormAware)
+            zope.interface.alsoProvides(widget,
+                                        interfaces.IContextAware,
+                                        interfaces.IFormAware)
             # Step 6: Set some variables
             widget.ignoreContext = self.ignoreContext
             widget.ignoreRequest = self.ignoreRequest

Modified: z3c.form/branches/adamg-objectwidget/src/z3c/form/object.py
===================================================================
--- z3c.form/branches/adamg-objectwidget/src/z3c/form/object.py	2008-10-13 13:18:24 UTC (rev 92115)
+++ z3c.form/branches/adamg-objectwidget/src/z3c/form/object.py	2008-10-13 13:26:41 UTC (rev 92116)
@@ -20,6 +20,8 @@
 import zope.interface
 import zope.component
 import zope.schema
+import zope.event
+import zope.lifecycleevent
 
 from z3c.form.converter import BaseDataConverter
 
@@ -84,6 +86,9 @@
 
         self._validate()
 
+    def getContent(self):
+        return self.__parent__._value
+
 class ObjectConverter(BaseDataConverter):
     """Data converter for IObjectWidget."""
 
@@ -95,13 +100,63 @@
         if value is self.field.missing_value:
             return interfaces.NOVALUE
 
-        return value
+        retval = {}
+        for name in zope.schema.getFieldNames(self.field.schema):
+            retval[name] = getattr(value, name, interfaces.NOVALUE)
 
+        return retval
+
+    def createObject(self, value):
+        #keep value passed, maybe some subclasses want it
+        #value here is the raw extracted from the widget's subform
+        #in the form of a dict key:fieldname, value:fieldvalue
+
+        name = self.field.schema.__module__+'.'+self.field.schema.__name__
+        creator = zope.component.queryMultiAdapter(
+            (self.widget.context, self.widget.request,
+             self.widget.form, self.widget),
+            interfaces.IObjectFactory,
+            name=name)
+        if creator:
+            obj = creator(value)
+        else:
+            raise ValueError("No IObjectFactory adapter registered for %s" %
+                             name)
+
+        return obj
+
     def toFieldValue(self, value):
         """See interfaces.IDataConverter"""
         if value is interfaces.NOVALUE:
             return self.field.missing_value
 
+        if self.widget.subform.ignoreContext:
+            obj = self.createObject(value)
+        else:
+            obj = getattr(self.widget.context, self.field.__name__)
+
+        obj = self.field.schema(obj)
+
+        names = []
+        for name in zope.schema.getFieldNames(self.field.schema):
+            try:
+                oldval = getattr(obj, name, interfaces.NOVALUE)
+                if (oldval != value[name]
+                    or zope.schema.interfaces.IObject.providedBy(
+                        self.field.schema[name])
+                    ):
+                    setattr(obj, name, value[name])
+                    names.append(name)
+            except KeyError:
+                pass
+
+        if names:
+            zope.event.notify(
+                zope.lifecycleevent.ObjectModifiedEvent(obj,
+                    zope.lifecycleevent.Attributes(self.field.schema, *names)))
+        return obj
+
+
         return value
 
 
@@ -137,39 +192,12 @@
         def get(self):
             return self.extract()
         def set(self, value):
-            if isinstance(value, tuple):
-                try:
-                    value = interfaces.IDataConverter(self).toFieldValue(value)
-                    self._value = value
-                except (zope.schema.ValidationError,
-                    ValueError, MultipleErrors), error:
-                    pass
-            else:
-                self._value = value
-
+            self._value = value
             # ensure that we apply our new values to the widgets
             self.updateWidgets()
         return property(get, set)
 
-    def createObject(self, value):
-        #keep value passed, maybe some subclasses want it
-        #nasty: value here is the raw extracted from the widget's subform
-        #in the form of (value-dict, (error1, error2))
 
-        name = self.field.schema.__module__+'.'+self.field.schema.__name__
-        creator = zope.component.queryMultiAdapter(
-            (self.context, self.request,
-             self.form, self),
-            interfaces.IObjectFactory,
-            name=name)
-        if creator:
-            obj = creator(value)
-        else:
-            raise ValueError("No IObjectFactory adapter registered for %s" %
-                             name)
-
-        return obj
-
     def extract(self, default=interfaces.NOVALUE):
         if self.name+'-empty-marker' in self.request:
             self.updateWidgets()
@@ -178,25 +206,23 @@
             #value here is (data-dict, (error1, error2))
 
             if value[1]:
-                #very-very-nasty: skip raising exceptions in extract while we're updating
+                #very-very-nasty: skip raising exceptions in extract
+                #while we're updating
                 if self._updating:
+                    #very-very-nasty: kill all errors on subwidgets
+                    if self.subform.widgets.errors:
+                        pass
+                        #from pub.dbgpclient import brk; brk('192.168.32.1')
+
+                    self.subform.widgets.errors = ()
+                    for w in self.subform.widgets.values():
+                        w.error=None
+
                     return default
                 raise MultipleErrors(value[1])
 
-            if (self._value is not interfaces.NOVALUE
-                and not self.subform.ignoreContext):
-                obj = self._value
-            else:
-                obj = self.createObject(value)
+            return value[0]
 
-            obj = self.field.schema(obj)
-
-            for name in zope.schema.getFieldNames(self.field.schema):
-                try:
-                    setattr(obj, name, value[0][name])
-                except KeyError:
-                    pass
-            return obj
         else:
             return default
 
@@ -217,7 +243,9 @@
 
     def __call__(self, value):
         #value is the extracted data from the form
-        return self.factory()
+        obj = self.factory()
+        zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(obj))
+        return obj
 
     def __repr__(self):
         return '<%s %r>' % (self.__class__.__name__, self.__name__)



More information about the Checkins mailing list