[Zope3-checkins] SVN: Zope3/trunk/ - Implemented a generic user preferences system.

Stephan Richter srichter at cosmos.phy.tufts.edu
Fri Apr 1 13:34:21 EST 2005


Log message for revision 29798:
  - Implemented a generic user preferences system.
  
      * User preferences are combined in groups that are described by
        schemas.
  
      * One can create a tree of preference groups using Python's dot
        notation in the group ids.
  
      * Preference groups can be declared to act as categories, which is
        used by the UI to improve the organization of the preferences.
  
      * Using a default preference provider, the site administrator can
        customize the default settings of the preferences for a user on a
        site wide bases. Acquisition in multi-site pages is supported.
  
      * The preferences are very easily accessible in TALES via a traversal
        namespace::
  
          /++preferences++/zmi/folder/sortedby
       
      * Preferences are easily accessible in Python code::
  
         >>> prefs = IUserPreferences(context)
  
        where the context merely has to be an ``ILocation``.
  
      * Preferences can be easily edited using intuitive URLs::
  
          http://localhost:8080/++preferences++/zmi
  
  

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/src/zope/app/apidoc/configure.zcml
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml
  U   Zope3/trunk/src/zope/app/apidoc/meta.zcml
  D   Zope3/trunk/src/zope/app/apidoc/preference/
  U   Zope3/trunk/src/zope/app/configure.zcml
  U   Zope3/trunk/src/zope/app/meta.zcml
  A   Zope3/trunk/src/zope/app/preference/
  D   Zope3/trunk/src/zope/app/preference/README.txt
  A   Zope3/trunk/src/zope/app/preference/README.txt
  U   Zope3/trunk/src/zope/app/preference/__init__.py
  D   Zope3/trunk/src/zope/app/preference/browser.py
  A   Zope3/trunk/src/zope/app/preference/browser.py
  D   Zope3/trunk/src/zope/app/preference/configure.zcml
  A   Zope3/trunk/src/zope/app/preference/configure.zcml
  A   Zope3/trunk/src/zope/app/preference/default.py
  A   Zope3/trunk/src/zope/app/preference/index.pt
  D   Zope3/trunk/src/zope/app/preference/interfaces.py
  A   Zope3/trunk/src/zope/app/preference/interfaces.py
  A   Zope3/trunk/src/zope/app/preference/macros.pt
  D   Zope3/trunk/src/zope/app/preference/menu.pt
  A   Zope3/trunk/src/zope/app/preference/menu.pt
  D   Zope3/trunk/src/zope/app/preference/meta.zcml
  A   Zope3/trunk/src/zope/app/preference/meta.zcml
  D   Zope3/trunk/src/zope/app/preference/metaconfigure.py
  A   Zope3/trunk/src/zope/app/preference/metaconfigure.py
  D   Zope3/trunk/src/zope/app/preference/metadirectives.py
  A   Zope3/trunk/src/zope/app/preference/metadirectives.py
  D   Zope3/trunk/src/zope/app/preference/preference.py
  A   Zope3/trunk/src/zope/app/preference/preference.py
  A   Zope3/trunk/src/zope/app/preference/subgroup.pt
  D   Zope3/trunk/src/zope/app/preference/tests.py
  A   Zope3/trunk/src/zope/app/preference/tests.py

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2005-04-01 18:25:18 UTC (rev 29797)
+++ Zope3/trunk/doc/CHANGES.txt	2005-04-01 18:34:21 UTC (rev 29798)
@@ -10,6 +10,36 @@
 
     New features
 
+      - Implemented a generic user preferences system.
+
+        * User preferences are combined in groups that are described by
+          schemas.
+
+        * One can create a tree of preference groups using Python's dot
+          notation in the group ids.
+
+        * Preference groups can be declared to act as categories, which is
+          used by the UI to improve the organization of the preferences.
+
+        * Using a default preference provider, the site administrator can
+          customize the default settings of the preferences for a user on a
+          site wide bases. Acquisition in multi-site pages is supported.
+
+        * The preferences are very easily accessible in TALES via a traversal
+          namespace::
+
+            /++preferences++/zmi/folder/sortedby
+     
+        * Preferences are easily accessible in Python code::
+
+           >>> prefs = IUserPreferences(context)
+
+          where the context merely has to be an ``ILocation``.
+
+        * Preferences can be easily edited using intuitive URLs::
+
+            http://localhost:8080/++preferences++/zmi
+
       - Added virtual host support in xml and static tree. The tree will
         switch the root to the virtualhost base.
 

Modified: Zope3/trunk/src/zope/app/apidoc/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/configure.zcml	2005-04-01 18:25:18 UTC (rev 29797)
+++ Zope3/trunk/src/zope/app/apidoc/configure.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -64,9 +64,8 @@
       />
 
   <include package=".browser" />
-  <include package=".preference" />
 
-  <apidoc:preferenceGroup
+  <preferenceGroup
       id="apidoc"
       title="API Doc Tool" 
       description="

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml	2005-04-01 18:25:18 UTC (rev 29797)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,7 +1,6 @@
 <configure
     xmlns="http://namespaces.zope.org/zope"
-    xmlns:browser="http://namespaces.zope.org/browser"
-    xmlns:apidoc="http://namespaces.zope.org/apidoc">
+    xmlns:browser="http://namespaces.zope.org/browser">
 
   <class class=".ifacemodule.InterfaceModule">
     <allow interface=".ifacemodule.IInterfaceModule" />
@@ -67,7 +66,7 @@
       template="menu.pt"
       />
 
-  <apidoc:preferenceGroup
+  <preferenceGroup
       id="apidoc.InterfaceDetails"
       schema=".interfaces.IInterfaceDetailsPreferences"
       title="Interface Details" 

Modified: Zope3/trunk/src/zope/app/apidoc/meta.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/meta.zcml	2005-04-01 18:25:18 UTC (rev 29797)
+++ Zope3/trunk/src/zope/app/apidoc/meta.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,6 +1,5 @@
 <configure xmlns:meta="http://namespaces.zope.org/meta">
   <include package=".codemodule" file="meta.zcml"/>
   <include package=".bookmodule" file="meta.zcml"/>
-  <include package=".preference" file="meta.zcml"/>
   <meta:provides feature="apidoc" />
 </configure>

Modified: Zope3/trunk/src/zope/app/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/configure.zcml	2005-04-01 18:25:18 UTC (rev 29797)
+++ Zope3/trunk/src/zope/app/configure.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -36,6 +36,7 @@
   <include package="zope.app.pagetemplate" />
   <include package=".generations" />
   <include package=".zapi" />
+  <include package="zope.app.preference" />
 
   <!-- Views -->
   <include package="zope.app.http" />
@@ -75,7 +76,6 @@
 
 
   <!-- Content types -->
-
   <include package="zope.app.folder" />
 
   <!-- Browser Configurations -->

Modified: Zope3/trunk/src/zope/app/meta.zcml
===================================================================
--- Zope3/trunk/src/zope/app/meta.zcml	2005-04-01 18:25:18 UTC (rev 29797)
+++ Zope3/trunk/src/zope/app/meta.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -12,6 +12,7 @@
 <include package="zope.app.pagetemplate" file="meta.zcml" />
 <include package="zope.app.schema" file="meta.zcml" />
 <include package="zope.app.container.browser" file="meta.zcml" />
+<include package="zope.app.preference" file="meta.zcml" />
 
 <!-- BBB: Goes away in 3.3 -->
 <include package="zope.app.site" file="meta.zcml" />

Copied: Zope3/trunk/src/zope/app/preference (from rev 29780, Zope3/trunk/src/zope/app/apidoc/preference)

