[Zope3-checkins] CVS: Zope3/src/zope/app/browser/form - widget.py:1.31

Jim Fulton jim@zope.com
Thu, 22 May 2003 18:49:35 -0400


Update of /cvs-repository/Zope3/src/zope/app/browser/form
In directory cvs.zope.org:/tmp/cvs-serv12528/src/zope/app/browser/form

Modified Files:
	widget.py 
Log Message:
Fixed bugs in the Checkbox widget. It didn't properly reflect current
data.

Fixed a bug (reported by Guido) in handling conversion of line endings
between web forms and regular data.

Added a number of additioanl tests.



=== Zope3/src/zope/app/browser/form/widget.py 1.30 => 1.31 ===
--- Zope3/src/zope/app/browser/form/widget.py:1.30	Mon May 19 16:54:03 2003
+++ Zope3/src/zope/app/browser/form/widget.py	Thu May 22 18:49:04 2003
@@ -17,6 +17,7 @@
 
 __metaclass__ = type
 
+import warnings
 from zope.proxy.introspection import removeAllProxies
 from zope.publisher.browser import BrowserView
 from zope.app.interfaces.browser.form import IBrowserWidget
@@ -32,7 +33,50 @@
 
 
 class BrowserWidget(Widget, BrowserView):
-    """A field widget that knows how to display itself as HTML."""
+    """A field widget that knows how to display itself as HTML.
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import Field
+    >>> field = Field(__name__='foo', title=u'Foo')
+    >>> request = TestRequest(form={'field.foo': u'hello\\r\\nworld'})
+    >>> widget = BrowserWidget(field, request)
+    >>> widget.name
+    'field.foo'
+    >>> widget.title
+    u'Foo'
+    >>> int(widget.haveData())
+    1
+    >>> widget.getData()
+    u'hello\\r\\nworld'
+    >>> int(widget.required)
+    1
+    >>> widget.setData('Hey\\nfolks')
+    >>> widget.getData()
+    u'hello\\r\\nworld'
+
+    >>> widget.setPrefix('test')
+    >>> widget.name
+    'test.foo'
+    >>> int(widget.haveData())
+    0
+    >>> widget.getData()
+    Traceback (most recent call last):
+    ...
+    MissingInputError: ('foo', u'Foo', 'the field is required')
+    >>> field.required = False
+    >>> int(widget.required)
+    0
+    >>> widget.getData()
+
+    When we generate labels, the labels are translated, so we need to set up
+    a lot of machinery to support translation:
+
+    >>> setUp()
+    >>> print widget.label()
+    <label for="test.foo">Foo</label>
+    >>> tearDown()
+    
+    """
 
     __implements__ = IBrowserWidget
 
@@ -47,7 +91,7 @@
 
     def haveData(self):
         if self.name in self.request.form:
-            return self._convert(self.request[self.name]) is not None
+            return self._convert(self.request[self.name]) != self._missing
         return False
 
     def getData(self, optional=0):
@@ -116,10 +160,15 @@
                              extra = self.getValue('extra'))
 
     def render(self, value):
+        warnings.warn("The widget render method is deprecated",
+                      DeprecationWarning, 2)
+
         self.setData(value)
         return self()
 
     def renderHidden(self, value):
+        warnings.warn("The widget render method is deprecated",
+                      DeprecationWarning, 2)
         self.setData(value)
         return self.hidden()
 
@@ -143,7 +192,66 @@
         return self._showData()
 
 class CheckBoxWidget(BrowserWidget):
