quixote/form widget.py,1.40,1.41

Neil Schemenauer <nascheme-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Thu, 07 Nov 2002 17:29:19 -0500
Newsgroups gmane.comp.web.quixote.cvs
Message-ID <[email protected]>
Update of /home/cvs/quixote/form
In directory hewson:/tmp/cvs-serv2742

Modified Files:
	widget.py 
Log Message:
Another stab at solving the "keys for selection options" problem.  If
keys are not provided, try using the _p_oid of the allowed values.  If
that doesn't work, check if the descriptions are unique and use them.
If that fails, raise a ValueError.  Explicit keys will have to be
provided in that case.

Using _p_oid works well for us since most of our objects have OIDs.  I
don't expect it to hurt other people either since the chance of someone
using _p_oid as an attribute is small.

Share code between SingleSelectWidget.parse and
MultipleSelectWidget.parse.


Index: widget.py
===================================================================
RCS file: /home/cvs/quixote/form/widget.py,v
retrieving revision 1.40
retrieving revision 1.41
diff -u -d -r1.40 -r1.41
--- widget.py	7 Nov 2002 18:00:36 -0000	1.40
+++ widget.py	7 Nov 2002 22:29:17 -0000	1.41
@@ -8,6 +8,7 @@
 
 __revision__ = "$Id$"
 
+import struct
 from types import FloatType, IntType, ListType, StringType, TupleType
 from quixote import get_request
 from quixote.html import htmltext, htmlescape, htmltag, ValuelessAttr
@@ -245,7 +246,7 @@
     </select>
 
     Instance attributes:
-      options : [ (value:any, description:any, key:any) ]
+      options : [ (value:any, description:any, key:string) ]
       value : any
         The value is None or an element of dict(options.values()).
       size : int
@@ -288,6 +289,34 @@
                 self.value = value
                 break
 
+    def _generate_keys (self, values, descriptions):
+        """Called if no keys were provided.  Try to generate a set of keys
+        that will be consistent between rendering and parsing.
+        """
+        # try to use ZODB object IDs
+        keys = []
+        for value in values:
+            if value is None:
+                oid = ""
+            else:
+                oid = getattr(value, "_p_oid", None)
+                if not oid:
+                    break
+                hi, lo = struct.unpack(">LL", oid)
+                oid = "%x" % ((hi << 32) | lo)
+            keys.append(oid)
+        else:
+            # found OID for every value
+            return keys
+        # can't use OIDs, try using descriptions
+        used_keys = {}
+        keys = map(str, descriptions)
+        for key in keys:
+            if used_keys.has_key(key):
+                raise ValueError, "duplicated descriptions (provide keys)"
+            used_keys[key] = 1
+        return keys
+        
     def set_options (self, options, sort=0):
         """(options: [objects:any], sort=0)
          or 
@@ -307,30 +336,29 @@
         before others.
         """
         if options:
-            # provide default values for descriptions and keys
             first = options[0]
+            values = []
+            descriptions = []
+            keys = []
             if type(first) is TupleType:
                 if len(first) == 2:
-                    options = [ (object, description, description)
-                                for object, description in options ]
-                elif len(first) != 3:
+                    for value, description in options:
+                        values.append(value)
+                        descriptions.append(description)
+                elif len(first) == 3:
+                    for value, description, key in options:
+                        values.append(value)
+                        descriptions.append(description)
+                        keys.append(str(key))
+                else:
                     raise ValueError, 'invalid options %r' % options
             else:
-                options = zip(options, options, options)
+                values = descriptions = options
 
-            # Look for duplicate keys.  If any are found, replace them.
-            found = {}
-            
-            def unique (key):
-                count = 1
-                while key in found:
-                    key = "%s__%d__" % (key, count)
-                    count += 1
-                found[key]=1
-                return key
+            if not keys:
+                keys = self._generate_keys(values, descriptions)
 
-            options = [ (object, description, unique(key))
-                        for object, description, key in options ]
+            options = zip(values, descriptions, keys)
 
             if sort:
                 def compare(a, b):
@@ -389,6 +417,13 @@
         return htmltext("\n").join(tags)
 
 
+def _parse_single_selection(parsed_key, options):
+    for value, description, key in options:
+        if key == parsed_key:
+            return value
+    else:
+        raise FormValueError, "invalid value selected"
+
 class SingleSelectWidget (SelectWidget):
     """Widget for single selection.
     """
@@ -396,15 +431,12 @@
     widget_type = "single_select"
 
     def parse (self, request):
-        value = request.form.get(self.name)
+        parsed_key = request.form.get(self.name)
         self.value = None
-        if value:
-            if type(value) is ListType:
+        if parsed_key:
+            if type(parsed_key) is ListType:
                 raise FormValueError, "cannot select multiple values"
-            for object, description, key in self.options:
-                if value == str(key): # str() not htmlescape()!
-                    self.value = object
-                    break
+            self.value = _parse_single_selection(parsed_key, self.options)
         return self.value
 
 
@@ -479,15 +511,16 @@
             return value in self.value
 
     def parse (self, request):
-        value = request.form.get(self.name)
-        if value and type(value) is ListType:
-            self.value =  [ object
-                            for object, description, key in self.options
-                            if str(key) in value ] or None
-        else:
-            self.value =  [ object
-                            for object, description, key in self.options
-                            if str(key) == value ] or None
+        parsed_keys = request.form.get(self.name)
+        self.value = None
+        if parsed_keys:
+            if type(parsed_keys) is ListType:
+                self.value =  [ value
+                                for value, description, key in self.options
+                                if key in parsed_keys ] or None
+            else:
+                self.value = [ _parse_single_selection(parsed_keys,
+                                                      self.options) ]
         return self.value