Deleted: Zope3/trunk/src/zope/app/preference/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/README.txt	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/README.txt	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,225 +0,0 @@
-================
-User Preferences
-================
-
-Implementing user preferences is usually a painful task, since it requires a
-lot of custom coding and constantly changing preferences makes it hard to
-maintain the data and UI. The `preference` package
-
-  >>> from zope.app.apidoc.preference import preference
-
-eases this pain by providing a generic user preferences framework that uses
-schemas to categorize and describe the preferences.
-
-
-Preferences Groups
-------------------
-
-Preferences are grouped in preference groups and the preferences inside a
-group a spcified via the preferences group schema:
-
-  >>> import zope.interface
-  >>> import zope.schema
-  >>> class IZMIUserSettings(zope.interface.Interface):
-  ...     """Basic User Preferences"""
-  ...
-  ...     email = zope.schema.TextLine(
-  ...         title=u"E-mail Address",
-  ...         description=u"E-mail Address used to send notifications")
-  ...
-  ...     skin = zope.schema.Choice(
-  ...         title=u"Skin",
-  ...         description=u"The skin that should be used for the ZMI.",
-  ...         values=['Rotterdam', 'ZopeTop', 'Basic'],
-  ...         default='Rotterdam')
-  ...
-  ...     showZopeLogo = zope.schema.Bool(
-  ...         title=u"Show Zope Logo",
-  ...         description=u"Specifies whether Zope logo should be displayed "
-  ...                     u"at the top of the screen.",
-  ...         default=True)
-
-Now we can instantiate the preference group. Each preference group must have a
-name by which it can be accessed and has an optional title field for UI
-purposes:
-
-  >>> settings = preference.PreferencesGroup(
-  ...     name="ZMISettings",
-  ...     schema=IZMIUserSettings,
-  ...     title=u"ZMI User Settings")
-
-Note that the preferences group provides the interface it is representing:
-
-  >>> IZMIUserSettings.providedBy(settings)
-  True
-
-and the name, schema and title of the group are directly available:
-
-  >>> settings.name
-  'ZMISettings'
-  >>> settings.schema
-  <InterfaceClass __builtin__.IZMIUserSettings>
-  >>> settings.title
-  u'ZMI User Settings'
-
-So let's ask the group for the skin setting:
-
-  >>> settings.skin #doctest:+ELLIPSIS
-  Traceback (most recent call last):
-  ...
-  ComponentLookupError: 
-  (<InterfaceClass ...interfaces.IPrincipalAnnotationUtility>, '')
-
-So why did the lookup fail? Because we have not specified a principal yet, for
-which we want to lookup the preferences. To do that, we have to create a new
-interaction:
-
-  >>> class Principal:
-  ...     def __init__(self, id):
-  ...         self.id = id
-  >>> principal = Principal('zope.user')
-
-  >>> class Participation:
-  ...     interaction = None
-  ...     def __init__(self, principal):
-  ...         self.principal = principal
-
-  >>> participation = Participation(principal)
-
-  >>> import zope.security.management
-  >>> zope.security.management.newInteraction(participation)
-
-We also need a principal annotations utility, in which we store the settings:
-
-  >>> from zope.app.principalannotation.interfaces import \
-  ...         IPrincipalAnnotationUtility
-  >>> class PrincipalAnnotations(dict):
-  ...     zope.interface.implements(IPrincipalAnnotationUtility)
-  ...
-  ...     def getAnnotations(self, principal):
-  ...         return self.setdefault(principal, {})
-
-  >>> annotations = PrincipalAnnotations()
-
-  >>> from zope.app.testing import ztapi
-  >>> ztapi.provideUtility(IPrincipalAnnotationUtility, annotations)
-
-Let's now try to access the settings again:
-
-  >>> settings.skin
-  'Rotterdam'
-
-which is the default value, since we have not set it yet. We can now reassign
-the value:
-
-  >>> settings.skin = 'Basic'
-  >>> settings.skin
-  'Basic'
-
-However, you cannot just enter any value, since it is validated before the
-assignment:
-
-  >>> settings.skin = 'MySkin'
-  Traceback (most recent call last):
-  ...
-  ConstraintNotSatisfied: MySkin  
-
-
-User Preferences
-----------------
-
-The various preferences groups are collectively available via the user
-preferences object:
-
-  >>> prefs = preference.UserPreferences()
-
-Using this objcet, you can access a list of all available groups
-
-  >>> prefs.items()
-  []
-
-But why did our new ZMI user settings group not appear? This is because we
-have to register it first as a preferences group:
-
-  >>> from zope.app.apidoc.preference.interfaces import IPreferencesGroup
-  >>> ztapi.provideUtility(IPreferencesGroup, settings, settings.name)
-
-Note that the name of the utility and the name saved in the group must be the
-same. Now let's try again:
-
-  >>> prefs.items() #doctest:+ELLIPSIS
-  [(u'ZMISettings', 
-    <zope.app.apidoc.preference.preference.PreferencesGroup object at ...>)]
-
-You can also just access one group at a time:
-
-  >>> prefs['ZMISettings'] #doctest:+ELLIPSIS
-  <zope.app.apidoc.preference.preference.PreferencesGroup object at ...>
-
-The entire `IReadContainer` interface is available.
-
-
-Traversal
----------
-
-Okay, so all these objects are nice, but they do not make it any easier to
-access the preferences in page templates. Thus, a special traversal namespace
-has been created that makes it very simple to access the preferences via a
-traversal path. But before we can use the path expressions, we have to
-register all necessary traversal components and the special `preferences`
-namespace:
-
-  >>> from zope.app.testing import setup
-  >>> setup.setUpTraversal()
-
-  >>> import zope.app.traversing.interfaces
-  >>> ztapi.provideAdapter(None,
-  ...                      zope.app.traversing.interfaces.ITraversable,
-  ...                      preference.preferencesNamespace,
-  ...                      'preferences')
-
-We can now access the preferences as follows:
-
-  >>> from zope.app import zapi
-
-  >>> zapi.traverse(None, '++preferences++ZMISettings/skin')
-  'Basic'
-  >>> zapi.traverse(None, '++preferences++/ZMISettings/skin')
-  'Basic'
-
-
-Security
---------
-
-You might already wonder under which permissions the preferences are
-available. They are actually available publically (`CheckerPublic`), but that
-is not a problem, since the available values are looked up specifically for
-the current user. And why should a user not have full access to his/her
-preferences? 
-
-Let's create a checker using the function that the security machinery is
-actually using:
-
-  >>> checker = preference.PreferencesGroupChecker(settings)
-  >>> checker.permission_id('skin')
-  Global(CheckerPublic,zope.security.checker)
-  >>> checker.setattr_permission_id('skin')
-  Global(CheckerPublic,zope.security.checker)
-
-The name, title and schema are publically available for access, but are not
-available for mutation at all:
-
-  >>> checker.permission_id('name')
-  Global(CheckerPublic,zope.security.checker)
-  >>> checker.setattr_permission_id('name') is None
-  True
-
-
-The only way security could be compromised is when one could override the
-annotations property. However, this property is not available for public
-consumption at all, including read access:
-
-  >>> checker.permission_id('annotation') is None
-  True
-  >>> checker.setattr_permission_id('annotation') is None
-  True

