[Checkins] SVN: z3c.form/trunk/src/z3c/form/ Implemented a few new
features based on user requests:
Stephan Richter
srichter at cosmos.phy.tufts.edu
Wed Jun 20 13:46:19 EDT 2007
Log message for revision 76848:
Implemented a few new features based on user requests:
- Feature: Originally, when an attribute access failed in Unauthorized or
ForbiddenAttribute exceptions, they were ignored as if the attribute would
have no value. Now those errors are propagated and the system will fail
providing the developer with more feedback. The datamanager also grew a new
``query()`` method that returns always a default and the ``get()`` method
propagates any exceptions.
- Feature: When writing to a field is forbidden due to insufficient
priviledges, the resulting widget mode will be set to "display". This
behavior can be overridden by explicitely specifying the mode on a
field.
Changed:
U z3c.form/trunk/src/z3c/form/README.txt
U z3c.form/trunk/src/z3c/form/datamanager.py
U z3c.form/trunk/src/z3c/form/datamanager.txt
U z3c.form/trunk/src/z3c/form/field.py
U z3c.form/trunk/src/z3c/form/field.txt
U z3c.form/trunk/src/z3c/form/form.txt
U z3c.form/trunk/src/z3c/form/widget.txt
-=-
Modified: z3c.form/trunk/src/z3c/form/README.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/README.txt 2007-06-20 17:33:27 UTC (rev 76847)
+++ z3c.form/trunk/src/z3c/form/README.txt 2007-06-20 17:46:18 UTC (rev 76848)
@@ -76,7 +76,12 @@
The ``util`` module provides several helper functions and classes. The
components not tested otherwise are explained in this file.
+- ``adding.txt`` [informative]
+ This module provides a base class for add forms that work with the
+ ``IAdding`` interface.
+
+
Browser Documentation
---------------------
Modified: z3c.form/trunk/src/z3c/form/datamanager.py
===================================================================
--- z3c.form/trunk/src/z3c/form/datamanager.py 2007-06-20 17:33:27 UTC (rev 76847)
+++ z3c.form/trunk/src/z3c/form/datamanager.py 2007-06-20 17:46:18 UTC (rev 76848)
@@ -20,8 +20,7 @@
import zope.interface
import zope.component
import zope.schema
-from zope.security.checker import canAccess
-from zope.security.checker import canWrite
+from zope.security.checker import canAccess, canWrite, Proxy
from z3c.form import interfaces
@@ -40,14 +39,21 @@
self.context = context
self.field = field
- def get(self, default=interfaces.NOVALUE):
+ def get(self):
"""See z3c.form.interfaces.IDataManager"""
# get the right adapter or context
context = self.context
if self.field.interface is not None:
context = self.field.interface(context)
- return getattr(context, self.field.__name__, default)
+ return getattr(context, self.field.__name__)
+ def query(self, default=interfaces.NOVALUE):
+ """See z3c.form.interfaces.IDataManager"""
+ try:
+ return self.get()
+ except AttributeError:
+ return default
+
def set(self, value):
"""See z3c.form.interfaces.IDataManager"""
if self.field.readonly:
@@ -62,13 +68,16 @@
def canAccess(self):
"""See z3c.form.interfaces.IDataManager"""
- return canAccess(self.context, self.field.__name__)
+ if isinstance(self.context, Proxy):
+ return canAccess(self.context, self.field.__name__)
+ return True
def canWrite(self):
"""See z3c.form.interfaces.IDataManager"""
- return canWrite(self.context, self.field.__name__)
+ if isinstance(self.context, Proxy):
+ return canWrite(self.context, self.field.__name__)
+ return True
-
class DictionaryField(DataManager):
"""Dictionary field."""
zope.component.adapts(
@@ -80,8 +89,12 @@
self.data = data
self.field = field
- def get(self, default=interfaces.NOVALUE):
+ def get(self):
"""See z3c.form.interfaces.IDataManager"""
+ return self.data[self.field.__name__]
+
+ def query(self, default=interfaces.NOVALUE):
+ """See z3c.form.interfaces.IDataManager"""
return self.data.get(self.field.__name__, default)
def set(self, value):
Modified: z3c.form/trunk/src/z3c/form/datamanager.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/datamanager.txt 2007-06-20 17:33:27 UTC (rev 76847)
+++ z3c.form/trunk/src/z3c/form/datamanager.txt 2007-06-20 17:46:18 UTC (rev 76848)
@@ -43,6 +43,8 @@
... name = zope.schema.TextLine(
... title=u'Name',
... default=u'<no name>')
+ ... phone = zope.schema.TextLine(
+ ... title=u'Phone')
>>> class Person(object):
... zope.interface.implements(IPerson)
@@ -57,11 +59,14 @@
>>> nameDm = datamanager.AttributeField(stephan, IPerson['name'])
The data manager consists of a few simple methods to accomplish its
-purpose. Getting the value is done using the ``get()`` method:
+purpose. Getting the value is done using the ``get()`` or ``query()`` method:
>>> nameDm.get()
u'Stephan Richter'
+ >>> nameDm.query()
+ u'Stephan Richter'
+
The value can be set using ``set()``:
>>> nameDm.set(u'Stephan "Caveman" Richter')
@@ -71,10 +76,33 @@
>>> stephan.name
u'Stephan "Caveman" Richter'
+If an attribute is not available, ``get()`` fails and ``query()`` returns a
+default value:
+
+ >>> phoneDm = datamanager.AttributeField(stephan, IPerson['phone'])
+
+ >>> phoneDm.get()
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'Person' object has no attribute 'phone'
+
+ >>> phoneDm.query()
+ <NOVALUE>
+ >>> phoneDm.query('nothing')
+ 'nothing'
+
A final feature that is supported by the data manager is the check whether a
-value can be accessed and written. To demonstrate the feature, we first have
-to provide security declarations for our person:
+value can be accessed and written. When the context is not security proxied,
+both, accessing and writing, is allowed:
+ >>> nameDm.canAccess()
+ True
+ >>> nameDm.canWrite()
+ True
+
+To demonstrate the behavior for a security-proxied component, we first have to
+provide security declarations for our person:
+
>>> from zope.security.management import endInteraction
>>> from zope.security.management import newInteraction
>>> from zope.security.management import setSecurityPolicy
@@ -216,6 +244,11 @@
Let's now access the name:
>>> nameDm.get()
+ Traceback (most recent call last):
+ ...
+ KeyError: 'name'
+
+ >>> nameDm.query()
<NOVALUE>
Initially we get the default value (as specified in the field), since the
Modified: z3c.form/trunk/src/z3c/form/field.py
===================================================================
--- z3c.form/trunk/src/z3c/form/field.py 2007-06-20 17:33:27 UTC (rev 76847)
+++ z3c.form/trunk/src/z3c/form/field.py 2007-06-20 17:46:18 UTC (rev 76848)
@@ -217,12 +217,18 @@
# Walk through each field, making a widget out of it
for field in self.form.fields.values():
# Step 1: Determine the mode of the widget.
- mode = field.mode
- if mode is None:
- if field.field.readonly and not self.ignoreReadonly:
+ mode = self.mode
+ if field.mode is not None:
+ mode = field.mode
+ elif field.field.readonly and not self.ignoreReadonly:
mode = interfaces.DISPLAY_MODE
- else:
- mode = self.mode
+ elif not self.ignoreContext:
+ # If we do not have enough permissions to write to the
+ # attribute, then switch to display mode.
+ dm = zope.component.getMultiAdapter(
+ (self.content, field.field), interfaces.IDataManager)
+ if not dm.canWrite():
+ mode = interfaces.DISPLAY_MODE
# Step 2: Get the widget for the given field.
factory = field.widgetFactory.get(mode)
if factory is not None:
Modified: z3c.form/trunk/src/z3c/form/field.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/field.txt 2007-06-20 17:33:27 UTC (rev 76847)
+++ z3c.form/trunk/src/z3c/form/field.txt 2007-06-20 17:46:18 UTC (rev 76848)
@@ -343,23 +343,28 @@
>>> from z3c.form import field
- >>> class AddPerson(object):
+ >>> class PersonForm(object):
... prefix = 'form.'
... fields = field.Fields(IPerson)
- >>> addPerson = AddPerson()
+ >>> personForm = PersonForm()
-For more details on how to define fields within a form, see ``field.txt``. We
+For more details on how to define fields within a form, see ``form.txt``. We
can now create the fields widget manager. It's discriminators are the form for
which the widgets are created, the request, and the context that is being
-manipulated. Since we are developing an add-form the context is ``None``.
+manipulated. In the simplest case the context is ``None`` and ignored, as it
+is true for an add form.
>>> from z3c.form.testing import TestRequest
>>> request = TestRequest()
>>> context = object()
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
+
+Widget Mapping
+~~~~~~~~~~~~~~
+
The main resposibility of the manager is to provide the ``IEnumerableMapping``
interface and an ``update()`` method. Initially the mapping, going from widget
id to widget value, is empty:
@@ -433,6 +438,10 @@
>>> len(manager)
3
+
+Properties of widgets within a manager
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
When a widget is added to the widget manager, it is located:
>>> lname = manager['lastName']
@@ -449,6 +458,10 @@
>>> lname.context is context
True
+
+Determination of the widget mode
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
By default, all widgets will also assume the mode of the manager:
>>> manager['lastName'].mode
@@ -480,8 +493,8 @@
In the second case, the last name will inherit the mode from the widget
manager, while the first name will want to use a display wdget:
- >>> addPerson.fields = field.Fields(IPerson).select('lastName')
- >>> addPerson.fields += field.Fields(
+ >>> personForm.fields = field.Fields(IPerson).select('lastName')
+ >>> personForm.fields += field.Fields(
... IPerson, mode=interfaces.DISPLAY_MODE).select('firstName')
>>> manager.mode = interfaces.INPUT_MODE
@@ -492,6 +505,83 @@
>>> manager['firstName'].mode
'display'
+In a third case, the widget will be shown in display mode, if the attribute of
+the context is not writable. Clearly this can never occur in add forms, since
+there the context is ignored, but is an important use case in edit forms.
+
+Thus we need an implementation of the ``IPerson`` interface including some
+security declarations:
+
+ >>> from zope.security import checker
+
+ >>> class Person(object):
+ ... zope.interface.implements(IPerson)
+ ...
+ ... def __init__(self, firstName, lastName):
+ ... self.id = firstName[0].lower() + lastName.lower()
+ ... self.firstName = firstName
+ ... self.lastName = lastName
+
+ >>> PersonChecker = checker.Checker(
+ ... get_permissions = {'id': checker.CheckerPublic,
+ ... 'firstName': checker.CheckerPublic,
+ ... 'lastName': checker.CheckerPublic},
+ ... set_permissions = {'firstName': 'test.Edit',
+ ... 'lastName': checker.CheckerPublic}
+ ... )
+
+ >>> srichter = checker.ProxyFactory(
+ ... Person(u'Stephan', u'Richter'), PersonChecker)
+
+In this case, the last name is always editable but for the first name the user
+will need the edit "test.Edit" permission.
+
+We also need to register the data manager and setup a new security policy:
+
+ >>> from z3c.form import datamanager
+ >>> zope.component.provideAdapter(datamanager.AttributeField)
+
+ >>> from zope.security import management
+ >>> from z3c.form import testing
+ >>> management.endInteraction()
+ >>> newPolicy = testing.SimpleSecurityPolicy()
+ >>> oldpolicy = management.setSecurityPolicy(newPolicy)
+ >>> management.newInteraction()
+
+Now we can create the widget manager:
+
+ >>> personForm = PersonForm()
+ >>> request = TestRequest()
+ >>> manager = field.FieldWidgets(personForm, request, srichter)
+
+After updating the widget manager, the fields are available as widgets, the
+first name being in display and the last name is input mode:
+
+ >>> manager.update()
+ >>> manager['id'].mode
+ 'display'
+ >>> manager['firstName'].mode
+ 'display'
+ >>> manager['lastName'].mode
+ 'input'
+
+However, explicitely overriding the mode in the field declaration, overrides
+this selection for you:
+
+ >>> personForm.fields['firstName'].mode = interfaces.INPUT_MODE
+
+ >>> manager.update()
+ >>> manager['id'].mode
+ 'display'
+ >>> manager['firstName'].mode
+ 'input'
+ >>> manager['lastName'].mode
+ 'input'
+
+
+Data extraction and validation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
Besides managing widgets, the widget manager also controls the process of
extracting and validating extracted data. Let's start with the validation
first, which only validates the data as a whole, assuming each individual
@@ -502,7 +592,7 @@
>>> from z3c.form import validator
>>> zope.component.provideAdapter(validator.InvariantsValidator)
- >>> addPerson.fields = field.Fields(IPerson)
+ >>> personForm.fields = field.Fields(IPerson)
>>> manager.update()
>>> manager.validate(
@@ -524,12 +614,12 @@
>>> name = zope.schema.TextLine(__name__='name')
- >>> class PersonForm(object):
+ >>> class PersonNameForm(object):
... prefix = 'form.'
... fields = field.Fields(name)
- >>> personForm = PersonForm()
+ >>> personNameForm = PersonNameForm()
- >>> manager = field.FieldWidgets(personForm, request, context)
+ >>> manager = field.FieldWidgets(personNameForm, request, context)
In this case, the widget manager's ``validate()`` method should simply ignore
the field and not try to look up any invariants:
@@ -550,7 +640,7 @@
... 'form.widgets.id': u'srichter',
... 'form.widgets.firstName': u'Stephan',
... 'form.widgets.lastName': u'Richter'})
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()
@@ -569,7 +659,7 @@
>>> request = TestRequest(form={
... 'form.widgets.firstName': u'Stephan', 'form.widgets.id': u'srichter'})
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()
>>> manager.extract()
@@ -581,7 +671,7 @@
... 'form.widgets.id': u'srichter',
... 'form.widgets.firstName': u'Stephan',
... 'form.widgets.lastName': u'Richter-Richter'})
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.ignoreContext = True
>>> manager.update()
>>> data, errors = manager.extract()
@@ -600,7 +690,7 @@
Let's have a look at the default form first. Initially, the standard
registered widgets are used:
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.update()
>>> manager['firstName']
@@ -616,11 +706,11 @@
It can be simply assigned as follows:
- >>> addPerson.fields['firstName'].widgetFactory = CustomInputWidgetFactory
+ >>> personForm.fields['firstName'].widgetFactory = CustomInputWidgetFactory
Now this widget should be used instead of the registered default one:
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.update()
>>> manager['firstName']
<CustomInputWidget 'form.widgets.firstName'>
@@ -629,7 +719,7 @@
default factory for in the ``WidgetFactories`` object, which manages the
custom widgets for all modes. Now all modes show this input widget:
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.mode = interfaces.DISPLAY_MODE
>>> manager.update()
>>> manager['firstName']
@@ -643,12 +733,12 @@
>>> def CustomDisplayWidgetFactory(field, request):
... return widget.FieldWidget(field, CustomDisplayWidget(request))
- >>> addPerson.fields['firstName']\
+ >>> personForm.fields['firstName']\
... .widgetFactory[interfaces.DISPLAY_MODE] = CustomDisplayWidgetFactory
Now the display mode should produce the custom display widget, ...
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.mode = interfaces.DISPLAY_MODE
>>> manager.update()
>>> manager['firstName']
@@ -656,7 +746,7 @@
... while the input mode still shows the default custom input widget:
- >>> manager = field.FieldWidgets(addPerson, request, context)
+ >>> manager = field.FieldWidgets(personForm, request, context)
>>> manager.mode = interfaces.INPUT_MODE
>>> manager.update()
>>> manager['firstName']
@@ -664,7 +754,7 @@
The widgets factories component,
- >>> factories = addPerson.fields['firstName'].widgetFactory
+ >>> factories = personForm.fields['firstName'].widgetFactory
>>> factories
{'display': <function CustomDisplayWidgetFactory at ...>}
Modified: z3c.form/trunk/src/z3c/form/form.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/form.txt 2007-06-20 17:33:27 UTC (rev 76847)
+++ z3c.form/trunk/src/z3c/form/form.txt 2007-06-20 17:46:18 UTC (rev 76848)
@@ -878,7 +878,8 @@
The only step the developer has to complete is to re-implement the form's
``getContent()`` method to return the dictionary:
- >>> personDict = {'id': u'rineichen', 'name': u'Roger Ineichen'}
+ >>> personDict = {'id': u'rineichen', 'name': u'Roger Ineichen',
+ ... 'gender': None, 'age': None}
>>> class PersonDictEditForm(PersonEditForm):
... def getContent(self):
... return personDict
@@ -907,7 +908,7 @@
<select id="form-widgets-gender" name="form.widgets.gender:list"
class="selectWidget" size="1">
<option id="form-widgets-gender-novalue"
- value="--NOVALUE--">no value</option>
+ value="--NOVALUE--" selected="selected">no value</option>
<option id="form-widgets-gender-0" value="male">male</option>
<option id="form-widgets-gender-1" value="female">female</option>
</select>
@@ -1154,4 +1155,4 @@
<input type="hidden" id="form-widgets-age"
name="form.widgets.age" class="hiddenWidget"
value="27" />
- ...
\ No newline at end of file
+ ...
Modified: z3c.form/trunk/src/z3c/form/widget.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/widget.txt 2007-06-20 17:33:27 UTC (rev 76847)
+++ z3c.form/trunk/src/z3c/form/widget.txt 2007-06-20 17:46:18 UTC (rev 76848)
@@ -262,7 +262,50 @@
>>> print ageWidget.render()
<input type="text" name="age" value="25" />
+But what happens if the object we are working on is securoty proxied? In
+particular, what happens, if the access to the attribute is denied. To see
+what happens, we have to create a proxied person:
+ >>> from zope.security import checker
+ >>> PersonChecker = checker.Checker({'age': 'Access'}, {'age': 'Edit'})
+
+ >>> ageWidget.request = TestRequest()
+ >>> ageWidget.context = checker.ProxyFactory(Person(), PersonChecker)
+
+After changing the security policy, ...
+
+ >>> from zope.security import management
+ >>> from z3c.form import testing
+ >>> management.endInteraction()
+ >>> newPolicy = testing.SimpleSecurityPolicy()
+ >>> oldPolicy = management.setSecurityPolicy(newPolicy)
+ >>> management.newInteraction()
+
+it is not possible anymore to update the widget:
+
+ >>> ageWidget.update()
+ Traceback (most recent call last):
+ ...
+ Unauthorized: (<Person object at ...>, 'age', 'Access')
+
+If no security declaration has been made at all, we get a
+``ForbiddenAttribute`` error:
+
+ >>> ageWidget.context = checker.ProxyFactory(Person(), checker.Checker({}))
+ >>> ageWidget.update()
+ Traceback (most recent call last):
+ ...
+ ForbiddenAttribute: ('age', <Person object at ...>)
+
+Let's clean up the setup:
+
+ >>> management.endInteraction()
+ >>> newPolicy = management.setSecurityPolicy(oldPolicy)
+ >>> management.newInteraction()
+
+ >>> ageWidget.context = Person()
+
+
Dynamically Changing Attribute Values
-------------------------------------
More information about the Checkins
mailing list