[Zope3-checkins] SVN: Zope3/trunk/ Fixed bug #721: Handling of empty prefixes in zope.formlib and zope.app.form

Jacob Holm jh at improva.dk
Wed Dec 20 18:34:36 EST 2006


Log message for revision 71638:
  Fixed bug #721: Handling of empty prefixes in zope.formlib and zope.app.form

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/src/zope/app/form/__init__.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_setprefix.py
  U   Zope3/trunk/src/zope/app/form/browser/widget.py
  U   Zope3/trunk/src/zope/app/form/tests/test_widget.py
  U   Zope3/trunk/src/zope/formlib/form.py
  U   Zope3/trunk/src/zope/formlib/form.txt

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/doc/CHANGES.txt	2006-12-20 23:34:35 UTC (rev 71638)
@@ -151,7 +151,10 @@
         was removed back then already.)
 
     Bug fixes
- 
+
+      - Fixed bug #721: Handling of empty prefixes in zope.formlib and
+        zope.app.form
+
       - Fixed bug #707: "layer" directive was marked as deprecated in a
         confusing way.
 

Modified: Zope3/trunk/src/zope/app/form/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/form/__init__.py	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/app/form/__init__.py	2006-12-20 23:34:35 UTC (rev 71638)
@@ -69,7 +69,7 @@
         return self._data is not self._data_marker
 
     def setPrefix(self, prefix):
-        if not prefix.endswith("."):
+        if prefix and not prefix.endswith("."):
             prefix += '.'
         self._prefix = prefix
         self.name = prefix + self.context.__name__

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py	2006-12-20 23:34:35 UTC (rev 71638)
@@ -117,6 +117,14 @@
             ['test.foo', 'test.bar', 'test.a', 'test.b', 'test.getbaz']
             )
 
+    def test_empty_prefix(self):
+        v = EV(C(), TestRequest())
+        v.setPrefix("")
+        self.assertEqual(
+            [w.name for w in v.widgets()],
+            ['foo', 'bar', 'a', 'b', 'getbaz']
+            )
+
     def test_fail_wo_adapter(self):
         c = Foo()
         request = TestRequest()

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py	2006-12-20 23:34:35 UTC (rev 71638)
@@ -99,6 +99,12 @@
         self.assertEqual(widget.name, 'foo.%s' %name)
         self.assertEqual(widget.empty_marker_name,
                          'foo.%s-empty-marker' %name)
+        # Declaring empty prefix
+        widget.setPrefix('')
+        self.assertEqual(widget._prefix, '')
+        self.assertEqual(widget.name, name)
+        self.assertEqual(widget.empty_marker_name,
+                         '%s-empty-marker' %name)
 
     def test_convertTokensToValues(self):
         widget = self._makeWidget()

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_setprefix.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_setprefix.py	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_setprefix.py	2006-12-20 23:34:35 UTC (rev 71638)
@@ -18,10 +18,11 @@
 import unittest
 
 from zope.app.form.browser import TextWidget
+from zope.app.form.browser.tests import support
 from zope.publisher.browser import TestRequest
 from zope.schema import Text
 
-class Test(unittest.TestCase):
+class Test(support.VerifyResults, unittest.TestCase):
 
     def setUp(self):
         field = Text(__name__ = 'foo')
@@ -38,22 +39,35 @@
         check_list = ('type="text"', 'id="spam.foo"', 'name="spam.foo"',
                       'value="Foo Value 2"', 'size="20"')
         self._widget.setRenderedValue(value)
-        self._verifyResult(self._widget(), check_list)
+        self.verifyResult(self._widget(), check_list)
         check_list = ('type="hidden"',) + check_list[1:-1]
-        self._verifyResult(self._widget.hidden(), check_list)
+        self.verifyResult(self._widget.hidden(), check_list)
         check_list = ('style="color: red"',) + check_list
         self._widget.extra = 'style="color: red"'
-        self._verifyResult(self._widget.hidden(), check_list)
+        self.verifyResult(self._widget.hidden(), check_list)
 