Copied: Zope3/trunk/src/zope/app/preference/README.txt (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/README.txt)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/README.txt	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/README.txt	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,476 @@
+================
+User Preferences
+================
+
+Implementing user preferences is usually a painful task, since it requires a
+lot of custom coding and constantly changing preferences makes it hard to
+maintain the data and UI. The `preference` package
+
+  >>> from zope.app.preference import preference
+
+eases this pain by providing a generic user preferences framework that uses
+schemas to categorize and describe the preferences.
+
+We also have to do some additional setup beforehand:
+
+  >>> from zope.app.testing import setup
+
+  >>> import zope.app.component.hooks
+  >>> zope.app.component.hooks.setHooks()
+  >>> setup.setUpTraversal()
+  >>> setup.setUpSiteManagerLookup()
+
+
+Preference Groups
+------------------
+
+Preferences are grouped in preference groups and the preferences inside a
+group a spcified via the preferences group schema:
+
+  >>> import zope.interface
+  >>> import zope.schema
+  >>> class IZMIUserSettings(zope.interface.Interface):
+  ...     """Basic User Preferences"""
+  ...
+  ...     email = zope.schema.TextLine(
+  ...         title=u"E-mail Address",
+  ...         description=u"E-mail Address used to send notifications")
+  ...
+  ...     skin = zope.schema.Choice(
+  ...         title=u"Skin",
+  ...         description=u"The skin that should be used for the ZMI.",
+  ...         values=['Rotterdam', 'ZopeTop', 'Basic'],
+  ...         default='Rotterdam')
+  ...
+  ...     showZopeLogo = zope.schema.Bool(
+  ...         title=u"Show Zope Logo",
+  ...         description=u"Specifies whether Zope logo should be displayed "
+  ...                     u"at the top of the screen.",
+  ...         default=True)
+
+Now we can instantiate the preference group. Each preference group must have
+an id by which it can be accessed and has an optional title and description
+field for UI purposes:
+
+  >>> settings = preference.PreferenceGroup(
+  ...     "ZMISettings",
+  ...     schema=IZMIUserSettings,
+  ...     title=u"ZMI User Settings",
+  ...     description=u"")
+
+Note that the preferences group provides the interface it is representing:
+
+  >>> IZMIUserSettings.providedBy(settings)
+  True
+
+and the id, schema and title of the group are directly available:
+
+  >>> settings.__id__
+  'ZMISettings'
+  >>> settings.__schema__
+  <InterfaceClass zope.app.preference.README.IZMIUserSettings>
+  >>> settings.__title__
+  u'ZMI User Settings'
+
+So let's ask the preference group for the `skin` setting:
+
+  >>> settings.skin #doctest:+ELLIPSIS
+  Traceback (most recent call last):
+  ...
+  ComponentLookupError: 
+  (<InterfaceClass ...interfaces.IPrincipalAnnotationUtility>, '')
+
+
+So why did the lookup fail? Because we have not specified a principal yet, for
+which we want to lookup the preferences. To do that, we have to create a new
+interaction:
+
+  >>> class Principal:
+  ...     def __init__(self, id):
+  ...         self.id = id
+  >>> principal = Principal('zope.user')
+
+  >>> class Participation:
+  ...     interaction = None
+  ...     def __init__(self, principal):
+  ...         self.principal = principal
+
+  >>> participation = Participation(principal)
+
+  >>> import zope.security.management
+  >>> zope.security.management.newInteraction(participation)
+
+We also need a principal annotations utility, in which we store the settings:
+
+  >>> from zope.app.principalannotation.interfaces import \
+  ...         IPrincipalAnnotationUtility
+  >>> class PrincipalAnnotations(dict):
+  ...     zope.interface.implements(IPrincipalAnnotationUtility)
+  ...
+  ...     def getAnnotations(self, principal):
+  ...         return self.setdefault(principal, {})
+
+  >>> annotations = PrincipalAnnotations()
+
+  >>> from zope.app.testing import ztapi
+  >>> ztapi.provideUtility(IPrincipalAnnotationUtility, annotations)
+
+Let's now try to access the settings again:
+
+  >>> settings.skin
+  'Rotterdam'
+
+which is the default value, since we have not set it yet. We can now reassign
+the value:
+
+  >>> settings.skin = 'Basic'
+  >>> settings.skin
+  'Basic'
+
+However, you cannot just enter any value, since it is validated before the
+assignment:
+
+  >>> settings.skin = 'MySkin'
+  Traceback (most recent call last):
+  ...
+  ConstraintNotSatisfied: MySkin  
+
+
+Preference Group Trees
+----------------------
+
+The preferences would not be very powerful, if you could create a full
+preferences. So let's create a sub-group for our ZMI user settings, where we
+can adjust the look and feel of the folder contents view:
+
+  >>> import sets
+  >>> class IFolderSettings(zope.interface.Interface):
+  ...     """Basic User Preferences"""
+  ...
+  ...     shownFields = zope.schema.Set(
+  ...         title=u"Shown Fields",
+  ...         description=u"Fields shown in the table.",
+  ...         value_type=zope.schema.Choice(['name', 'size', 'creator']),
+  ...         default=sets.Set(['name', 'size']))
+  ...
+  ...     sortedBy = zope.schema.Choice(
+  ...         title=u"Sorted By",
+  ...         description=u"Data field to sort by.",
+  ...         values=['name', 'size', 'creator'],
+  ...         default='name')
+
+  >>> folderSettings = preference.PreferenceGroup(
+  ...     "ZMISettings.Folder",
+  ...     schema=IFolderSettings,
+  ...     title=u"Folder Content View Settings")
+
+Note that the id was chosen so that the parent id is the prefix of the child's
+id. Our new preference sub-group should now be available as an attribute or an
+item on the parent group ...
+
+  >>> settings.Folder
+  Traceback (most recent call last):
+  ...
+  AttributeError: 'Folder' is not a preference or sub-group.
+
+... but not before we register the groups as utilities:
+
+  >>> from zope.app.preference import interfaces
+  >>> from zope.app.testing import ztapi
+
+  >>> ztapi.provideUtility(interfaces.IPreferenceGroup, settings,
+  ...                      name='ZMISettings')
+  >>> ztapi.provideUtility(interfaces.IPreferenceGroup, folderSettings,
+  ...                      name='ZMISettings.Folder')
+
+If we now try to lookup the sub-group again, we should be successfull:
+
+  >>> settings.Folder #doctest:+ELLIPSIS
+  <zope.app.preference.preference.PreferenceGroup object at ...>
+
+  >>> settings['Folder'] #doctest:+ELLIPSIS
+  <zope.app.preference.preference.PreferenceGroup object at ...>
+
+While the registry of the preference groups is flat, the careful naming of the
+ids allows us to have a tree of preferences. Note that this pattern is very
+similar to the way modules are handled in Python; they are stored in a flat
+dictionary in ``sys.modules``, but due to the naming they appear to be in a
+namespace tree.
+
+While we are at it, there are also preference categories that can be compared
+to Python packages. They basically are just a higher level grouping concept
+that is used by the UI to better organize the preferences. A preference group
+can be converted to a category by simply providing an additional interface:
+
+  >>> zope.interface.alsoProvides(settings, interfaces.IPreferenceCategory)
+
+  >>> interfaces.IPreferenceCategory.providedBy(settings)
+  True
+
+
+Default Preferences
+-------------------
+
+It sometimes desirable to define default settings on a site-by-site basis,
+instead of just using the default value from the schema. The preferences
+package provides a module
+ 
+  >>> from zope.app.preference import default
+
+that implements a default preferences provider that can be added as a unnamed
+utility for each site. So the first step is to create a site:
+  
+  >>> root = setup.buildSampleFolderTree()
+  >>> rsm = setup.createSiteManager(root, True)
+
+Now we can register the default preference provider with the root site:
+
+  >>> provider = setup.addUtility(rsm, '', 
+  ...                             interfaces.IDefaultPreferenceProvider, 
+  ...                             default.DefaultPreferenceProvider())
+
+So before we set an explicit default value for a preference, the schema field
+default is used:
+
+  >>> settings.Folder.sortedBy
+  'name'
+
+But if we now set a new default value with the provider,
+
+  >>> defaultFolder = provider.getDefaultPreferenceGroup('ZMISettings.Folder')
+  >>> defaultFolder.sortedBy = 'size'
+
+then the default of the setting changes:
+  
+  >>> settings.Folder.sortedBy
+  'size'
+
+The default preference providers also implictly acquire default values from
+parent sites. So if we make `folder1` a site and set it as the active site
+
+  >>> folder1 = root['folder1']
+  >>> sm1 = setup.createSiteManager(folder1, True)
+
+and add a default provider there,
+
+  >>> provider1 = setup.addUtility(sm1, '', 
+  ...                              interfaces.IDefaultPreferenceProvider, 
+  ...                              default.DefaultPreferenceProvider())
+
+then we still get the root's default values, because we have not defined any
+in the higher default provider:
+
+  >>> settings.Folder.sortedBy
+  'size'
+
+But if we provide the new provider with a default value for `sortedBy`,
+
+  >>> defaultFolder1 = provider1.getDefaultPreferenceGroup('ZMISettings.Folder')
+  >>> defaultFolder1.sortedBy = 'creator'
+
+then it is used instead:
+
+  >>> settings.Folder.sortedBy
+  'creator'
+
+Of course, once the root site becomes our active site again
+
+  >>> zope.app.component.hooks.setSite(root)
+
+the default value of the root provider is used:
+
+  >>> settings.Folder.sortedBy
+  'size'
+
+Of course, all the defaults in the world are not relevant anymore as soon as
+the user actually provides a value:
+
+  >>> settings.Folder.sortedBy = 'name'
+  >>> settings.Folder.sortedBy
+  'name'
+
+Oh, and have I mentioned that entered values are always validated? So you
+cannot just assign any old value:
+
+  >>> settings.Folder.sortedBy = 'foo'
+  Traceback (most recent call last):
+  ...
+  ConstraintNotSatisfied: foo
+
+Finally, if the user deletes his/her explicit setting, we are back to the
+default value:
+
+  >>> del settings.Folder.sortedBy
+  >>> settings.Folder.sortedBy
+  'size'
+
+
+Creating Preference Groups Using ZCML
+-------------------------------------
+
+If you are using the user preference system in Zope 3, you will not have to
+manually setup the preference groups as we did above (of course). We will use
+ZCML instead. First, we need to register the directives:
+
+  >>> from zope.configuration import xmlconfig
+  >>> import zope.app.preference
+  >>> context = xmlconfig.file('meta.zcml', zope.app.preference)
+
+Then the system sets up a root preference group:
+
+  >>> context = xmlconfig.string('''
+  ...     <configure
+  ...         xmlns="http://namespaces.zope.org/zope"
+  ...         i18n_domain="test">
+  ...
+  ...       <preferenceGroup
+  ...           id=""
+  ...           title="User Preferences" 
+  ...           />
+  ...
+  ...     </configure>''', context)
+
+Now we can use the preference system in its intended way. We access the folder
+settings as follows:
+
+  >>> from zope.app import zapi
+  >>> prefs = zapi.getUtility(interfaces.IPreferenceGroup)
+  >>> prefs.ZMISettings.Folder.sortedBy
+  'size'
+
+Let's register the ZMI settings again under a new name via ZCML:
+
+  >>> context = xmlconfig.string('''
+  ...     <configure
+  ...         xmlns="http://namespaces.zope.org/zope"
+  ...         i18n_domain="test">
+  ...
+  ...       <preferenceGroup
+  ...           id="ZMISettings2"
+  ...           title="ZMI Settings NG"
+  ...           schema="zope.app.preference.README.IZMIUserSettings"
+  ...           category="true"
+  ...           />
+  ...
+  ...     </configure>''', context)
+
+  >>> prefs.ZMISettings2 #doctest:+ELLIPSIS
+  <zope.app.preference.preference.PreferenceGroup object at ...>
+
+  >>> prefs.ZMISettings2.__title__
+  u'ZMI Settings NG'
+
+  >>> IZMIUserSettings.providedBy(prefs.ZMISettings2)
+  True
+  >>> interfaces.IPreferenceCategory.providedBy(prefs.ZMISettings2)
+  True
+
+And the tree can built again by carefully cosntructing the id:
+
+  >>> context = xmlconfig.string('''
+  ...     <configure
+  ...         xmlns="http://namespaces.zope.org/zope"
+  ...         i18n_domain="test">
+  ...
+  ...       <preferenceGroup
+  ...           id="ZMISettings2.Folder"
+  ...           title="Folder Settings"
+  ...           schema="zope.app.preference.README.IFolderSettings"
+  ...           />
+  ...
+  ...     </configure>''', context)
+
+  >>> prefs.ZMISettings2 #doctest:+ELLIPSIS
+  <zope.app.preference.preference.PreferenceGroup object at ...>
+
+  >>> prefs.ZMISettings2.Folder.__title__
+  u'Folder Settings'
+
+  >>> IFolderSettings.providedBy(prefs.ZMISettings2.Folder)
+  True
+  >>> interfaces.IPreferenceCategory.providedBy(prefs.ZMISettings2.Folder)
+  False
+
+
+Simple Python-Level Access
+--------------------------
+
+If a site is set, getting the user preferences is very simple:
+
+  >>> from zope.app.preference import UserPreferences
+  >>> prefs2 = UserPreferences()
+  >>> prefs2.ZMISettings.Folder.sortedBy
+  'size'
+
+This function is also commonly registered as an adapter,
+
+  >>> from zope.app.location.interfaces import ILocation
+  >>> ztapi.provideAdapter(ILocation, interfaces.IUserPreferences, 
+  ...                      UserPreferences)
+
+so that you can adapt any location to the user preferences:
+
+  >>> prefs3 = interfaces.IUserPreferences(folder1)
+  >>> prefs3.ZMISettings.Folder.sortedBy
+  'creator'
+
+
+Traversal
+---------
+
+Okay, so all these objects are nice, but they do not make it any easier to
+access the preferences in page templates. Thus, a special traversal namespace
+has been created that makes it very simple to access the preferences via a
+traversal path. But before we can use the path expressions, we have to
+register all necessary traversal components and the special `preferences`
+namespace:
+
+  >>> import zope.app.traversing.interfaces
+  >>> ztapi.provideAdapter(None,
+  ...                      zope.app.traversing.interfaces.ITraversable,
+  ...                      preference.preferencesNamespace,
+  ...                      'preferences')
+
+We can now access the preferences as follows:
+
+  >>> zapi.traverse(None, '++preferences++ZMISettings/skin')
+  'Basic'
+  >>> zapi.traverse(None, '++preferences++/ZMISettings/skin')
+  'Basic'
+
+
+Security
+--------
+
+You might already wonder under which permissions the preferences are
+available. They are actually available publically (`CheckerPublic`), but that
+is not a problem, since the available values are looked up specifically for
+the current user. And why should a user not have full access to his/her
+preferences? 
+
+Let's create a checker using the function that the security machinery is
+actually using:
+
+  >>> checker = preference.PreferenceGroupChecker(settings)
+  >>> checker.permission_id('skin')
+  Global(CheckerPublic,zope.security.checker)
+  >>> checker.setattr_permission_id('skin')
+  Global(CheckerPublic,zope.security.checker)
+
+The id, title, description, and schema are publically available for access,
+but are not available for mutation at all:
+
+  >>> checker.permission_id('__id__')
+  Global(CheckerPublic,zope.security.checker)
+  >>> checker.setattr_permission_id('__id__') is None
+  True
+
+
+The only way security could be compromised is when one could override the
+annotations property. However, this property is not available for public
+consumption at all, including read access:
+
+  >>> checker.permission_id('annotation') is None
+  True
+  >>> checker.setattr_permission_id('annotation') is None
+  True

