[Checkins] SVN: z3c.form/trunk/ fix duplicated widgets key and values generation by calling update more then once.

Roger Ineichen roger at projekt01.ch
Sat Feb 21 11:39:43 EST 2009


Log message for revision 96962:
  fix duplicated widgets key and values generation by calling update more then once.
  See CHANGES.txt for more infos.

Changed:
  U   z3c.form/trunk/CHANGES.txt
  U   z3c.form/trunk/src/z3c/form/button.py
  U   z3c.form/trunk/src/z3c/form/button.txt
  U   z3c.form/trunk/src/z3c/form/field.py
  U   z3c.form/trunk/src/z3c/form/field.txt
  U   z3c.form/trunk/src/z3c/form/util.py

-=-
Modified: z3c.form/trunk/CHANGES.txt
===================================================================
--- z3c.form/trunk/CHANGES.txt	2009-02-21 16:19:18 UTC (rev 96961)
+++ z3c.form/trunk/CHANGES.txt	2009-02-21 16:39:43 UTC (rev 96962)
@@ -5,8 +5,18 @@
 Version 2.0.0 (unreleased)
 --------------------------
 
+- Bug: FieldWidgets update where appending keys and values within each
+  update call. Now the util.Manager uses a UniqueOrderedKeys implementation
+  which will ensure that we can't add duplicted manager keys. The implementation
+  also ensures that we can't override the UniqueOrderedKeys instace with a new
+  list by using a decorator. If this UniqueOrderedKeys implementation doesn't
+  fit for all use cases, we should probably use a customized UserList
+  implementation. Now we can call widgets.update() more then one time without
+  any side effect.
+
 - Bug: ButtonActions update where appending keys and values within each
-  update call. 
+  update call. Now we can call actions.update() more then one time without
+  any side effect.
 
 - Bug: The CollectionSequenceDataConverter no longer throws a
   "TypeError: 'NoneType' object is not iterable" when passed the value

Modified: z3c.form/trunk/src/z3c/form/button.py
===================================================================
--- z3c.form/trunk/src/z3c/form/button.py	2009-02-21 16:19:18 UTC (rev 96961)
+++ z3c.form/trunk/src/z3c/form/button.py	2009-02-21 16:39:43 UTC (rev 96962)
@@ -254,7 +254,7 @@
         prefix = util.expandPrefix(self.form.prefix)
         prefix += util.expandPrefix(self.form.buttons.prefix)
         # Walk through each field, making an action out of it.
-        orderedNames = []
+        uniqueOrderedKeys = []
         for name, button in self.form.buttons.items():
             # Step 1: Only create an action for the button, if the condition is
             #         fulfilled.
@@ -286,13 +286,14 @@
             buttonAction.update()
             zope.event.notify(AfterWidgetUpdateEvent(buttonAction))
             # Step 7: Add the widget to the manager
-            orderedNames.append(name)
+            uniqueOrderedKeys.append(name)
             if newButton:
-                # allways keep the order given from button items
-                self._data_keys = orderedNames
                 self._data_values.append(buttonAction)
                 self._data[name] = buttonAction
                 zope.location.locate(buttonAction, self, name)
+            # allways ensure that we add all keys and keep the order given from
+            # button items
+            self._data_keys = uniqueOrderedKeys
 
 
 class ButtonActionHandler(action.ActionHandlerBase):

Modified: z3c.form/trunk/src/z3c/form/button.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/button.txt	2009-02-21 16:19:18 UTC (rev 96961)
+++ z3c.form/trunk/src/z3c/form/button.txt	2009-02-21 16:39:43 UTC (rev 96962)
@@ -93,6 +93,9 @@
 As you can see we still will get the old button action if we only call update:
 
   >>> actions.update()
+  >>> actions.keys()
+  ['apply', 'cancel']
+
   >>> actions['apply']
   <ButtonAction 'form.buttons.apply' u'Apply'>
 
@@ -128,7 +131,6 @@
   This button factory creates a button only once.
 
   >>> actions.update()
-
   >>> actions['apply'].css
   'happy'
 

Modified: z3c.form/trunk/src/z3c/form/field.py
===================================================================
--- z3c.form/trunk/src/z3c/form/field.py	2009-02-21 16:19:18 UTC (rev 96961)
+++ z3c.form/trunk/src/z3c/form/field.py	2009-02-21 16:39:43 UTC (rev 96962)
@@ -223,6 +223,7 @@
         prefix = util.expandPrefix(self.form.prefix)
         prefix += util.expandPrefix(self.prefix)
         # Walk through each field, making a widget out of it.
+        uniqueOrderedKeys = []
         for field in self.form.fields.values():
             # Step 0. Determine whether the context should be ignored.
             ignoreContext = self.ignoreContext
@@ -242,14 +243,19 @@
                 if not dm.canWrite():
                     mode = interfaces.DISPLAY_MODE
             # Step 2: Get the widget for the given field.
