[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