Modified: Zope3/trunk/src/zope/app/preference/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/__init__.py	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/__init__.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1 +1,3 @@
 # Make a package
+
+from zope.app.preference.preference import UserPreferences

Deleted: Zope3/trunk/src/zope/app/preference/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/browser.py	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/browser.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,37 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 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.
-#
-##############################################################################
-"""User Preferences Browser Views
-
-$Id: menu.py 29269 2005-02-23 22:22:48Z srichter $
-"""
-__docformat__ = 'restructuredtext'
-from zope.security.proxy import removeSecurityProxy
-from zope.app.pagetemplate.simpleviewclass import simple
-from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
-from zope.app.form.browser.editview import EditView
-
-from zope.app.apidoc import utilities
-
-
-class EditPreferencesGroup(EditView):
-
-    def __init__(self, context, request):
-        self.__used_for__ = removeSecurityProxy(context.schema)
-        self.schema = removeSecurityProxy(context.schema)
-        self.label = context.title + ' Preferences'
-        super(EditPreferencesGroup, self).__init__(context, request)
-
-    def getIntroduction(self):
-        return utilities.renderText(self.schema.__doc__,
-                                    self.schema.__module__)

Copied: Zope3/trunk/src/zope/app/preference/browser.py (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/browser.py)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/browser.py	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/browser.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""User Preferences Browser Views
+
+$Id: menu.py 29269 2005-02-23 22:22:48Z srichter $
+"""
+__docformat__ = 'restructuredtext'
+import re
+import zope.interface
+import zope.schema
+from zope.security.proxy import removeSecurityProxy
+
+from zope.app import zapi
+from zope.app.basicskin.standardmacros import StandardMacros
+from zope.app.container.interfaces import IObjectFindFilter
+from zope.app.form.browser.editview import EditView
+from zope.app.pagetemplate.simpleviewclass import simple
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+from zope.app.tree.browser.cookie import CookieTreeView
+
+from zope.app.preference import interfaces
+
+NoneInterface = zope.interface.interface.InterfaceClass('None')
+
+class PreferencesMacros(StandardMacros):
+    """Page Template METAL macros for preferences"""
+    macro_pages = ('preference_macro_definitions',)
+
+
+class PreferenceGroupFilter(object):
+    """A special filter for """
+    zope.interface.implements(IObjectFindFilter)
+
+    def matches(self, obj):
+        """Decide whether the object is shown in the tree."""
+        if interfaces.IPreferenceCategory.providedBy(obj):
+            return True
+
+        if interfaces.IPreferenceGroup.providedBy(obj):
+            parent = zapi.getParent(obj)
+            if interfaces.IPreferenceCategory.providedBy(parent):
+                return True
+
+        return False
+        
+
+class PreferencesTree(CookieTreeView):
+    """Preferences Tree using the stateful cookie tree."""
+
+    def tree(self):
+        root = zapi.getRoot(self.context)
+        filter = PreferenceGroupFilter()
+        return self.cookieTree(root, filter)
+
+
+class EditPreferenceGroup(EditView):
+
+    def __init__(self, context, request):
+        self.__used_for__ = removeSecurityProxy(context.__schema__)
+        self.schema = removeSecurityProxy(context.__schema__)
+
+        if self.schema is None:
+            self.schema = NoneInterface 
+            zope.interface.alsoProvides(removeSecurityProxy(context),
+                                        NoneInterface)
+            
+        self.label = context.__title__ + ' Preferences'
+        super(EditPreferenceGroup, self).__init__(context, request)
+        self.setPrefix(context.__id__)
+
+    def getIntroduction(self):
+        text = self.context.__description__ or self.schema.__doc__
+
+        # Determine common whitespace ...
+        cols = len(re.match('^[ ]*', text).group())
+        # ... and clean it up.
+        text = re.sub('\n[ ]{%i}' %cols, '\n', text).strip()
+
+        if not text:
+            return u''
+
+        # Render the description as ReST.
+        source = zapi.createObject('zope.source.rest', text)
+        renderer = zapi.getMultiAdapter((source, self.request))
+        return renderer.render()
+

Deleted: Zope3/trunk/src/zope/app/preference/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/configure.zcml	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/configure.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,49 +0,0 @@
-<configure
-  xmlns="http://namespaces.zope.org/zope"
-  xmlns:browser="http://namespaces.zope.org/browser"
-  xmlns:apidoc="http://namespaces.zope.org/apidoc"
-  i18n_domain="zope">
-
-  <class class=".preference.UserPreferences">
-    <allow interface=".interfaces.IUserPreferences" 
-           attributes="__parent__ __name__" />
-  </class>
-
-  <view
-      name="preferences" type="*"
-      provides="zope.app.traversing.interfaces.ITraversable" for="*"
-      factory=".preference.preferencesNamespace"
-      />
-
-  <adapter
-      name="preferences"
-      provides="zope.app.traversing.interfaces.ITraversable" for="*"
-      factory=".preference.preferencesNamespace"
-      />
-
-  <!-- Browser Views -->
-
-  <browser:page
-      for=".interfaces.IUserPreferences"
-      permission="zope.Public"
-      name="menu.html"
-      template="menu.pt"
-      />
-
-  <browser:page
-      name="edit.html"
-      for=".interfaces.IPreferencesGroup"
-      class=".browser.EditPreferencesGroup"
-      template="edit.pt"
-      permission="zope.Public"
-      />
-
-  <!-- Books Chapter -->
-
-  <apidoc:bookchapter 
-      id="preferences"
-      title="User Preferences API"
-      doc_path="README.txt"
-      />
-
-</configure>

Copied: Zope3/trunk/src/zope/app/preference/configure.zcml (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/configure.zcml)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/configure.zcml	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/configure.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,117 @@
+<configure
+  xmlns="http://namespaces.zope.org/zope"
+  xmlns:browser="http://namespaces.zope.org/browser"
+  xmlns:apidoc="http://namespaces.zope.org/apidoc"
+  xmlns:zcml="http://namespaces.zope.org/zcml"
+  i18n_domain="zope">
+
+  <view
+      name="preferences" 
+      for="*"
+      type="*"
+      provides="zope.app.traversing.interfaces.ITraversable"
+      factory=".preference.preferencesNamespace"
+      />
+
+  <adapter
+      name="preferences"
+      for="*"
+      provides="zope.app.traversing.interfaces.ITraversable"
+      factory=".preference.preferencesNamespace"
+      />
+
+  <!-- Root preference group -->
+  <preferenceGroup
+      id=""
+      title="User Preferences" 
+      />
+
+
+  <!-- Preference Groups -->
+  <browser:page
+      name="index.html"
+      for=".interfaces.IPreferenceGroup"
+      class=".browser.EditPreferenceGroup"
+      template="index.pt"
+      permission="zope.Public"
+      />
+
+  <browser:page
+      name="editAsSubGroup"
+      for=".interfaces.IPreferenceGroup"
+      class=".browser.EditPreferenceGroup"
+      template="subgroup.pt"
+      permission="zope.Public"
+      />
+
+
+  <!-- Default Preference Provider -->
+  <localUtility class=".default.DefaultPreferenceProvider">
+    <require
+        permission="zope.ManageSite"
+        interface=".interfaces.IDefaultPreferenceProvider"
+        />
+  </localUtility>
+
+  <view
+      name="preferences" 
+      for=".interfaces.IDefaultPreferenceProvider"
+      type="*"
+      provides="zope.interface.Interface"
+      factory=".default.DefaultPreferences"
+      />
+
+  <browser:addMenuItem
+       class=".default.DefaultPreferenceProvider"
+       title="Default User Preferences Provider"
+       description="A Default User Preferences Provider"
+       permission="zope.ManageSite"
+       />
+
+  <browser:tool
+      interface=".interfaces.IDefaultPreferenceProvider"
+      title="Default User Preferences Provider"
+      description="
+          This component lets you define the local default user
+          preferences. The values of this provider are used, if the
+          user has not made a selection yet."
+      unique="true"
+      />
+
+  <!-- Preferences-specific macros -->
+  <browser:page
+      for="*"
+      name="preferences_macros"
+      permission="zope.View"
+      class=".browser.PreferencesMacros"
+      allowed_interface="zope.interface.common.mapping.IItemMapping" 
+      />
+  
+  <browser:page
+      for="*"
+      name="preference_macro_definitions"
+      permission="zope.View"
+      template="macros.pt"
+      />
+
+
+  <!-- Preferences Tree -->
+
+  <browser:page
+      name="tree"
+      for=".interfaces.IPreferenceGroup"
+      class=".browser.PreferencesTree"
+      permission="zope.View"
+      attribute="tree" 
+      />
+
+  <!-- Book Chapter -->
+
+  <apidoc:bookchapter 
+      id="preferences"
+      title="User Preferences API"
+      doc_path="README.txt"
+      zcml:condition="have apidoc"
+      />
+
+</configure>

Copied: Zope3/trunk/src/zope/app/preference/default.py (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/default.py)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/default.py	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/default.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,116 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Default Preferences Provider
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import persistent
+from BTrees.OOBTree import OOBTree
+
+import zope.interface
+from zope.security.checker import defineChecker
+
+from zope.app import component
+from zope.app import zapi
+from zope.app.container.contained import Contained
+from zope.app.location import locate
+from zope.app.traversing.interfaces import IContainmentRoot
+
+from zope.app.preference import preference, interfaces
+
+
+class DefaultPreferenceProvider(persistent.Persistent, Contained):
+    zope.interface.implements(interfaces.IDefaultPreferenceProvider)
+
+    def __init__(self):
+        self.data = OOBTree()
+
+    def getDefaultPreferenceGroup(self, id=''):
+        group = zapi.getUtility(interfaces.IPreferenceGroup, name=id)
+        group = group.__bind__(self)
+        default = DefaultPreferenceGroup(group, self)
+        zope.interface.alsoProvides(default, IContainmentRoot)
+        locate(default, self, 'preferences')
+        return default
+
+    preferences = property(getDefaultPreferenceGroup)
+
+
+def DefaultPreferences(context, request):
+    return context.preferences
+
+
+class DefaultPreferenceGroup(preference.PreferenceGroup):
+    """A preference group representing the site-wide default values."""
+
+    def __init__(self, group, provider):
+        self.provider = provider
+        super(DefaultPreferenceGroup, self).__init__(
+            group.__id__, group.__schema__,
+            group.__title__, group.__description__)
+
+        # Make sure that we also mark the default group as category if the
+        # actual group is one; this is important for the UI.
+        if interfaces.IPreferenceCategory.providedBy(group):
+            zope.interface.alsoProvides(self, interfaces.IPreferenceCategory)
+
+    def get(self, key, default=None):
+        group = super(DefaultPreferenceGroup, self).get(key, default)
+        if group is default:
+            return default
+        return DefaultPreferenceGroup(group, self.provider).__bind__(self)
+    
+    def items(self):
+        return [
+            (id, DefaultPreferenceGroup(group, self.provider).__bind__(self))
+            for id, group in super(DefaultPreferenceGroup, self).items()]
+
+    def __getattr__(self, key):
+        # Try to find a sub-group of the given id
+        group = self.get(key)
+        if group is not None:
+            return group
+
+        # Try to find a preference of the given name
+        if self.__schema__ and key in self.__schema__:
+            marker = object()
+            value = self.data.get(key, marker)
+            if value is not marker:
+                return value
+
+            # There is currently no local entry, so let's go to the next
+            # provider and lookup the group and value there.
+            nextProvider = component.queryNextUtility(
+                self.provider, interfaces.IDefaultPreferenceProvider)
+
+            # No more providers found, so return the schema's default
+            if nextProvider is None: 
+                return self.__schema__[key].default
+
+            nextGroup = nextProvider.getDefaultPreferenceGroup(self.__id__)
+            return getattr(nextGroup, key, self.__schema__[key].default)
+
+        # Nothing found, raise an attribute error
+        raise AttributeError, "'%s' is not a preference or sub-group." %key
+
+    def data(self):
+        if self.__id__ not in self.provider.data:
+            self.provider.data[self.__id__] = OOBTree()
+
+        return self.provider.data[self.__id__]
+    data = property(data)
+
+
+defineChecker(DefaultPreferenceGroup, preference.PreferenceGroupChecker)

Copied: Zope3/trunk/src/zope/app/preference/index.pt (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/index.pt)

Deleted: Zope3/trunk/src/zope/app/preference/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/interfaces.py	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/interfaces.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,52 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2005 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.
-#
-##############################################################################
-"""User Preferences API
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-
-import zope.interface
-import zope.schema
-
-from zope.app.container.interfaces import IReadContainer
-
-class IPreferencesGroup(zope.interface.Interface):
-    """A group of preferences.
-
-    This component represents a logical group of preferences. The preferences
-    contained by this group is defined through the schema. The group has also
-    a name by which it can be accessed.
-    """
-
-    name = zope.schema.TextLine(
-        title=u"Name",
-        description=u"The name of the group.",
-        required=True)
-
-    schema = zope.schema.InterfaceField(
-        title=u"Schema",
-        description=u"Schema describing the preferences of the group.",
-        required=True)
-
-    title = zope.schema.TextLine(
-        title=u"Title",
-        description=u"The title of the group used in the UI.",
-        required=True)
-
-
-class IUserPreferences(IReadContainer):
-    """Component that manages all preference groups."""
-
-    