-            factory = field.widgetFactory.get(mode)
-            if factory is not None:
+            shortName = field.__name__
+            newWidget = True
+            if shortName in self._data:
+                # reuse existing widget
+                widget = self._data[shortName]
+                newWidget = False
+            elif field.widgetFactory.get(mode) is not None:
+                factory = field.widgetFactory.get(mode)
                 widget = factory(field.field, self.request)
             else:
                 widget = zope.component.getMultiAdapter(
                     (field.field, self.request), interfaces.IFieldWidget)
             # Step 3: Set the prefix for the widget
-            shortName = field.__name__
             widget.name = prefix + shortName
             widget.id = (prefix + shortName).replace('.', '-')
             # Step 4: Set the context
@@ -269,10 +275,14 @@
             widget.update()
             zope.event.notify(AfterWidgetUpdateEvent(widget))
             # Step 9: Add the widget to the manager
-            self._data_keys.append(shortName)
-            self._data_values.append(widget)
-            self._data[shortName] = widget
-            zope.location.locate(widget, self, shortName)
+            uniqueOrderedKeys.append(shortName)
+            if newWidget:
+                self._data_values.append(widget)
+                self._data[shortName] = widget
+                zope.location.locate(widget, self, shortName)
+            # allways ensure that we add all keys and keep the order given from
+            # button items
+            self._data_keys = uniqueOrderedKeys
 
     def extract(self):
         """See interfaces.IWidgets"""

Modified: z3c.form/trunk/src/z3c/form/field.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/field.txt	2009-02-21 16:19:18 UTC (rev 96961)
+++ z3c.form/trunk/src/z3c/form/field.txt	2009-02-21 16:39:43 UTC (rev 96962)
@@ -417,29 +417,13 @@
   >>> manager.keys()
   ['id', 'lastName', 'firstName']
 
+As you can see, if we call update twice, we still get the same amount and
+order of keys:
 
-
-ISSUE: duplicated key, values if update ge called more then once. 
-NOTE: I'll fix this today.
-
-  >>> keys = list(manager.keys())
-  >>> values = list(manager.values())
-  >>> data = dict(manager._data)
-
   >>> manager.update()
   >>> manager.keys()
-  ['id', 'lastName', 'firstName', 'id', 'lastName', 'firstName']
+  ['id', 'lastName', 'firstName']
 
-set values back to manager since the test will get otherwise messup
-
-  >>> manager._data_keys = keys
-  >>> manager._data_values = values
-  >>> manager._data = data
-
-END ISSUE
-
-
-
 Let's make sure that all enumerable mapping functions work correctly:
 
   >>> manager['lastName']

Modified: z3c.form/trunk/src/z3c/form/util.py
===================================================================
--- z3c.form/trunk/src/z3c/form/util.py	2009-02-21 16:19:18 UTC (rev 96961)
+++ z3c.form/trunk/src/z3c/form/util.py	2009-02-21 16:39:43 UTC (rev 96962)
@@ -117,29 +117,62 @@
     return widget.filename
 
 
+class UniqueOrderedKeys(object):
+    """Ensures that we only ue unique keys in a list.
+    
+    This is usefull since we use the keys and values list only as ordered
+    keys and values addition for our data dict.
+    
+    Note, this list is only used for Manager keys and not for values since we
+    can't really compare values if we will get new instances of widgets or
+    actions.
+    """
+
+    def __init__(self, values=[]):
+        self.data = []
+        # ensure that we not intialize a list with duplicated key values
+        [self.data.append(value) for value in values]
+
+    def append(self, value):
+        if value in self.data:
+            raise ValueError(value)
+        self.data.append(value)
+
+
 class Manager(object):
     """Non-persistent IManager implementation."""
     zope.interface.implements(interfaces.IManager)
 
     def __init__(self, *args, **kw):
-        self._data_keys = []
+        self.__data_keys = UniqueOrderedKeys()
         self._data_values = []
         self._data = {}
 
+    @apply
+    def _data_keys():
+        """Use a special ordered list which will check for duplicated keys.""" 
+        def get(self):
+            return self.__data_keys
+        def set(self, values):
+            if isinstance(values, UniqueOrderedKeys):
+                self.__data_keys = values
+            else:
+                self.__data_keys = UniqueOrderedKeys(values)
+        return property(get, set)
+
     def __len__(self):
         return len(self._data_values)
 
     def __iter__(self):
-        return iter(self._data_keys)
+        return iter(self._data_keys.data)
 
     def __getitem__(self, name):
         return self._data[name]
 
     def __delitem__(self, name):
-        if name not in self._data_keys:
+        if name not in self._data_keys.data:
             raise KeyError(name)
-
-        del self._data_keys[self._data_keys.index(name)]
+        del self._data_keys.data[self._data_keys.data.index(name)]
         value = self._data[name]
         del self._data_values[self._data_values.index(value)]
         del self._data[name]
@@ -148,13 +181,13 @@
         return self._data.get(name, default)
 
     def keys(self):
-        return self._data_keys
+        return self._data_keys.data
 
     def values(self):
         return self._data_values
 
     def items(self):
-        return [(i, self._data[i]) for i in self._data_keys]
+        return [(i, self._data[i]) for i in self._data_keys.data]
 
     def __contains__(self, name):
         return bool(self.get(name))



More information about the Checkins mailing list