-    def _verifyResult(self, result, check_list):
-        for check in check_list:
-            self.assertNotEqual(-1, result.find(check),
-                                '"'+check+'" not found in "'+result+'"')
+class TestEmpty(support.VerifyResults, unittest.TestCase):
 
+    def setUp(self):
+        field = Text(__name__ = 'foo')
+        request = TestRequest()
+        request.form['foo'] = u'Foo Value'
+        self._widget = TextWidget(field, request)
+        self._widget.setPrefix('')
 
+    def testGetData(self):
+        self.assertEqual(self._widget.getInputValue(), u'Foo Value')
 
+    def testRender(self):
+        check_list = ('id="foo"', 'name="foo"')
+        self.verifyResult(self._widget(), check_list)
+        self.verifyResult(self._widget.hidden(), check_list)
+
 def test_suite():
-    return unittest.makeSuite(Test)
+    return unittest.TestSuite((
+        unittest.makeSuite(Test),
+        unittest.makeSuite(TestEmpty),
+        ))
 
 if __name__=='__main__':
     unittest.main(defaultTest='test_suite')

Modified: Zope3/trunk/src/zope/app/form/browser/widget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/widget.py	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/app/form/browser/widget.py	2006-12-20 23:34:35 UTC (rev 71638)
@@ -175,8 +175,12 @@
         >>> widget.error()
         ''
 
-    You can modify the prefix used to create the widget name as follows:
+    You can use 'setPrefix' to remove or modify the prefix used to create the
+    widget name as follows:
 
+        >>> widget.setPrefix('')
+        >>> widget.name
+        'foo'
         >>> widget.setPrefix('baz')
         >>> widget.name
         'baz.foo'

Modified: Zope3/trunk/src/zope/app/form/tests/test_widget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/tests/test_widget.py	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/app/form/tests/test_widget.py	2006-12-20 23:34:35 UTC (rev 71638)
@@ -80,6 +80,12 @@
         >>> widget.name
         'newprefix.Test'
 
+    Using the empty string as prefix leaves the prefix off entirely:
+
+        >>> widget.setPrefix('')
+        >>> widget.name
+        'Test'
+
     To configure a widget, call setRenderedValue with a value that the
     widget should display:
 

Modified: Zope3/trunk/src/zope/formlib/form.py
===================================================================
--- Zope3/trunk/src/zope/formlib/form.py	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/formlib/form.py	2006-12-20 23:34:35 UTC (rev 71638)
@@ -50,6 +50,15 @@
 
 _identifier = re.compile('[A-Za-z][a-zA-Z0-9_]*$')
 
+def expandPrefix(prefix):
+    """Expand prefix string by adding a trailing period if needed.
+
+    expandPrefix(p) should be used instead of p+'.' in most contexts.
+    """
+    if prefix and not prefix.endswith('.'):
+        return prefix + '.'
+    return prefix
+
 class FormField:
 
     interface.implements(interfaces.IFormField)
@@ -62,9 +71,7 @@
         if name is None:
             name = field.__name__
         assert name
-        if prefix:
-            name = prefix + '.' + name
-        self.__name__ = name
+        self.__name__ = expandPrefix(prefix) + name
         self.prefix = prefix
         if interface is None:
             interface = field.interface
@@ -170,12 +177,23 @@
 
     interface.implements(interfaces.IWidgets)
 
-    def __init__(self, widgets, prefix_length):
+    def __init__(self, widgets, prefix_length=None, prefix=None):
         self.__Widgets_widgets_items__ = widgets
         self.__Widgets_widgets_list__ = [w for (i, w) in widgets]
-        self.__Widgets_widgets_dict__ = dict(
-            [(w.name[prefix_length:], w) for (i, w) in widgets]
-            )
+        if prefix is None:
+            # BBB Allow old code using the prefix_length argument.
+            if prefix_length is None:
+                raise TypeError(
+                    "One of 'prefix_length' and 'prefix' is required."
+                    )
+            self.__Widgets_widgets_dict__ = dict(
+                [(w.name[prefix_length:], w) for (i, w) in widgets]
+                )
+        else:
+            prefix = expandPrefix(prefix)
+            self.__Widgets_widgets_dict__ = dict(
+                [(_widgetKey(w, prefix), w) for (i, w) in widgets]
+                )
 
     def __iter__(self):
         return iter(self.__Widgets_widgets_list__)