Copied: Zope3/trunk/src/zope/app/preference/interfaces.py (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/interfaces.py)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/interfaces.py	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/interfaces.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,87 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""User Preferences Interfaces
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+
+from zope.app.container.interfaces import IReadContainer
+from zope.app.location.interfaces import ILocation
+
+
+class IPreferenceGroup(ILocation):
+    """A group of preferences.
+
+    This component represents a logical group of preferences. The preferences
+    contained by this group is defined through the schema. The group has also
+    a name by which it can be accessed.
+
+    The fields specified in the schema *must* be available as attributes and
+    items of the group instance. It is up to the implementation how this is
+    realized, however, most often one will implement __setattr__ and
+    __getattr__ as well as the common mapping API. 
+
+    The reason all the API fields are doubly underlined is to avoid name
+    clashes.
+    """
+
+    __id__ = zope.schema.TextLine(
+        title=u"Id",
+        description=u"The id of the group.",
+        required=True)
+
+    __schema__ = zope.schema.InterfaceField(
+        title=u"Schema",
+        description=u"Schema describing the preferences of the group.",
+        required=False)
+
+    __title__ = zope.schema.TextLine(
+        title=u"Title",
+        description=u"The title of the group used in the UI.",
+        required=True)
+
+    __description__ = zope.schema.Text(
+        title=u"Description",
+        description=u"The description of the group used in the UI.",
+        required=False)
+
+
+class IPreferenceCategory(zope.interface.Interface):
+    """A collection of preference groups.
+
+    Objects providing this interface serve as groups of preference
+    groups. This allows UIs to distinguish between high- and low-level
+    prefernce groups.
+    """
+
+class IUserPreferences(zope.interface.Interface):
+    """Objects providing this interface have to provide the root preference
+    group API as well."""
+
+
+class IDefaultPreferenceProvider(zope.interface.Interface):
+    """A root object providing default values for the entire preferences tree.
+
+    Default preference providers are responsible for providing default values
+    for all preferences. The way they get these values are up to the
+    implementation.
+    """
+
+    preferences = zope.schema.Field(
+        title = u"Default Preferences Root",
+        description = u"Link to the default preferences")

