[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/form/ zope.app.form.utility setUpEditWidgets, when given a source

Gary Poster gary at zope.com
Mon Mar 7 13:22:16 EST 2005


Log message for revision 29405:
  zope.app.form.utility setUpEditWidgets, when given a source
  that is security proxied and asked to set up edit widgets
  for fields that will raise Unauthorized when set, now raises
  Unauthorized by default, eliminating the lie that the form
  is usable.  It also accepts degradeDisplay and degradeInput
  optional keyword arguments to change the behavior to degrade
  unavailable input widgets to display widgets and to hide
  unavailable display widgets.  setUpDisplayWidgets now also
  accepts degradeDisplay.
  
  

Changed:
  U   Zope3/trunk/src/zope/app/form/browser/editwizard.py
  U   Zope3/trunk/src/zope/app/form/browser/form.txt
  U   Zope3/trunk/src/zope/app/form/browser/formview.py
  U   Zope3/trunk/src/zope/app/form/browser/objectwidget.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_directives.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_editwizardview.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_widgetdirective.py
  U   Zope3/trunk/src/zope/app/form/tests/test_utility.py
  A   Zope3/trunk/src/zope/app/form/tests/utils.py
  U   Zope3/trunk/src/zope/app/form/utility.py

-=-
Modified: Zope3/trunk/src/zope/app/form/browser/editwizard.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/editwizard.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/editwizard.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -35,7 +35,8 @@
 from submit import Next, Previous, Update
 from zope.app.form.interfaces import WidgetInputError, WidgetsError
 from zope.app.form.utility \
-        import setUpEditWidgets, getWidgetsData, applyWidgetsChanges
+        import setUpWidgets, getWidgetsData, applyWidgetsChanges
+from zope.app.form.interfaces import IInputWidget
 
 
 PaneNumber = 'CURRENT_PANE_IDX'
@@ -76,7 +77,7 @@
             self.storage = WizardStorage(self.fieldNames, adapted)
 
         # Add all our widgets as attributes on this view
-        setUpEditWidgets(self, self.schema, source=self.storage,
+        setUpWidgets(self, self.schema, IInputWidget, initial=self.storage,
                          names=self.fieldNames)
 
     def widgets(self):

Modified: Zope3/trunk/src/zope/app/form/browser/form.txt
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/form.txt	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/form.txt	2005-03-07 18:22:16 UTC (rev 29405)
@@ -45,7 +45,7 @@
   ...         name[1] = data['last']
 
 
-We now immitate the form-directive's behavior and create the view class:
+We now imitate the form-directive's behavior and create the view class:
 
   >>> from zope.app.form.browser.formview import FormView
   >>> View = type('View', bases=(DataHandler, FormView), 
@@ -86,7 +86,7 @@
   >>> name
   [u'Stephan', u'Richter']
 
-And that's pretty much all that there is to it. The rest behaves exactely like
+And that's pretty much all that there is to it. The rest behaves exactly like
 an edit form, from which the form view was derived. 
 
 Of course you can also completely overwrite the `update()` method like for the
@@ -204,4 +204,4 @@
 
 Now we need to clean up afterwards.
 
-  >>> del sys.modules['form']
\ No newline at end of file
+  >>> del sys.modules['form']

Modified: Zope3/trunk/src/zope/app/form/browser/formview.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/formview.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/formview.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -18,9 +18,9 @@
 __docformat__ = 'restructuredtext'
 from transaction import get_transaction
 
-from zope.app.form.interfaces import WidgetsError
+from zope.app.form.interfaces import WidgetsError, IInputWidget
 
-from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
+from zope.app.form.utility import setUpWidgets, applyWidgetsChanges
 from zope.app.form.browser.editview import EditView
 from zope.app.form.browser.submit import Update
 from zope.app.i18n import ZopeMessageIDFactory as _
@@ -54,8 +54,9 @@
     
     def _setUpWidgets(self):
         self.data = Data(self.getData())
-        setUpEditWidgets(
-            self, self.schema, source=self.data, names=self.fieldNames)
+        setUpWidgets(
+            self, self.schema, IInputWidget, initial=self.data, 
+            names=self.fieldNames)
 
     def update(self):
         if self.update_status is not None:
@@ -76,8 +77,8 @@
             else:
                 if changed:
                     self.setData(self.data)
-                setUpEditWidgets(
-                    self, self.schema, source=self.data,
+                setUpWidgets(
+                    self, self.schema, IInputWidget, initial=self.data,
                     ignoreStickyValues=True, names=self.fieldNames)
 
         self.update_status = status

Modified: Zope3/trunk/src/zope/app/form/browser/objectwidget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/objectwidget.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/objectwidget.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -23,7 +23,7 @@
 from zope.app.form.interfaces import IInputWidget
 from zope.app.form import InputWidget
 from zope.app.form.browser.widget import BrowserWidget
-from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
+from zope.app.form.utility import setUpWidgets, applyWidgetsChanges
 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
 
 
@@ -78,7 +78,7 @@
 
     def _setUpEditWidgets(self):
         # subwidgets need a new name
-        setUpEditWidgets(self, self.context.schema, source=self.context,
+        setUpWidgets(self, self.context.schema, IInputWidget,
                          prefix=self.name, names=self.names, 
                          context=self.context)
 
@@ -132,7 +132,9 @@
         # if there's changes, then store the new value on the content
         if changes:
             field.set(content, value)
-
+        # TODO: If value implements ILocation, set name to field name and
+        # parent to content
+        
         return changes
 
     def hasInput(self):

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_directives.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_directives.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_directives.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -33,8 +33,8 @@
 import zope.app.publisher.browser
 from zope.app.form.browser import TextWidget
 from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.app.form.tests import utils
 
-
 tests_path = os.path.join(
     os.path.dirname(zope.app.publisher.browser.__file__),
     'tests')
@@ -61,7 +61,8 @@
 class Ob(object):
     implements(IC)
 
-ob = Ob()
+unwrapped_ob = Ob()
+ob = utils.securityWrap(unwrapped_ob, IC)
 
 class ISomeWidget(Interface):
     displayWidth = Int(
@@ -110,7 +111,7 @@
               permission="zope.Public" />
             """)))
 
-        v = zapi.queryMultiAdapter((ob, request), name='add.html')
+        v = zapi.getMultiAdapter((ob, request), name='add.html')
         # expect to fail as standard macros are not configured
         self.assertRaises(TraversalError, v)
 
@@ -136,7 +137,7 @@
               permission="zope.Public" />
             """)))
 
-        v = zapi.queryMultiAdapter((ob, request), name='edit.html')
+        v = zapi.getMultiAdapter((ob, request), name='edit.html')
         # expect to fail as standard macros are not configured
         self.assertRaises(TraversalError, v)
 

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_editview.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -32,6 +32,8 @@
 from zope.app.form.browser.submit import Update
 from zope.component.exceptions import ComponentLookupError
 from zope.app.form.interfaces import IInputWidget
+from zope.app.form.tests import utils
+from zope.app.location.interfaces import ILocation
 
 class I(Interface):
     foo = TextLine(title=u"Foo")
@@ -50,6 +52,7 @@
     bar = u"c bar"
     a   = u"c a"
     b   = u"c b"
+    __Security_checker__ = utils.SchemaChecker(I)
 
     _baz = u"c baz"
     def getbaz(self): return self._baz
@@ -64,6 +67,7 @@
 
 class Foo(object):
     implements(IFoo)
+    __Security_checker__ = utils.SchemaChecker(IFoo)
 
     foo = u'Foo foo'
     
@@ -73,17 +77,12 @@
     foo = u'Foo foo'
 
     def __conform__(self, interface):
-        # fake proxied adapter (attention only read proxy)
-        from zope.security.checker import InterfaceChecker
-        from zope.security.checker import ProxyFactory
-        
         if interface is IBar:
-            checker = InterfaceChecker(IBar)
-            return ProxyFactory(OtherFooBarAdapter(self), checker)
+            return OtherFooBarAdapter(self)
 
             
 class FooBarAdapter(object):
-    implements(IBar)
+    implements(IBar, ILocation)
     __used_for__ = IFoo
 
     def __init__(self, context):
@@ -94,6 +93,8 @@
 
     bar = property(getbar, setbar)
     
+    __Security_checker__ = utils.SchemaChecker(IBar)
+    
 class OtherFooBarAdapter(FooBarAdapter):
     pass
 

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_editwizardview.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_editwizardview.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_editwizardview.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -27,8 +27,9 @@
 from zope.app.form.browser.editwizard import EditWizardView
 from zope.app.form.browser import TextWidget
 from zope.app.form.interfaces import IInputWidget
+from zope.app.location.interfaces import ILocation
+from zope.app.form.tests import utils
 
-
 class I(Interface):
     foo = TextLine(title=u"Foo")
     bar = TextLine(title=u"Bar")
@@ -43,11 +44,11 @@
 
 class C(object):
     implements(I)
+    __Security_checker__ = utils.SchemaChecker(I)
     foo = u"c foo"
     bar = u"c bar"
     a   = u"c a"
     b   = u"c b"
-
     _baz = u"c baz"
     def getbaz(self): return self._baz
     def setbaz(self, v): self._baz = v
@@ -61,7 +62,7 @@
 
 class Foo(object):
     implements(IFoo)
-
+    __Security_checker__ = utils.SchemaChecker(IFoo)
     foo = u'Foo foo'
     
 class ConformFoo(object):
@@ -70,16 +71,14 @@
     foo = u'Foo foo'
 
     def __conform__(self, interface):
-        # fake proxied adapter (attention only read proxy)        
         if interface is IBar:
-            checker = InterfaceChecker(IBar)
-            return ProxyFactory(OtherFooBarAdapter(self), checker)
+            return OtherFooBarAdapter(self)
 
             
 class FooBarAdapter(object):
-    implements(IBar)
+    implements(IBar, ILocation)
     __used_for__ = IFoo
-
+    __Security_checker__ = utils.SchemaChecker(IBar)
     def __init__(self, context):
         self.context = context
 

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_widgetdirective.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_widgetdirective.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_widgetdirective.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -25,6 +25,7 @@
 import zope.app.container.interfaces
 import zope.app.form.browser.interfaces
 import zope.app.form.interfaces
+from zope.app.form.tests import utils
 import zope.app.testing.placelesssetup
 
 from zope.app import zapi
@@ -44,6 +45,7 @@
 class Content(object):
 
     zope.interface.implements(IContent)
+    __Security_checker__ = utils.SchemaChecker(IContent)
 
     __parent__ = None
     __name__ = "sample-content"

Modified: Zope3/trunk/src/zope/app/form/tests/test_utility.py
===================================================================
--- Zope3/trunk/src/zope/app/form/tests/test_utility.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/tests/test_utility.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -21,9 +21,11 @@
 from zope.component.interfaces import IViewFactory
 from zope.component.exceptions import ComponentLookupError
 from zope.publisher.browser import TestRequest
-from zope.security.interfaces import ForbiddenAttribute
+import zope.security.checker
+from zope.security.interfaces import ForbiddenAttribute, Unauthorized
+import zope.security.checker
 
-from zope.schema import Field, Int
+from zope.schema import Field, Int, accessors
 from zope.schema.interfaces import IField, IInt
 
 from zope.app.testing import ztapi, placelesssetup
@@ -37,6 +39,8 @@
 from zope.app.form.utility import getWidgetsData, viewHasInput
 from zope.app.form.utility import applyWidgetsChanges
 
+from zope.app.form.tests import utils
+
 request = TestRequest()
 
 class IFoo(IField):
@@ -60,9 +64,11 @@
 class IContent(Interface):
     foo = Foo()
     bar = Bar()
+
     
 class Content(object):
     implements(IContent)
+    __Security_checker__ = utils.SchemaChecker(IContent)
     foo = 'Foo'
 
 class IFooWidget(IWidget):
@@ -86,6 +92,24 @@
     def getRenderedValue(self): return self._data # exposes _data for testing
     def renderedValueSet(self): return self._renderedValueSet() # for testing
 
+class IExtendedContent(IContent):
+    getBaz, setBaz = accessors(Baz())
+    getAnotherBaz, setAnotherBaz = accessors(Baz())
+    shazam = Foo()
+
+class ExtendedContent(Content):
+    implements(IExtendedContent)
+    _baz = _anotherbaz = shazam = None
+    def getBaz(self): return self._baz
+    def setBaz(self, value): self._baz = value
+    def getAnotherBaz(self): return self._anotherbaz
+    def setAnotherBaz(self, value): self._anotherbaz = value
+
+extended_checker = utils.DummyChecker(
+        {'foo':True, 'bar': True, 'getBaz': True, 'setBaz': True, 
+         'getAnotherBaz': True, 'setAnotherBaz': False, 'shazam': False},
+        {'foo':True, 'bar': False, 'shazam': True})
+
 def setUp():
     """Setup for tests."""
     placelesssetup.setUp()
@@ -607,6 +631,7 @@
         A call to setUpEditWidgets with the view:
             
             >>> setUpEditWidgets(view, IContent)
+            ['foo', 'bar']
             
         configures the view with widgets that accept input for the context 
         field values:
@@ -628,6 +653,7 @@
             >>> source.foo = 'abc2'
             >>> source.bar = 'def2'
             >>> setUpEditWidgets(view, IContent, source=source)
+            ['foo', 'bar']
             >>> view.foo_widget.getRenderedValue()
             'abc2'
             >>> view.bar_widget.getRenderedValue()
@@ -643,10 +669,115 @@
             >>> IContent['foo'].readonly = True
             >>> delattr(view, 'foo_widget')
             >>> setUpEditWidgets(view, IContent)
+            ['foo', 'bar']
             >>> isinstance(view.foo_widget, DisplayWidget)
             True
             >>> IContent['foo'].readonly = save  # restore readonly value
         
+        By default, setUpEditWidgets raises Unauthorized if it is asked to
+        set up a field to which the user does not have permission to
+        access or to change.  In the definition of the ExtendedContent
+        interface, notice the __Security_checker__ attribute, which stubs
+        out a checker that allows the user to view the bar attribute,
+        but not set it, and call getAnotherBaz but not setAnotherBaz.
+        
+            >>> view.context = context = zope.security.checker.Proxy(
+            ...     ExtendedContent(), extended_checker)
+            >>> setUpEditWidgets(view, IExtendedContent, names=['bar'])
+            ... # can' write to bar
+            Traceback (most recent call last):
+            ...
+            Unauthorized: bar
+            >>> setUpEditWidgets(
+            ... view, IExtendedContent, names=['getAnotherBaz'])
+            ... # can't access the setter, setAnotherBaz
+            Traceback (most recent call last):
+            ...
+            Unauthorized: setAnotherBaz
+            >>> setUpEditWidgets(
+            ... view, IExtendedContent, names=['shazam'])
+            ... # can't even access shazam
+            Traceback (most recent call last):
+            ...
+            Unauthorized
+        
+        Two optional flags can change this behavior.  degradeDisplay=True 
+        causes the form machinery to skip fields silently that the user may
+        not access.  In this case, the return value of setUpEditWidgets--
+        a list of the field names set up--will be different that the names
+        provided to the function.
+        
+            >>> delattr(view, 'foo_widget')
+            >>> delattr(view, 'bar_widget')
+            >>> ztapi.browserViewProviding(IBaz, InputWidget, IInputWidget)
+            >>> setUpEditWidgets(
+            ...     view, IExtendedContent, names=['foo', 'shazam', 'getBaz'],
+            ...     degradeDisplay=True)
+            ['foo', 'getBaz']
+            >>> IInputWidget.providedBy(view.foo_widget)
+            True
+            >>> IInputWidget.providedBy(view.getBaz_widget)
+            True
+            >>> view.shazam_widget
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'BrowserView' object has no attribute 'shazam_widget'
+        
+        Similarly, degradeInput=True causes the function to degrade to
+        display widgets for any fields that the current user cannot change,
+        but can see.
+        
+            >>> delattr(view, 'foo_widget')
+            >>> delattr(view, 'getBaz_widget')
+            >>> ztapi.browserViewProviding(IBar, DisplayWidget, IDisplayWidget)
+            >>> ztapi.browserViewProviding(IBaz, DisplayWidget, IDisplayWidget)
+            >>> setUpEditWidgets(
+            ...     view, IExtendedContent, 
+            ...     names=['foo', 'bar', 'getBaz', 'getAnotherBaz'],
+            ...     degradeInput=True)
+            ['foo', 'bar', 'getBaz', 'getAnotherBaz']
+            >>> IInputWidget.providedBy(view.foo_widget)
+            True
+            >>> IDisplayWidget.providedBy(view.bar_widget)
+            True
+            >>> IInputWidget.providedBy(view.getBaz_widget)
+            True
+            >>> IDisplayWidget.providedBy(view.getAnotherBaz_widget)
+            True
+        
+        Note that if the user cannot view the current value then they cannot
+        view the input widget.  The two flags can then, of course, be used 
+        together.
+        
+            >>> delattr(view, 'foo_widget')
+            >>> delattr(view, 'bar_widget')
+            >>> delattr(view, 'getBaz_widget')
+            >>> delattr(view, 'getAnotherBaz_widget')
+            >>> setUpEditWidgets(
+            ...     view, IExtendedContent, 
+            ...     names=['foo', 'bar', 'shazam', 'getBaz', 'getAnotherBaz'],
+            ...     degradeInput=True)
+            Traceback (most recent call last):
+            ...
+            Unauthorized
+            >>> setUpEditWidgets(
+            ...     view, IExtendedContent, 
+            ...     names=['foo', 'bar', 'shazam', 'getBaz', 'getAnotherBaz'],
+            ...     degradeInput=True, degradeDisplay=True)
+            ['foo', 'bar', 'getBaz', 'getAnotherBaz']
+            >>> IInputWidget.providedBy(view.foo_widget)
+            True
+            >>> IDisplayWidget.providedBy(view.bar_widget)
+            True
+            >>> IInputWidget.providedBy(view.getBaz_widget)
+            True
+            >>> IDisplayWidget.providedBy(view.getAnotherBaz_widget)
+            True
+            >>> view.shazam_widget
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'BrowserView' object has no attribute 'shazam_widget'
+        
         >>> tearDown()
         """
         
@@ -659,7 +790,7 @@
         The function looks up widgets of type IDisplayWidget for the specified
         schema.
         
-        We'll first create and register widgets for the schame fields
+        We'll first create and register widgets for the schema fields
         we want to edit:
             
             >>> class DisplayWidget(Widget):
@@ -675,9 +806,10 @@
             >>> context.bar = 'def'
             >>> view = BrowserView(context, request)
             
-        A call to setUpEditWidgets with the view:
+        A call to setUpDisplayWidgets with the view:
             
             >>> setUpDisplayWidgets(view, IContent)
+            ['foo', 'bar']
             
         configures the view with widgets that display the context fields:
             
@@ -698,11 +830,44 @@
             >>> source.foo = 'abc2'
             >>> source.bar = 'def2'
             >>> setUpDisplayWidgets(view, IContent, source=source)
+            ['foo', 'bar']
             >>> view.foo_widget.getRenderedValue()
             'abc2'
             >>> view.bar_widget.getRenderedValue()
             'def2'
         
+        Also like setUpEditWidgets, the degradeDisplay flag allows widgets
+        to silently disappear if they are unavailable.
+        
+            >>> view.context = context = zope.security.checker.Proxy(
+            ...     ExtendedContent(), extended_checker)
+            >>> delattr(view, 'foo_widget')
+            >>> delattr(view, 'bar_widget')
+            >>> ztapi.browserViewProviding(IBaz, DisplayWidget, IDisplayWidget)
+            >>> setUpDisplayWidgets(
+            ...     view, IExtendedContent, 
+            ...     names=['foo', 'bar', 'shazam', 'getBaz', 'getAnotherBaz'])
+            Traceback (most recent call last):
+            ...
+            Unauthorized
+            >>> setUpDisplayWidgets(
+            ...     view, IExtendedContent, 
+            ...     names=['foo', 'bar', 'shazam', 'getBaz', 'getAnotherBaz'],
+            ...     degradeDisplay=True)
+            ['foo', 'bar', 'getBaz', 'getAnotherBaz']
+            >>> IDisplayWidget.providedBy(view.foo_widget)
+            True
+            >>> IDisplayWidget.providedBy(view.bar_widget)
+            True
+            >>> IDisplayWidget.providedBy(view.getBaz_widget)
+            True
+            >>> IDisplayWidget.providedBy(view.getAnotherBaz_widget)
+            True
+            >>> view.shazam_widget
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'BrowserView' object has no attribute 'shazam_widget'
+        
         >>> tearDown()
         """
         
@@ -728,6 +893,7 @@
             >>> ztapi.browserViewProviding(IBar, InputWidget, IInputWidget)
             >>> view = BrowserView(Content(), request)
             >>> setUpEditWidgets(view, IContent)
+            ['foo', 'bar']
             
         Because InputWidget is configured to not have input by default, the
         view does not have input:
@@ -776,7 +942,9 @@
             
             >>> context = Content()
             >>> view = BrowserView(context, request)
-            >>> setUpEditWidgets(view, IContent, names=('foo',))
+            >>> setUpEditWidgets(
+            ...     view, IContent, context=context, names=('foo',))
+            ['foo']
             
         We now specify new widget input and apply the changes: 
 
@@ -813,12 +981,16 @@
             AttributeError: 'BrowserView' object has no attribute 'foo_widget'
 
         When applyWidgetsChanges is called with multiple form
-        fields, some with valid data and some with invalid data, none
-        of the data is applied:
+        fields, some with valid data and some with invalid data, 
+        *changes may be applied*.  For instance, below see that context.foo
+        changes from 'Foo' to 'a' even though trying to change context.bar
+        fails.  Generally, ZODB transactional behavior is expected to
+        correct this sort of problem.
 
             >>> context = Content()
             >>> view = BrowserView(context, request)
             >>> setUpEditWidgets(view, IContent, names=('foo', 'bar'))
+            ['foo', 'bar']
             >>> view.foo_widget.input = 'a'
             >>> view.bar_widget.input = 'b'
             >>> view.bar_widget.valid = False
@@ -863,6 +1035,7 @@
         
             >>> view = BrowserView(Content(), request)
             >>> setUpEditWidgets(view, IContent)
+            ['foo', 'bar']
             
         The simplest form of getWidgetsData requires a view and a schema:
             

Added: Zope3/trunk/src/zope/app/form/tests/utils.py
===================================================================
--- Zope3/trunk/src/zope/app/form/tests/utils.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/tests/utils.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -0,0 +1,81 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Utilities for testing form machinery
+
+$Id$
+"""
+from zope.interface.interfaces import IMethod
+from zope.security.interfaces import ForbiddenAttribute, Unauthorized
+import zope.security.checker
+from zope.schema import getFieldsInOrder
+
+class DummyChecker(object):
+    """a checker for testing that requires explicit declarations
+    
+    requires explicit declaration of what is and is not authorized; does not
+    require testing machinery to set up an interaction or a request.
+    
+    To instantiate, pass two dictionaries, the first for get access attribute
+    protection, and the second for set access attribute protection.  keys
+    should be the attribute names, and values should be boolean True and
+    False, where True indicates authorized and False, unauthorized.  Any
+    attributes that are not explicitly set and, in the case of get protection,
+    are not in the zope.security.checker._available_by_default list,
+    will cause ForbiddenAttribute to be raised when the name is checked, as
+    with the real zope.security checkers.
+    """
+    def __init__(self, getnames, setnames):
+        self.getnames = getnames
+        self.setnames = setnames
+    def check_getattr(self, obj, name):
+        if name not in zope.security.checker._available_by_default:
+            try:
+                val = self.getnames[name]
+            except KeyError:
+                raise ForbiddenAttribute
+            else:
+                if not val:
+                    raise Unauthorized
+    check = check_getattr
+    def check_setattr(self, obj, name):
+        try:
+            val = self.setnames[name]
+        except KeyError:
+            raise ForbiddenAttribute
+        else:
+            if not val:
+                raise Unauthorized
+    def proxy(self, value):
+        return value
+
+def SchemaChecker(schema, readonly=False):
+    """returns a checker that allows read and write access to fields in schema.
+    """
+    get = {}
+    set = {}
+    for name, field in getFieldsInOrder(schema):
+        get[name] = True
+        if not field.readonly:
+            if IMethod.providedBy(field):
+                get[field.writer.__name__] = True
+            else:
+                set[name] = True
+    if readonly:
+        for nm in set:
+            set[nm] = False
+    return DummyChecker(get, set)
+
+def securityWrap(ob, schema, readonly=False):
+    return zope.security.checker.Proxy(ob, SchemaChecker(schema, readonly))
+    


Property changes on: Zope3/trunk/src/zope/app/form/tests/utils.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/app/form/utility.py
===================================================================
--- Zope3/trunk/src/zope/app/form/utility.py	2005-03-07 18:17:24 UTC (rev 29404)
+++ Zope3/trunk/src/zope/app/form/utility.py	2005-03-07 18:22:16 UTC (rev 29405)
@@ -34,7 +34,11 @@
 """
 __docformat__ = 'restructuredtext'
 
-from zope.security.interfaces import ForbiddenAttribute
+from zope import security
+from zope.security.proxy import Proxy
+from zope.proxy import isProxy
+from zope.interface.interfaces import IMethod
+from zope.security.interfaces import ForbiddenAttribute, Unauthorized
 from zope.schema import getFieldsInOrder
 from zope.app import zapi
 from zope.app.form.interfaces import IWidget
@@ -114,6 +118,9 @@
 def setUpWidgets(view, schema, viewType, prefix=None, ignoreStickyValues=False,
                  initial={}, names=None, context=None):
     """Sets up widgets for the fields defined by a `schema`.
+    
+    Appropriate for collecting input without a current object implementing
+    the schema (such as an add form).
 
     `view` is the view that will be configured with widgets. 
 
@@ -146,7 +153,8 @@
                     context=context)
 
 def setUpEditWidgets(view, schema, source=None, prefix=None,
-                     ignoreStickyValues=False, names=None, context=None):
+                     ignoreStickyValues=False, names=None, context=None,
+                     degradeInput=False, degradeDisplay=False):
     """Sets up widgets to collect input on a view.
     
     See `setUpWidgets` for details on `view`, `schema`, `prefix`,
@@ -154,12 +162,72 @@
     
     `source`, if specified, is an object from which initial widget values are
     read. If source is not specified, the view context is used as the source.
+    
+    `degradeInput` is a flag that changes the behavior when a user does not
+    have permission to edit a field in the names.  By default, the function
+    raises Unauthorized.  If degradeInput is True, the field is changed to
+    an IDisplayWidget.
+    
+    `degradeDisplay` is a flag that changes the behavior when a user does not
+    have permission to access a field in the names.  By default, the function
+    raises Unauthorized.  If degradeDisplay is True, the field is removed from
+    the form.
+    
+    Returns a list of names, equal to or a subset of the names that were 
+    supposed to be drawn, with uninitialized undrawn fields missing.
     """
-    _setUpFormWidgets(view, schema, source, prefix, ignoreStickyValues,
-                      names, context, IDisplayWidget, IInputWidget)
+    if context is None:
+        context = view.context
+    if source is None:
+        source = view.context
+    security_proxied = isProxy(source, Proxy)
+    res_names = []
+    for name, field in _fieldlist(names, schema):
+        try:
+            value = field.get(source)
+        except ForbiddenAttribute:
+            raise
+        except AttributeError, v:
+            value = no_value
+        except Unauthorized:
+            if degradeDisplay:
+                continue
+            else:
+                raise
+        if field.readonly:
+            viewType = IDisplayWidget
+        else:
+            if security_proxied:
+                is_accessor = IMethod.providedBy(field)
+                if is_accessor:
+                    set_name = field.writer.__name__
+                    authorized = security.canAccess(source, set_name)
+                else:
+                    set_name = name
+                    authorized = security.canWrite(source, name)
+                if not authorized:
+                    if degradeInput:
+                        viewType = IDisplayWidget
+                    else:
+                        raise Unauthorized(set_name)
+                else:
+                    viewType = IInputWidget
+            else:
+                # if object is not security proxied, might be a standard
+                # adapter without a registered checker.  If the feature of
+                # paying attention to the users ability to actually set a
+                # field is decided to be a must-have for the form machinery,
+                # then we ought to change this case to have a deprecation
+                # warning.
+                viewType = IInputWidget
+        setUpWidget(view, name, field, viewType, value, prefix,
+                    ignoreStickyValues, context)
+        res_names.append(name)
+    return res_names
 
 def setUpDisplayWidgets(view, schema, source=None, prefix=None, 
-                        ignoreStickyValues=False, names=None, context=None):
+                        ignoreStickyValues=False, names=None, context=None,
+                        degradeDisplay=False):
     """Sets up widgets to display field values on a view.
     
     See `setUpWidgets` for details on `view`, `schema`, `prefix`,
@@ -167,30 +235,36 @@
     
     `source`, if specified, is an object from which initial widget values are
     read. If source is not specified, the view context is used as the source.
+    
+    `degradeDisplay` is a flag that changes the behavior when a user does not
+    have permission to access a field in the names.  By default, the function
+    raises Unauthorized.  If degradeDisplay is True, the field is removed from
+    the form.
+    
+    Returns a list of names, equal to or a subset of the names that were 
+    supposed to be drawn, with uninitialized undrawn fields missing.
     """
-    _setUpFormWidgets(view, schema, source, prefix, ignoreStickyValues,
-                      names, context, IDisplayWidget, IDisplayWidget)
-
-def _setUpFormWidgets(view, schema, source, prefix, ignoreStickyValues,
-                      names, context, displayType, inputType):
-    """A helper function used by `setUpDisplayWidget` and `setUpEditWidget`."""
     if context is None:
         context = view.context
     if source is None:
         source = view.context
+    res_names = []
     for name, field in _fieldlist(names, schema):
-        if field.readonly:
-            viewType = displayType
-        else:
-            viewType = inputType
         try:
             value = field.get(source)
         except ForbiddenAttribute:
             raise
         except AttributeError, v:
             value = no_value
-        setUpWidget(view, name, field, viewType, value, prefix,
+        except Unauthorized:
+            if degradeDisplay:
+                continue
+            else:
+                raise
+        setUpWidget(view, name, field, IDisplayWidget, value, prefix,
                     ignoreStickyValues, context)
+        res_names.append(name)
+    return res_names
 
 def viewHasInput(view, schema, names=None):
     """Returns ``True`` if the any of the view's widgets contain user input.



More information about the Zope3-Checkins mailing list