-    """Checkbox widget"""
+    """Checkbox widget
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import Bool
+    >>> field = Bool(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo.used': u'on',
+    ...                             'field.foo': u'on'})
+    >>> widget = CheckBoxWidget(field, request)
+    >>> int(widget.haveData())
+    1
+    >>> int(widget.getData())
+    1
+    
+    >>> def normalize(s):
+    ...   return '\\n  '.join(s.split())
+
+    >>> print normalize( widget() )
+    <input
+      class="hiddenType"
+      id="field.foo.used"
+      name="field.foo.used"
+      type="hidden"
+      value=""
+      />
+      <input
+      class="checkboxType"
+      checked="checked"
+      id="field.foo"
+      name="field.foo"
+      type="checkbox"
+      />
+
+    >>> print normalize( widget.hidden() )
+    <input
+      class="hiddenType"
+      id="field.foo"
+      name="field.foo"
+      type="hidden"
+      value="1"
+      />
+
+    Calling setData will change what gets output:
+    
+    >>> widget.setData(False)
+    >>> print normalize( widget() )
+    <input
+      class="hiddenType"
+      id="field.foo.used"
+      name="field.foo.used"
+      type="hidden"
+      value=""
+      />
+      <input
+      class="checkboxType"
+      id="field.foo"
+      name="field.foo"
+      type="checkbox"
+      />
+
+    """
     propertyNames = BrowserWidget.propertyNames + \
                      ['extra', 'default']
 
@@ -157,19 +265,31 @@
             kw = {'checked': None}
         else:
             kw = {}
-        return renderElement(self.getValue('tag'),
+        return "%s %s" % (
+            renderElement(self.getValue('tag'),
+                          type = 'hidden',
+                          name = self.name+".used",
+                          id = self.name+".used",
+                          value=""
+                          ),
+            renderElement(self.getValue('tag'),
                              type = self.getValue('type'),
                              name = self.name,
                              id = self.name,
                              cssClass = self.getValue('cssClass'),
                              extra = self.getValue('extra'),
-                             **kw)
+                             **kw),
+            )
 
     def _convert(self, value):
         return value == 'on'
 
     def haveData(self):
-        return True
+        return (
+            self.name+".used" in self.request.form
+            or
+            self.name in self.request.form
+            )
 
     def getData(self, optional=0):
         # When it's checked, its value is 'on'.
@@ -178,24 +298,65 @@
         value = self.request.form.get(self.name, 'off')
         return value == 'on'
 
-class PossiblyEmptyMeansMissing:
-
-    def haveData(self):
-        v = self.request.form.get(self.name)
-        if v is None:
-            return False
-        if not v and getattr(self.context, 'min_length', 1) > 0:
-            return False
-        return True
+class PossiblyEmptyMeansMissing(BrowserWidget):
 
     def _convert(self, value):
-        v = self.request.form.get(self.name)
-        if not v and getattr(self.context, 'min_length', 1) > 0:
+        value = super(PossiblyEmptyMeansMissing, self)._convert(value)
+        if not value and getattr(self.context, 'min_length', 1) > 0:
             return None
-        return v
+        return value
 
 class TextWidget(PossiblyEmptyMeansMissing, BrowserWidget):
-    """Text widget."""
+    """Text widget.
+
+    Single-line text (unicode) input
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import TextLine
+    >>> field = TextLine(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Bob'})
+    >>> widget = TextWidget(field, request)
+    >>> int(widget.haveData())
+    1
+    >>> widget.getData()
+    u'Bob'
+    
+    >>> def normalize(s):
+    ...   return '\\n  '.join(filter(None, s.split(' ')))
+
+    >>> print normalize( widget() )
+    <input
+      class="textType"
+      id="field.foo"
+      name="field.foo"
+      size="20"
+      type="text"
+      value="Bob"
+      />
+
+    >>> print normalize( widget.hidden() )
+    <input
+      class="hiddenType"
+      id="field.foo"
+      name="field.foo"
+      type="hidden"
+      value="Bob"
+      />
+
+    Calling setData will change what gets output:
+    
+    >>> widget.setData("Barry")
+    >>> print normalize( widget() )
+    <input
+      class="textType"
+      id="field.foo"
+      name="field.foo"
+      size="20"
+      type="text"
+      value="Barry"
+      />
+
+    """
     propertyNames = (BrowserWidget.propertyNames +
                      ['displayWidth', 'displayMaxWidth', 'extra', 'default']
                      )
@@ -208,11 +369,6 @@
     style = ''
     __values = None
 
-    def _convert(self, value):
-        if self.context.min_length and not value:
-            return None
-        return value
-
     def __init__(self, *args):
         super(TextWidget, self).__init__(*args)
 
@@ -278,12 +434,9 @@
                                  size = self.getValue('displayWidth'),
                                  extra = self.getValue('extra'))
 