Copied: Zope3/trunk/src/zope/app/preference/macros.pt (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/macros.pt)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/macros.pt	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/macros.pt	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,153 @@
+<metal:block define-macro="tree">
+
+<table cellspacing="0" cellpadding="0"
+       tal:define="root           context/@@tree;
+                   result         root/getFlatDicts;
+                   nodeDictList   python:result[0];
+                   maxDepth       python:result[1]">
+
+<tr>
+  <td class="list-item"
+      tal:attributes="colspan python:maxDepth+2">
+    Preferences
+  </td>
+</tr>
+
+<tr tal:repeat="nodeInfo nodeDictList">
+<tal:block tal:define="node nodeInfo/node">
+
+  <td style="width:16px" tal:repeat="state nodeInfo/row-state">
+    <img tal:attributes="src context/++resource++tree_images/vline.png"
+         tal:condition="state" alt="|" border="0" />
+  </td>
+
+  <td style="width:16px">
+    <a href=""
+       tal:attributes="href string:?tree-state=${nodeInfo/tree-state}"
+       tal:condition="node/hasChildren">
+      <tal:block condition="not:nodeInfo/last-level-node">
+        <img tal:attributes="src context/++resource++tree_images/plus_vline.png"
+             tal:condition="not:node/expanded" alt="+" border="0" />
+        <img tal:attributes="src context/++resource++tree_images/minus_vline.png"
+             tal:condition="node/expanded" alt="-" border="0" />
+      </tal:block>
+      <tal:block condition="nodeInfo/last-level-node">
+        <img tal:attributes="src context/++resource++tree_images/plus.png"
+             tal:condition="not:node/expanded" alt="+" border="0" />
+        <img tal:attributes="src context/++resource++tree_images/minus.png"
+             tal:condition="node/expanded" alt="-" border="0" />
+      </tal:block>
+    </a>
+    <tal:block condition="not:node/hasChildren">
+      <img tal:attributes="src context/++resource++tree_images/tline.png"
+           tal:condition="not:nodeInfo/last-level-node" alt="" border="0" />
+      <img tal:attributes="src context/++resource++tree_images/lline.png"
+           tal:condition="nodeInfo/last-level-node" alt="" border="0" />
+    </tal:block>
+  </td>
+
+  <td class="list-item"
+      tal:attributes="colspan python:maxDepth-len(nodeInfo['row-state'])+1">
+    &nbsp;<a href=""
+       tal:attributes="href 
+           string:${node/context/@@absolute_url}/@@index.html"
+       tal:content="node/context/zope:name">
+      node/id
+    </a>
+  </td>
+
+</tal:block>
+</tr>
+
+</table>
+  
+</metal:block>
+
+
+<metal:block define-macro="pref_view">
+
+<html metal:use-macro="context/@@standard_macros/view">
+<body>
+
+<div id="navigators" metal:fill-slot="navigators">
+ <div class="box">
+   <h4>Preferences</h4>
+   <div class="body">
+    <metal:block use-macro="context/@@preferences_macros/tree" />
+   </div>
+ </div>
+</div>
+
+<div metal:fill-slot="tabs">
+  <h1 tal:content="context/__title__">User Preferences</h1>
+</div>
+
+<div metal:fill-slot="body">
+
+  <div metal:define-slot="body">
+     <p>Body here</p>
+  </div>
+
+</div>
+
+</body>
+
+</html>
+</metal:block>
+
+
+<metal:block define-macro="edit_pref_group">
+
+  <div tal:content="structure view/getIntroduction">
+    Category Description goes here.
+  </div>  
+  <br/>
+
+  <p tal:define="status view/update"
+     tal:condition="status"
+     tal:content="status" />
+
+  <p tal:condition="view/errors" i18n:translate="">
+    There are <strong tal:content="python:len(view.errors)"
+                      i18n:name="num_errors">6</strong> input errors.
+  </p>
+      
+  <table class="listing" width="90%" cellspacing="0" cellpadding="0"
+         tal:condition="view/widgets">
+
+    <thead>
+      <tr>
+        <th i18n:translate="">Description</th>
+        <th i18n:translate="">Value</th>
+      </tr>
+    </thead>
+
+    <tal:block repeat="widget view/widgets" >
+    <tr class=""
+        tal:define="oddrow repeat/widget/odd;
+                    firstrow repeat/widget/start"
+        tal:attributes="class python:oddrow and 'even' or 'odd'">
+      <td class="description">
+        <b tal:content="widget/label">Option</b>
+        <div class="indent small">
+          <div tal:content="widget/hint">
+            Explanation
+          </div>
+          <div class="error" tal:define="error widget/error"
+            tal:condition="error" tal:content="structure error">
+            The Error
+          </div>
+        </div>
+      </td>
+      <td class="input" tal:content="structure widget">
+        <input type="text" style="width:100%"/>
+      </td>
+    </tr>
+    </tal:block>
+  </table>
+
+  <div tal:repeat="subgroup context/values">
+    <tal:block replace="structure subgroup/@@editAsSubGroup" />
+  </div>
+
+</metal:block>

Deleted: Zope3/trunk/src/zope/app/preference/menu.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/menu.pt	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/menu.pt	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,19 +0,0 @@
-<html metal:use-macro="views/apidoc_macros/menu">
-<body>
-
-  <div class="menu" metal:fill-slot="menu-title" i18n:translate="">
-    Preferences
-  </div>
-
-  <div metal:fill-slot="menu" class="small">
-    <ul>
-      <li tal:repeat="group context/values">
-        <a href="" target="main"
-           tal:attributes="href string:./${group/name}/edit.html" 
-           tal:content="group/title" />
-      </li>
-    </ul>
-  </div>
-
-</body>
-</html>

Copied: Zope3/trunk/src/zope/app/preference/menu.pt (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/menu.pt)

