[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