@@ -261,7 +279,7 @@
 
         prefix = form_prefix
         if form_field.prefix:
-            prefix += '.' + form_field.prefix
+            prefix = expandPrefix(prefix) + form_field.prefix
 
         widget.setPrefix(prefix)
 
@@ -278,7 +296,7 @@
 
         widgets.append((not readonly, widget))
 
-    return Widgets(widgets, len(form_prefix)+1)
+    return Widgets(widgets, prefix=form_prefix)
 
 def setUpInputWidgets(form_fields, form_prefix, context, request,
                       form=None, ignore_request=False):
@@ -289,7 +307,7 @@
 
         prefix = form_prefix
         if form_field.prefix:
-            prefix += '.' + form_field.prefix
+            prefix = expandPrefix(prefix) + form_field.prefix
 
         widget.setPrefix(prefix)
 
@@ -301,7 +319,7 @@
             widget.setRenderedValue(value)
 
         widgets.append((True, widget))
-    return Widgets(widgets, len(form_prefix)+1)
+    return Widgets(widgets, prefix=form_prefix)
 
 
 def _createWidget(form_field, field, request, iface):
@@ -313,7 +331,7 @@
 
 def getWidgetsData(widgets, form_prefix, data):
     errors = []
-    form_prefix += '.'
+    form_prefix = expandPrefix(form_prefix)
 
     for input, widget in widgets.__iter_input_and_widget__():
         if input and IInputWidget.providedBy(widget):
@@ -380,7 +398,7 @@
 
         prefix = form_prefix
         if form_field.prefix:
-            prefix += '.' + form_field.prefix
+            prefix = expandPrefix(prefix) + form_field.prefix
 
         widget.setPrefix(prefix)
 
@@ -391,7 +409,7 @@
 
         widgets.append((not readonly, widget))
 
-    return Widgets(widgets, len(form_prefix)+1)
+    return Widgets(widgets, prefix=form_prefix)
 
 def setUpDataWidgets(form_fields, form_prefix, context, request, data=(),
                      for_display=False, ignore_request=False):
@@ -407,7 +425,8 @@
 
         prefix = form_prefix
         if form_field.prefix:
-            prefix += '.' + form_field.prefix
+            prefix = expandPrefix(prefix) + form_field.prefix
+
         widget.setPrefix(prefix)
 
         if ((form_field.__name__ in data)
@@ -417,7 +436,7 @@
 
         widgets.append((not readonly, widget))
 
-    return Widgets(widgets, len(form_prefix)+1)
+    return Widgets(widgets, prefix=form_prefix)
 
 
 class NoInputData(interface.Invalid):
@@ -550,7 +569,7 @@
             else:
                 name = label.encode('hex')
 
-        self.__name__ = prefix + '.' + name
+        self.__name__ = expandPrefix(prefix) + name
 
         if data is None:
             data = {}
@@ -562,7 +581,7 @@
         result = self.__class__.__new__(self.__class__)
         result.__dict__.update(self.__dict__)
         result.form = form
-        result.__name__ = form.prefix + '.' + result.__name__
+        result.__name__ = expandPrefix(form.prefix) + result.__name__
         interface.alsoProvides(result, interfaces.IBoundAction)
         return result
 
@@ -570,13 +589,6 @@
         condition = self.condition
         return (condition is None) or condition(self.form, self)
 
-    def submitted(self):
-        if not self.available():
-            return False
-        form = self.form
-        name = "%s.%s" % (form.prefix, self.__name__)
-        return name in form.request.form
-
     def validate(self, data):
         if self.validator is not None:
             return self.validator(self.form, self, data)

Modified: Zope3/trunk/src/zope/formlib/form.txt
===================================================================
--- Zope3/trunk/src/zope/formlib/form.txt	2006-12-20 22:22:35 UTC (rev 71637)
+++ Zope3/trunk/src/zope/formlib/form.txt	2006-12-20 23:34:35 UTC (rev 71638)
@@ -1637,6 +1637,106 @@
 
 # TODO
 
+
+Omitting the form prefix
+------------------------
+
+For certain use cases (e.g. forms that post data to a different server whose
+software you do not control) it is important to be able to generate forms
+*without* a prefix. Using an empty string for the prefix omits it entirely.
+
+    >>> form_fields = form.Fields(IOrder).select('name')
+    >>> request = TestRequest()
+    >>> widgets = form.setUpWidgets(form_fields, '', None, request)
+    >>> print widgets['name']() # doctest: +NORMALIZE_WHITESPACE
+    <input class="textType" id="name" name="name" size="20"
+           type="text" value=""  />
+
+Of course, getting the widget data still works.
+
+    >>> request.form['name'] = 'foo'
+    >>> widgets = form.setUpWidgets(form_fields, '', None, request)
+    >>> data = {}
+    >>> form.getWidgetsData(widgets, '', data)
+    []
+    >>> data
+    {'name': u'foo'}
+
+And the value from the request is also visible in the rendered form.
+
+    >>> print widgets['name']() # doctest: +NORMALIZE_WHITESPACE
+    <input class="textType" id="name" name="name" size="20"
+           type="text" value="foo"  />
+
+The same is true when using the other setup*Widgets helpers.
+
+    >>> widgets = form.setUpInputWidgets(form_fields, '', None, request)
+    >>> print widgets['name']() # doctest: +NORMALIZE_WHITESPACE
+    <input class="textType" id="name" name="name" size="20"
+           type="text" value="foo"  />
+
+    >>> order = Order(42)
+    >>> widgets = form.setUpEditWidgets(form_fields, '', order, request)
+    >>> print widgets['name']() # doctest: +NORMALIZE_WHITESPACE
+    <input class="textType" id="name" name="name" size="20"
+           type="text" value="foo"  />
+
+    >>> widgets = form.setUpDataWidgets(form_fields, '', None, request)
+    >>> print widgets['name']() # doctest: +NORMALIZE_WHITESPACE
+    <input class="textType" id="name" name="name" size="20"
+           type="text" value="foo"  />
+
+Form actions have their own prefix in addition to the form prefix. This can be
+suppressed for each action by passing the empty string as the 'prefix'
+argument.
+
+    >>> class MyForm(form.Form):
+    ...
+    ...     prefix = ''
+    ...     form_fields = form.Fields()
+    ...
+    ...     @form.action('Button 1', name='button1')
+    ...     def handle_button1(self, action, data):
+    ...         self.status = 'Button 1 detected'
+    ...
+    ...     @form.action('Button 2', prefix='', name='button2')
+    ...     def handle_button2(self, action, data):
+    ...         self.status = 'Button 2 detected'
+    ...
+    >>> request = TestRequest()
+    >>> request.form['actions.button1'] = ''
+    >>> print MyForm(None, request)() # doctest: +NORMALIZE_WHITESPACE
+    Button 1 detected
+    <input type="submit" id="actions.button1" name="actions.button1"
+           value="Button 1" class="button" />
+    <input type="submit" id="button2" name="button2"
+           value="Button 2" class="button" />
+    >>> request = TestRequest()
+    >>> request.form['button2'] = ''
+    >>> print MyForm(None, request)() # doctest: +NORMALIZE_WHITESPACE
+    Button 2 detected
+    <input type="submit" id="actions.button1" name="actions.button1"
+           value="Button 1" class="button" />
+    <input type="submit" id="button2" name="button2"
+           value="Button 2" class="button" />
+
+It is also possible to keep the form prefix and just suppress the 'actions' prefix.
+
+    >>> class MyForm(form.Form):
+    ...
+    ...     form_fields = form.Fields()
+    ...
+    ...     @form.action('Button', prefix='', name='button')
+    ...     def handle_button(self, action, data):
+    ...         self.status = 'Button detected'
+    ...
+    >>> request = TestRequest()
+    >>> request.form['form.button'] = ''
+    >>> print MyForm(None, request)() # doctest: +NORMALIZE_WHITESPACE
+    Button detected
+    <input type="submit" id="form.button" name="form.button"
+           value="Button" class="button" />
+
 Additional Cases
 ================
 



More information about the Zope3-Checkins mailing list