Deleted: Zope3/trunk/src/zope/app/preference/meta.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/meta.zcml	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/meta.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,16 +0,0 @@
-<configure
-    xmlns="http://namespaces.zope.org/zope"
-    xmlns:meta="http://namespaces.zope.org/meta"
-    >
-
-  <meta:directives namespace="http://namespaces.zope.org/apidoc">
-
-    <meta:directive
-        name="preferencesGroup"
-        schema=".metadirectives.IPreferencesGroupDirective"
-        handler=".metaconfigure.preferencesGroup"
-        />
-
-  </meta:directives>
-
-</configure>

Copied: Zope3/trunk/src/zope/app/preference/meta.zcml (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/meta.zcml)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/meta.zcml	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/meta.zcml	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,18 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:meta="http://namespaces.zope.org/meta"
+    >
+
+  <meta:provides feature="preference" />
+
+  <meta:directives namespace="http://namespaces.zope.org/zope">
+
+    <meta:directive
+        name="preferenceGroup"
+        schema=".metadirectives.IPreferenceGroupDirective"
+        handler=".metaconfigure.preferenceGroup"
+        />
+
+  </meta:directives>
+
+</configure>

Deleted: Zope3/trunk/src/zope/app/preference/metaconfigure.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/metaconfigure.py	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/metaconfigure.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,26 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 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.
-#
-##############################################################################
-"""This module handles the 'apidoc' namespace directives.
-
-$Id: metaconfigure.py 26889 2004-08-04 04:00:36Z pruggera $
-"""
-__docformat__ = 'restructuredtext'
-from zope.app.component.metaconfigure import utility
-
-from zope.app.apidoc.preference import preference, interfaces
-
-
-def preferencesGroup(_context, name, schema, title):
-    group = preference.PreferencesGroup(name, schema, title)
-    utility(_context, interfaces.IPreferencesGroup, group, name=name)

Copied: Zope3/trunk/src/zope/app/preference/metaconfigure.py (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/metaconfigure.py)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/metaconfigure.py	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/metaconfigure.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""This module handles the 'apidoc' namespace directives.
+
+$Id: metaconfigure.py 26889 2004-08-04 04:00:36Z pruggera $
+"""
+__docformat__ = 'restructuredtext'
+from zope.app.component.metaconfigure import utility
+
+from zope.app.preference.interfaces import IPreferenceGroup
+from zope.app.preference.preference import PreferenceGroup
+
+
+def preferenceGroup(_context, id=None, schema=None,
+                    title=u'', description=u'', category=False):
+    if id is None:
+        id = ''
+    group = PreferenceGroup(id, schema, title, description, category)
+    utility(_context, IPreferenceGroup, group, name=id)

Deleted: Zope3/trunk/src/zope/app/preference/metadirectives.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/metadirectives.py	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/metadirectives.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,42 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 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.
-#
-##############################################################################
-"""``apidoc:preferencesgroup`` ZCML directive interface
-
-$Id: metadirectives.py 26613 2004-07-18 21:50:40Z srichter $
-"""
-__docformat__ = 'restructuredtext'
-from zope.interface import Interface
-from zope.configuration import fields
-
-class IPreferencesGroupDirective(Interface):
-    """Register a preference group."""
-
-    name = fields.PythonIdentifier(
-        title=u"Name",
-        description=u"Name of the preference group used to access the group.",
-        required=True
-        )
-
-    schema = fields.GlobalInterface(
-        title=u"Schema",
-        description=u"Schema of the preference group used defining the "
-                    u"preferences of the group.",
-        required=True        
-        )
-
-    title = fields.MessageID(
-        title=u"Title",
-        description=u"Title of the preference group used in UIs.",
-        required=True
-        )

Copied: Zope3/trunk/src/zope/app/preference/metadirectives.py (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/metadirectives.py)

Deleted: Zope3/trunk/src/zope/app/preference/preference.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/preference.py	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/preference.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,129 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2005 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.
-#
-##############################################################################
-"""User Preference System
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-from BTrees.OOBTree import OOBTree
-
-import zope.interface
-from zope.schema import getFields
-from zope.security.checker import CheckerPublic, Checker, defineChecker
-from zope.security.management import getInteraction
-
-from zope.app import zapi
-from zope.app.container.contained import Contained
-from zope.app.location import LocationProxy, locate
-from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
-
-from zope.app.apidoc.utilities import ReadContainerBase
-from zope.app.apidoc.preference import interfaces
-
-pref_key = 'zope.app.user.UserPreferences'
-
-class PreferencesGroup(object):
-
-    zope.interface.implements(interfaces.IPreferencesGroup)
-
-    name = None
-    schema = None
-    title = None
-
-    def __init__(self, name, schema, title=u''):
-        self.name = name
-        self.schema = schema
-        self.title = title
-        zope.interface.directlyProvides(self, self.schema)
-
-    def __getattr__(self, key):
-        if key in self.schema:
-            marker = object()
-            value = self.annotations.get(key, marker)
-            if value is marker:
-                return self.schema[key].default
-            return value
-        raise AttributeError, "'%s' is not a preference." %key
-
-    def __setattr__(self, key, value):
-        if self.schema and key in self.schema:
-            # Validate the value
-            bound = self.schema[key].bind(self)
-            bound.validate(value)
-            # Assign value
-            self.annotations[key] = value
-        else:
-            self.__dict__[key] = value
-            
-    def annotations(self):
-        utility = zapi.getUtility(IPrincipalAnnotationUtility)
-        # TODO: what if we have multiple participations
-        principal = getInteraction().participations[0].principal
-        ann = utility.getAnnotations(principal)
-
-        if  ann.get(pref_key) is None:
-            ann[pref_key] = OOBTree()
-        prefs = ann[pref_key]
-
-        if self.name not in prefs.keys():
-            prefs[self.name] = OOBTree()
-
-        return prefs[self.name]
-    annotations = property(annotations)
-
-
-def PreferencesGroupChecker(instance):
-    """A function that can be registered as a Checker in defineChecker()"""
-    read_perm_dict = {'name': CheckerPublic, 'schema': CheckerPublic,
-                      'title': CheckerPublic}
-    write_perm_dict = {}
-
-    for name in getFields(instance.schema):
-        read_perm_dict[name] = CheckerPublic
-        write_perm_dict[name] = CheckerPublic
-
-    return Checker(read_perm_dict, write_perm_dict)
-
-defineChecker(PreferencesGroup, PreferencesGroupChecker)
-
-
-class UserPreferences(ReadContainerBase, Contained):
-
-    zope.interface.implements(interfaces.IUserPreferences)
-
-    def get(self, key, default=None):
-        """See zope.app.container.interfaces.IReadContainer"""
-        group = zapi.queryUtility(interfaces.IPreferencesGroup, key, default)
-        if group == default:
-            return default
-        return LocationProxy(group, self, key)
-
-    def items(self):
-        """See zope.app.container.interfaces.IReadContainer"""
-        items = list(zapi.getUtilitiesFor(interfaces.IPreferencesGroup))
-        return [(key, LocationProxy(group, self, key))
-                for key, group in items]
-
-
-class preferencesNamespace(object):
-    """Used to traverse to an User Preferences."""
-    def __init__(self, ob, request=None):
-        self.context = ob
-        
-    def traverse(self, name, ignore):
-        prefs = UserPreferences()
-        locate(prefs, self.context, '++preferences++')
-        if not name:
-            return prefs
-        return prefs[name]

