[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">
+ <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