-class Bytes:
+class Bytes(BrowserWidget):
 
     def _convert(self, value):
-        if self.context.min_length and not value:
-            return None
-
         value = super(Bytes, self)._convert(value)
         if type(value) is unicode:
             try:
@@ -294,7 +447,21 @@
         return value
 
 class BytesWidget(Bytes, TextWidget):
-    pass
+    """Bytes widget.
+
+    Single-line data (string) input
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import BytesLine
+    >>> field = BytesLine(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Bob'})
+    >>> widget = BytesWidget(field, request)
+    >>> int(widget.haveData())
+    1
+    >>> widget.getData()
+    'Bob'
+
+    """
 
 class IntWidget(TextWidget):
     displayWidth = 10
@@ -329,7 +496,55 @@
                 raise ConversionError("Invalid datetime data", v)
 
 class TextAreaWidget(PossiblyEmptyMeansMissing, BrowserWidget):
-    """Textarea widget."""
+    """TextArea widget.
+
+    Multi-line text (unicode) input.
+    
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import Text
+    >>> field = Text(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+    >>> widget = TextAreaWidget(field, request)
+    >>> int(widget.haveData())
+    1
+    >>> widget.getData()
+    u'Hello\\nworld!'
+    
+    >>> def normalize(s):
+    ...   return '\\n  '.join(filter(None, s.split(' ')))
+
+    >>> print normalize( widget() )
+    <textarea
+      cols="60"
+      id="field.foo"
+      name="field.foo"
+      rows="15"
+      >Hello\r
+    world!</textarea>
+
+    >>> print normalize( widget.hidden() )
+    <input
+      class="hiddenType"
+      id="field.foo"
+      name="field.foo"
+      type="hidden"
+      value="Hello\r
+    world!"
+      />
+
+    Calling setData will change what gets output:
+    
+    >>> widget.setData("Hey\\ndude!")
+    >>> print normalize( widget() )
+    <textarea
+      cols="60"
+      id="field.foo"
+      name="field.foo"
+      rows="15"
+      >Hey\r
+    dude!</textarea>
+
+    """
     propertyNames = BrowserWidget.propertyNames + ['width', 'height', 'extra']
 
     default = ""
@@ -339,10 +554,17 @@
     style = ''
 
     def _convert(self, value):
-        if self.context.min_length and not value:
-            return None
+        value = super(TextAreaWidget, self)._convert(value)
+        if value:
+            value = value.replace("\r\n", "\n")
         return value
 
+    def _unconvert(self, value):
+        value = super(TextAreaWidget, self)._unconvert(value)
+        if value:
+            value = value.replace("\n", "\r\n")
+        return value        
+
     def __call__(self):
         return renderElement("textarea",
                              name = self.name,
@@ -362,7 +584,21 @@
                 self.label(), self())
 
 class BytesAreaWidget(Bytes, TextAreaWidget):
-    pass
+    """BytesArea widget.
+
+    Multi-line text (unicode) input.
+    
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import Bytes
+    >>> field = Bytes(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+    >>> widget = BytesAreaWidget(field, request)
+    >>> int(widget.haveData())
+    1
+    >>> widget.getData()
+    'Hello\\nworld!'
+
+    """
 
 class PasswordWidget(TextWidget):
     """Password Widget"""
@@ -734,7 +970,9 @@
         extra = ""
 
     # handle other attributes
-    for key, value in kw.items():
+    items = kw.items()
+    items.sort()
+    for key, value in items:
         if value == None:
             value = key
         attr_list.append('%s="%s"' % (key, str(value)))
@@ -750,3 +988,16 @@
         return "%s>%s</%s>" % (renderTag(tag, **kw), contents, tag)
     else:
         return renderTag(tag, **kw) + " />"
+
+def setUp():
+    import zope.app.tests.placelesssetup
+    global setUp
+    setUp = zope.app.tests.placelesssetup.setUp
+    setUp()
+
+def tearDown():
+    import zope.app.tests.placelesssetup
+    global tearDown
+    tearDown = zope.app.tests.placelesssetup.tearDown
+    tearDown()
+