Copied: Zope3/trunk/src/zope/app/preference/preference.py (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/preference.py)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/preference.py	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/preference.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,247 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""User Preferences System
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from BTrees.OOBTree import OOBTree
+
+import zope.interface
+from zope.schema import getFields
+from zope.security.checker import CheckerPublic, Checker, defineChecker
+from zope.security.management import getInteraction
+
+import zope.app.component.hooks
+from zope.app import zapi
+from zope.app.container.contained import Contained
+from zope.app.container.interfaces import IReadContainer
+from zope.app.location import LocationProxy, locate, Location
+from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
+from zope.app.traversing.interfaces import IContainmentRoot
+
+from zope.app.preference.interfaces import IPreferenceGroup 
+from zope.app.preference.interfaces import IPreferenceCategory 
+from zope.app.preference.interfaces import IDefaultPreferenceProvider 
+
+pref_key = 'zope.app.user.UserPreferences'
+
+
+class PreferenceGroup(Location):
+    """A feature-rich ``IPreferenceGroup`` implementation.
+
+    This class implements the 
+    """
+    zope.interface.implements(IPreferenceGroup, IReadContainer)
+
+    # Declare attributes here, so that they are always available.
+    __id__ = None
+    __schema__ = None
+    __title__ = None
+    __description__ = None
+
+    def __init__(self, id, schema=None, title=u'', description=u'',
+                 isCategory=False):
+        self.__id__ = id
+        self.__schema__ = schema
+        self.__title__ = title
+        self.__description__ = description
+
+        # The last part of the id is the name.
+        self.__name__ = id.split('.')[-1]
+
+        # Make sure this group provides all important interfaces.
+        directlyProvided = ()
+        if isCategory:
+            directlyProvided += (IPreferenceCategory,)
+        if schema:
+            directlyProvided += (schema,)
+        zope.interface.directlyProvides(self, directlyProvided)
+
+    # Store the actual parent in ``__parent``. Usually we would just override
+    # the property to an actual value during binding, but because we overrode
+    # ``__setattr__`` this is not possible anymore. 
+    __parent = None
+    def __parent__(self):
+        return self.__parent or zope.app.component.hooks.getSite()
+    __parent__ = property(__parent__)
+
+
+    def __bind__(self, parent):
+        clone = self.__class__.__new__(self.__class__)
+        clone.__dict__.update(self.__dict__)
+        clone.__parent = parent
+        return clone
+
+
+    def get(self, key, default=None):
+        id = self.__id__ and self.__id__ + '.' + key or key
+        group = zapi.queryUtility(IPreferenceGroup, id, default)
+        if group is default:
+            return default
+        return group.__bind__(self)
+    
+
+    def items(self):
+        cutoff = self.__id__ and len(self.__id__)+1 or 0
+        return [(id[cutoff:], group.__bind__(self))
+                for id, group in zapi.getUtilitiesFor(IPreferenceGroup)
+                if id != self.__id__ and \
+                   id.startswith(self.__id__) and \
+                   id[cutoff:].find('.') == -1]
+
+
+    def __getitem__(self, key):
+        """See zope.app.container.interfaces.IReadContainer"""
+        default = object()
+        obj = self.get(key, default)
+        if obj is default:
+            raise KeyError, key
+        return obj
+
+    def __contains__(self, key):
+        """See zope.app.container.interfaces.IReadContainer"""
+        return self.get(key) is not None
+
+    def keys(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        return [id for id, group in self.items()]
+
+    def __iter__(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        return self.values().__iter__()
+        
+    def values(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        return [group for id, group in self.items()]
+
+    def __len__(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        return len(self.items())    
+
+    def __getattr__(self, key):
+        # Try to find a sub-group of the given id
+        group = self.get(key)
+        if group is not None:
+            return group
+
+        # Try to find a preference of the given name
+        if self.__schema__ and key in self.__schema__:
+            marker = object()
+            value = self.data.get(key, marker)
+            if value is marker:
+                # Try to find a default preference provider
+                provider = zapi.queryUtility(IDefaultPreferenceProvider,
+                                             context=self)
+                if provider is None:
+                    return self.__schema__[key].default
+                defaultGroup = provider.getDefaultPreferenceGroup(self.__id__)
+                return getattr(defaultGroup, key)
+            return value
+
+        # Nothing found, raise an attribute error
+        raise AttributeError, "'%s' is not a preference or sub-group." %key
+
+    def __setattr__(self, key, value):
+        if self.__schema__ and key in self.__schema__:
+            # Validate the value
+            bound = self.__schema__[key].bind(self)
+            bound.validate(value)
+            # Assign value
+            self.data[key] = value
+        else:
+            self.__dict__[key] = value
+
+    def __delattr__(self, key):
+        if self.__schema__ and key in self.__schema__:
+            del self.data[key]
+        else:
+            del self.__dict__[key]
+
+    def data(self):
+        utility = zapi.getUtility(IPrincipalAnnotationUtility, context=self)
+        # TODO: what if we have multiple participations?
+        principal = getInteraction().participations[0].principal
+        ann = utility.getAnnotations(principal)
+
+        # If no preferences exist, create the root preferences object.
+        if  ann.get(pref_key) is None:
+            ann[pref_key] = OOBTree()
+        prefs = ann[pref_key]
+
+        # If no entry for the group exists, create a new entry.
+        if self.__id__ not in prefs.keys():
+            prefs[self.__id__] = OOBTree()
+
+        return prefs[self.__id__]
+    data = property(data)
+
+
+
+def PreferenceGroupChecker(instance):
+    """A function that can be registered as a Checker in defineChecker()
+
+    The attributes available in a preference group are dynamically generated
+    based on the group schema and the available sub-groups. Thus, the
+    permission dictionaries have to be generated at runtime and are unique for
+    each preference group instance.
+    """
+    read_perm_dict = {}
+    write_perm_dict = {}
+
+    # Make sure that the attributes from IPreferenceGroup and IReadContainer
+    # are public.
+    for attrName in ('__id__', '__schema__', '__title__', '__description__',
+                     'get', 'items', 'keys', 'values',
+                     '__getitem__', '__contains__', '__iter__', '__len__'):
+        read_perm_dict[attrName] = CheckerPublic
+
+    # Make the attributes generated from the schema available as well.
+    if instance.__schema__ is not None:
+        for name in getFields(instance.__schema__):
+            read_perm_dict[name] = CheckerPublic
+            write_perm_dict[name] = CheckerPublic
+
+    # Make all sub-groups available as well.
+    for name in instance.keys():
+        read_perm_dict[name] = CheckerPublic
+        write_perm_dict[name] = CheckerPublic
+
+    return Checker(read_perm_dict, write_perm_dict)
+
+defineChecker(PreferenceGroup, PreferenceGroupChecker)
+
+
+def UserPreferences(context=None):
+    """Adapts an ``ILocation`` object to the ``IUserPreferences`` interface."""
+    if context is None:
+        context = zapi.getSiteManager()
+    rootGroup = zapi.getUtility(IPreferenceGroup)
+    rootGroup = rootGroup.__bind__(context)
+    rootGroup.__name__ = '++preferences++'
+    zope.interface.alsoProvides(rootGroup, IContainmentRoot)
+    return rootGroup
+
+class preferencesNamespace(object):
+    """Used to traverse to the root preferences group."""
+
+    def __init__(self, ob, request=None):
+        self.context = ob
+        
+    def traverse(self, name, ignore):
+        rootGroup = zapi.getUtility(IPreferenceGroup)
+        rootGroup = rootGroup.__bind__(self.context)
+        rootGroup.__name__ = '++preferences++'
+        zope.interface.alsoProvides(rootGroup, IContainmentRoot)
+        return name and rootGroup[name] or rootGroup

Copied: Zope3/trunk/src/zope/app/preference/subgroup.pt (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/subgroup.pt)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/subgroup.pt	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/subgroup.pt	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,6 @@
+<fieldset>
+  <legend tal:content="context/__title__">Title</legend>
+
+  <div metal:use-macro="context/@@preferences_macros/edit_pref_group" />
+
+</fieldset>
\ No newline at end of file

Deleted: Zope3/trunk/src/zope/app/preference/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/tests.py	2005-04-01 14:32:25 UTC (rev 29780)
+++ Zope3/trunk/src/zope/app/preference/tests.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -1,31 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 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.
-#
-##############################################################################
-"""Tests for the Preferences System
-
-$Id: tests.py 29143 2005-02-14 22:43:16Z srichter $
-"""
-import unittest
-from zope.testing import doctest, doctestunit
-from zope.component.testing import setUp, tearDown
-
-def test_suite():
-    return unittest.TestSuite((
-        doctest.DocFileSuite('README.txt',
-                             setUp=setUp, tearDown=tearDown,
-                             globs={'pprint': doctestunit.pprint},
-                             optionflags=doctest.NORMALIZE_WHITESPACE),
-        ))
-
-if __name__ == '__main__':
-    unittest.main(default='test_suite')

Copied: Zope3/trunk/src/zope/app/preference/tests.py (from rev 29791, Zope3/trunk/src/zope/app/apidoc/preference/tests.py)
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/tests.py	2005-04-01 16:54:46 UTC (rev 29791)
+++ Zope3/trunk/src/zope/app/preference/tests.py	2005-04-01 18:34:21 UTC (rev 29798)
@@ -0,0 +1,40 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Tests for the Preferences System
+
+$Id: tests.py 29143 2005-02-14 22:43:16Z srichter $
+"""
+import unittest
+from zope.testing import doctest, doctestunit
+from zope.component import testing
+from zope.app.testing import setup
+
+def setUp(test):
+    testing.setUp(test)
+    setup.setUpTestAsModule(test, 'zope.app.preference.README')
+
+def tearDown(test):
+    testing.tearDown(test)
+    setup.tearDownTestAsModule(test)
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt',
+                             setUp=setUp, tearDown=tearDown,
+                             globs={'pprint': doctestunit.pprint},
+                             optionflags=doctest.NORMALIZE_WHITESPACE),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(default='test_suite')



More information about the Zope3-Checkins mailing list