[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/apidoc/ New revised
preference system. I still need to move it to another
Stephan Richter
srichter at cosmos.phy.tufts.edu
Fri Apr 1 11:08:47 EST 2005
Log message for revision 29783:
New revised preference system. I still need to move it to another
location, so I write a detailed message later.
Changed:
U Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml
U Zope3/trunk/src/zope/app/apidoc/browser/modules.pt
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/ifacemodule/index.pt
U Zope3/trunk/src/zope/app/apidoc/preference/README.txt
U Zope3/trunk/src/zope/app/apidoc/preference/browser.py
U Zope3/trunk/src/zope/app/apidoc/preference/configure.zcml
A Zope3/trunk/src/zope/app/apidoc/preference/default.py
D Zope3/trunk/src/zope/app/apidoc/preference/edit.pt
A Zope3/trunk/src/zope/app/apidoc/preference/index.pt
U Zope3/trunk/src/zope/app/apidoc/preference/interfaces.py
A Zope3/trunk/src/zope/app/apidoc/preference/macros.pt
U Zope3/trunk/src/zope/app/apidoc/preference/meta.zcml
U Zope3/trunk/src/zope/app/apidoc/preference/metaconfigure.py
U Zope3/trunk/src/zope/app/apidoc/preference/metadirectives.py
U Zope3/trunk/src/zope/app/apidoc/preference/preference.py
A Zope3/trunk/src/zope/app/apidoc/preference/subgroup.pt
U Zope3/trunk/src/zope/app/apidoc/preference/tests.py
-=-
Modified: Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/bookmodule/book.zcml 2005-04-01 16:08:47 UTC (rev 29783)
@@ -210,7 +210,7 @@
<configure package="zope.testing">
<bookchapter
id="formparser"
- title="Form Parse"
+ title="Form Parser"
doc_path="formparser.txt"
parent="test"
/>
Modified: Zope3/trunk/src/zope/app/apidoc/browser/modules.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/browser/modules.pt 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/browser/modules.pt 2005-04-01 16:08:47 UTC (rev 29783)
@@ -18,9 +18,11 @@
</li>
</ul>
<div id="preference_entry">
+<!--
<a href="./++preferences++/@@menu.html" target="menu">
User Preferences
</a>
+-->
</div>
</div>
Modified: Zope3/trunk/src/zope/app/apidoc/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/configure.zcml 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/configure.zcml 2005-04-01 16:08:47 UTC (rev 29783)
@@ -66,6 +66,15 @@
<include package=".browser" />
<include package=".preference" />
+ <apidoc:preferenceGroup
+ id="apidoc"
+ title="API Doc Tool"
+ description="
+ These are all the preferences related to viewing the API
+ documtation."
+ category="True"
+ />
+
<!-- API Documentation Modules -->
<include package=".bookmodule" />
<include package=".codemodule" />
Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml 2005-04-01 16:08:47 UTC (rev 29783)
@@ -67,8 +67,8 @@
template="menu.pt"
/>
- <apidoc:preferencesGroup
- name="InterfaceDetails"
+ <apidoc:preferenceGroup
+ id="apidoc.InterfaceDetails"
schema=".interfaces.IInterfaceDetailsPreferences"
title="Interface Details"
/>
Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/index.pt 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/index.pt 2005-04-01 16:08:47 UTC (rev 29783)
@@ -69,7 +69,7 @@
<div tal:define="
elementId string:adapters.required.specific;
- show context/++preferences++InterfaceDetails/showSpecificRequiredAdapters">
+ show context/++preferences++apidoc/InterfaceDetails/showSpecificRequiredAdapters">
<h4 i18n:translate="">
<metal:block use-macro="context/@@apidoc_macros/displaySwitch" />
@@ -93,7 +93,7 @@
<div tal:define="
elementId string:adapters.required.extended;
- show context/++preferences++InterfaceDetails/showExtendedRequiredAdapters">
+ show context/++preferences++apidoc/InterfaceDetails/showExtendedRequiredAdapters">
<h4>
<metal:block use-macro="context/@@apidoc_macros/displaySwitch" />
@@ -117,7 +117,7 @@
<div tal:define="
elementId string:adapters.required.generic;
- show context/++preferences++InterfaceDetails/showGenericRequiredAdapters">
+ show context/++preferences++apidoc/InterfaceDetails/showGenericRequiredAdapters">
<h4>
<metal:block use-macro="context/@@apidoc_macros/displaySwitch" />
@@ -169,17 +169,18 @@
<div class="indent">
<tal:block define="
- type string:Browser;
- specific_views view/specificBrowserViews;
- extended_views view/extendedBrowserViews;
- generic_views view/genericBrowserViews;
- show context/++preferences++InterfaceDetails/showBrowserViews;
- show_specific
- context/++preferences++InterfaceDetails/showSpecificBrowserViews;
- show_extended
- context/++preferences++InterfaceDetails/showExtendedBrowserViews;
- show_generic
- context/++preferences++InterfaceDetails/showGenericBrowserViews;
+ type string:Browser;
+ specific_views view/specificBrowserViews;
+ extended_views view/extendedBrowserViews;
+ generic_views view/genericBrowserViews;
+ show
+ context/++preferences++apidoc/InterfaceDetails/showBrowserViews;
+ show_specific
+ context/++preferences++apidoc/InterfaceDetails/showSpecificBrowserViews;
+ show_extended
+ context/++preferences++apidoc/InterfaceDetails/showExtendedBrowserViews;
+ show_generic
+ context/++preferences++apidoc/InterfaceDetails/showGenericBrowserViews;
">
<metal:block use-macro="context/@@interface_macros/viewtype" />
@@ -187,18 +188,19 @@
</tal:block>
<tal:block define="
- type string:XML-RPC;
- specific_views view/specificXMLRPCViews;
- extended_views view/extendedXMLRPCViews;
- generic_views view/genericXMLRPCViews;
- show context/++preferences++InterfaceDetails/showXMLRPCViews;
- show_specific
- context/++preferences++InterfaceDetails/showSpecificXMLRPCViews;
- show_extended
- context/++preferences++InterfaceDetails/showExtendedXMLRPCViews;
- show_generic
- context/++preferences++InterfaceDetails/showGenericXMLRPCViews;
- ">
+ type string:XML-RPC;
+ specific_views view/specificXMLRPCViews;
+ extended_views view/extendedXMLRPCViews;
+ generic_views view/genericXMLRPCViews;
+ show
+ context/++preferences++apidoc/InterfaceDetails/showXMLRPCViews;
+ show_specific
+ context/++preferences++apidoc/InterfaceDetails/showSpecificXMLRPCViews;
+ show_extended
+ context/++preferences++apidoc/InterfaceDetails/showExtendedXMLRPCViews;
+ show_generic
+ context/++preferences++apidoc/InterfaceDetails/showGenericXMLRPCViews;
+ ">
<metal:block use-macro="context/@@interface_macros/viewtype" />
@@ -209,13 +211,14 @@
specific_views view/specificHTTPViews;
extended_views view/extendedHTTPViews;
generic_views view/genericHTTPViews;
- show context/++preferences++InterfaceDetails/showHTTPViews;
+ show
+ context/++preferences++apidoc/InterfaceDetails/showHTTPViews;
show_specific
- context/++preferences++InterfaceDetails/showSpecificHTTPViews;
+ context/++preferences++apidoc/InterfaceDetails/showSpecificHTTPViews;
show_extended
- context/++preferences++InterfaceDetails/showExtendedHTTPViews;
+ context/++preferences++apidoc/InterfaceDetails/showExtendedHTTPViews;
show_generic
- context/++preferences++InterfaceDetails/showGenericHTTPViews;
+ context/++preferences++apidoc/InterfaceDetails/showGenericHTTPViews;
">
<metal:block use-macro="context/@@interface_macros/viewtype" />
@@ -227,13 +230,14 @@
specific_views view/specificFTPViews;
extended_views view/extendedFTPViews;
generic_views view/genericFTPViews;
- show context/++preferences++InterfaceDetails/showFTPViews;
+ show
+ context/++preferences++apidoc/InterfaceDetails/showFTPViews;
show_specific
- context/++preferences++InterfaceDetails/showSpecificFTPViews;
+ context/++preferences++apidoc/InterfaceDetails/showSpecificFTPViews;
show_extended
- context/++preferences++InterfaceDetails/showExtendedFTPViews;
+ context/++preferences++apidoc/InterfaceDetails/showExtendedFTPViews;
show_generic
- context/++preferences++InterfaceDetails/showGenericFTPViews;
+ context/++preferences++apidoc/InterfaceDetails/showGenericFTPViews;
">
<metal:block use-macro="context/@@interface_macros/viewtype" />
@@ -245,13 +249,14 @@
specific_views view/specificOtherViews;
extended_views view/extendedOtherViews;
generic_views view/genericOtherViews;
- show context/++preferences++InterfaceDetails/showOtherViews;
+ show
+ context/++preferences++apidoc/InterfaceDetails/showOtherViews;
show_specific
- context/++preferences++InterfaceDetails/showSpecificOtherViews;
+ context/++preferences++apidoc/InterfaceDetails/showSpecificOtherViews;
show_extended
- context/++preferences++InterfaceDetails/showExtendedOtherViews;
+ context/++preferences++apidoc/InterfaceDetails/showExtendedOtherViews;
show_generic
- context/++preferences++InterfaceDetails/showGenericOtherViews;
+ context/++preferences++apidoc/InterfaceDetails/showGenericOtherViews;
">
<metal:block use-macro="context/@@interface_macros/viewtype" />
Modified: Zope3/trunk/src/zope/app/apidoc/preference/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/README.txt 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/README.txt 2005-04-01 16:08:47 UTC (rev 29783)
@@ -12,7 +12,7 @@
schemas to categorize and describe the preferences.
-Preferences Groups
+Preference Groups
------------------
Preferences are grouped in preference groups and the preferences inside a
@@ -39,30 +39,31 @@
... 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:
+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.PreferencesGroup(
- ... name="ZMISettings",
+ >>> settings = preference.PreferenceGroup(
+ ... "ZMISettings",
... schema=IZMIUserSettings,
- ... title=u"ZMI User Settings")
+ ... title=u"ZMI User Settings",
+ ... description=u"")
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:
+and the id, schema and title of the group are directly available:
- >>> settings.name
+ >>> settings.id
'ZMISettings'
>>> settings.schema
- <InterfaceClass __builtin__.IZMIUserSettings>
+ <InterfaceClass zope.app.apidoc.preference.README.IZMIUserSettings>
>>> settings.title
u'ZMI User Settings'
-So let's ask the group for the skin setting:
+So let's ask the preference group for the `skin` setting:
>>> settings.skin #doctest:+ELLIPSIS
Traceback (most recent call last):
@@ -70,6 +71,7 @@
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:
@@ -125,40 +127,269 @@
ConstraintNotSatisfied: MySkin
-User Preferences
-----------------
+Preference Group Trees
+----------------------
-The various preferences groups are collectively available via the user
-preferences object:
+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:
- >>> prefs = preference.UserPreferences()
+ >>> 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')
-Using this objcet, you can access a list of all available groups
+ >>> folderSettings = preference.PreferenceGroup(
+ ... "ZMISettings.Folder",
+ ... schema=IFolderSettings,
+ ... title=u"Folder Content View Settings")
- >>> prefs.items()
- []
+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 ...
-But why did our new ZMI user settings group not appear? This is because we
-have to register it first as a preferences group:
+ >>> settings.Folder
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'Folder' is not a preference or sub-group.
- >>> from zope.app.apidoc.preference.interfaces import IPreferencesGroup
- >>> ztapi.provideUtility(IPreferencesGroup, settings, settings.name)
+... but not before we register the groups as utilities:
-Note that the name of the utility and the name saved in the group must be the
-same. Now let's try again:
+ >>> from zope.app.apidoc.preference import interfaces
+ >>> from zope.app.testing import ztapi
- >>> prefs.items() #doctest:+ELLIPSIS
- [(u'ZMISettings',
- <zope.app.apidoc.preference.preference.PreferencesGroup object at ...>)]
+ >>> ztapi.provideUtility(interfaces.IPreferenceGroup, settings,
+ ... name='ZMISettings')
+ >>> ztapi.provideUtility(interfaces.IPreferenceGroup, folderSettings,
+ ... name='ZMISettings.Folder')
-You can also just access one group at a time:
+If we now try to lookup the sub-group again, we should be successfull:
- >>> prefs['ZMISettings'] #doctest:+ELLIPSIS
- <zope.app.apidoc.preference.preference.PreferencesGroup object at ...>
+ >>> settings.Folder #doctest:+ELLIPSIS
+ <zope.app.apidoc.preference.preference.PreferenceGroup object at ...>
-The entire `IReadContainer` interface is available.
+ >>> settings['Folder'] #doctest:+ELLIPSIS
+ <zope.app.apidoc.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.apidoc.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:
+
+ >>> from zope.app.testing import setup
+
+ >>> import zope.app.component.hooks
+ >>> zope.app.component.hooks.setHooks()
+ >>> setup.setUpTraversal()
+ >>> setup.setUpSiteManagerLookup()
+
+ >>> 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.apidoc.preference
+ >>> context = xmlconfig.file('meta.zcml', zope.app.apidoc.preference)
+
+Then the system sets up a root preference group:
+
+ >>> context = xmlconfig.string('''
+ ... <configure
+ ... xmlns="http://namespaces.zope.org/apidoc"
+ ... 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/apidoc"
+ ... i18n_domain="test">
+ ...
+ ... <preferenceGroup
+ ... id="ZMISettings2"
+ ... title="ZMI Settings NG"
+ ... schema="zope.app.apidoc.preference.README.IZMIUserSettings"
+ ... category="true"
+ ... />
+ ...
+ ... </configure>''', context)
+
+ >>> prefs.ZMISettings2 #doctest:+ELLIPSIS
+ <zope.app.apidoc.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/apidoc"
+ ... i18n_domain="test">
+ ...
+ ... <preferenceGroup
+ ... id="ZMISettings2.Folder"
+ ... title="Folder Settings"
+ ... schema="zope.app.apidoc.preference.README.IFolderSettings"
+ ... />
+ ...
+ ... </configure>''', context)
+
+ >>> prefs.ZMISettings2 #doctest:+ELLIPSIS
+ <zope.app.apidoc.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
+
+
Traversal
---------
@@ -169,9 +400,6 @@
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,
@@ -180,8 +408,6 @@
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')
@@ -200,18 +426,18 @@
Let's create a checker using the function that the security machinery is
actually using:
- >>> checker = preference.PreferencesGroupChecker(settings)
+ >>> checker = preference.PreferenceGroupChecker(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:
+The id, title, description, and schema are publically available for access,
+but are not available for mutation at all:
- >>> checker.permission_id('name')
+ >>> checker.permission_id('id')
Global(CheckerPublic,zope.security.checker)
- >>> checker.setattr_permission_id('name') is None
+ >>> checker.setattr_permission_id('id') is None
True
Modified: Zope3/trunk/src/zope/app/apidoc/preference/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/browser.py 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/browser.py 2005-04-01 16:08:47 UTC (rev 29783)
@@ -16,22 +16,78 @@
$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.form.browser.editview import EditView
+from zope.app.tree.browser.cookie import CookieTreeView
-from zope.app.apidoc import utilities
+from zope.app.apidoc.preference import interfaces
+NoneInterface = zope.interface.interface.InterfaceClass('None')
-class EditPreferencesGroup(EditView):
+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(EditPreferencesGroup, self).__init__(context, request)
+ super(EditPreferenceGroup, self).__init__(context, request)
+ self.setPrefix(context.id)
def getIntroduction(self):
- return utilities.renderText(self.schema.__doc__,
- self.schema.__module__)
+ # TODO: Remove dependency
+ from zope.app.apidoc import utilities
+ 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)
+
+ return utilities.renderText(text.strip(), self.schema.__module__)
+
Modified: Zope3/trunk/src/zope/app/apidoc/preference/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/configure.zcml 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/configure.zcml 2005-04-01 16:08:47 UTC (rev 29783)
@@ -4,42 +4,108 @@
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="*"
+ name="preferences"
+ for="*"
+ type="*"
+ provides="zope.app.traversing.interfaces.ITraversable"
factory=".preference.preferencesNamespace"
/>
<adapter
name="preferences"
- provides="zope.app.traversing.interfaces.ITraversable" for="*"
+ for="*"
+ provides="zope.app.traversing.interfaces.ITraversable"
factory=".preference.preferencesNamespace"
/>
- <!-- Browser Views -->
+ <!-- Root preference group -->
+ <apidoc:preferenceGroup
+ id=""
+ title="User Preferences"
+ />
+
+ <!-- Preference Groups -->
<browser:page
- for=".interfaces.IUserPreferences"
+ name="index.html"
+ for=".interfaces.IPreferenceGroup"
+ class=".browser.EditPreferenceGroup"
+ template="index.pt"
permission="zope.Public"
- name="menu.html"
- template="menu.pt"
/>
<browser:page
- name="edit.html"
- for=".interfaces.IPreferencesGroup"
- class=".browser.EditPreferencesGroup"
- template="edit.pt"
+ name="editAsSubGroup"
+ for=".interfaces.IPreferenceGroup"
+ class=".browser.EditPreferenceGroup"
+ template="subgroup.pt"
permission="zope.Public"
/>
- <!-- Books Chapter -->
+ <!-- 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="zope.app.apidoc.preference.interfaces.IPreferenceGroup"
+ class=".browser.PreferencesTree"
+ permission="zope.View"
+ attribute="tree"
+ />
+
+ <!-- Book Chapter -->
+
<apidoc:bookchapter
id="preferences"
title="User Preferences API"
Added: Zope3/trunk/src/zope/app/apidoc/preference/default.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/default.py 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/default.py 2005-04-01 16:08:47 UTC (rev 29783)
@@ -0,0 +1,115 @@
+##############################################################################
+#
+# 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.apidoc.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)
Property changes on: Zope3/trunk/src/zope/app/apidoc/preference/default.py
___________________________________________________________________
Name: svn:eol-style
+ native
Deleted: Zope3/trunk/src/zope/app/apidoc/preference/edit.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/edit.pt 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/edit.pt 2005-04-01 16:08:47 UTC (rev 29783)
@@ -1,123 +0,0 @@
-<html metal:use-macro="context/@@apidoc_macros/details">
-<head>
- <style type="text/css" media="all"
- metal:fill-slot="style_slot">
-
-table.prefs {
- border: 0pt;
- width: 80%;
-}
-
-tr {
- margin: 0pt;
- padding: 0pt;
- border: 0pt;
-}
-
-tr.odd {
- background: #fffbbe;
-}
-
-tr.first td {
- border-top: 1pt solid #0000C0;
-}
-
-td {
- padding: 3pt;
- border-bottom: 1pt solid #0000C0;
-}
-
-td.input {
- vertical-align: middle;
- text-align: center;
-}
-
-td.description {
-
-}
-
-td.controls {
- margin-top: 10pt;
- border: 0pt;
- padding: 4pt;
- background: #ccf;
- text-align: right;
-}
-
-td.spacer {
- padding: 5pt;
- border: 0pt;
-}
-
-div.documentation blockquote {
- margin: 0pt;
- padding: 0pt;
-}
-
-div.error {
- font-weight: bold;
- margin: 3pt 0pt;
- color: #ffaa22;
-}
- </style>
-</head>
-<body metal:fill-slot="contents">
-
- <h1 tal:content="view/label">Edit something</h1>
-
- <div class="documentation" tal:content="structure view/getIntroduction">
- Here is the doc string
- </div>
-
- <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>
- <br />
-
- <form action="." tal:attributes="action request/URL" method="post"
- enctype="multipart/form-data">
-
- <table class="prefs" cellspacing="0" cellpadding="0">
- <tal:block repeat="widget view/widgets" >
- <tr class=""
- tal:define="parity repeat/widget/parity;
- firstrow repeat/widget/start"
- tal:attributes="class python: parity +
- (firstrow and ' first' or '')">
- <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>
- <tr><td class="spacer"></td></tr>
- <tr>
- <td colspan="2" class="controls">
- <input type="submit" value="Refresh"
- i18n:attributes="value refresh-button" />
- <input type="submit" name="UPDATE_SUBMIT" value="Change"
- i18n:attributes="value submit-button"/>
- </td>
- </tr>
- </table>
-
- </form>
-
-</body>
-</html>
Added: Zope3/trunk/src/zope/app/apidoc/preference/index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/index.pt 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/index.pt 2005-04-01 16:08:47 UTC (rev 29783)
@@ -0,0 +1,26 @@
+<html metal:use-macro="context/@@preferences_macros/pref_view">
+
+<div metal:fill-slot="body">
+
+ <form action="." tal:attributes="action request/URL" method="post"
+ enctype="multipart/form-data">
+
+ <div metal:use-macro="context/@@preferences_macros/edit_pref_group" />
+
+ <table class="prefs" cellspacing="0" cellpadding="0">
+ <tr><td class="spacer"></td></tr>
+ <tr>
+ <td colspan="2" class="controls">
+ <input type="submit" value="Refresh"
+ i18n:attributes="value refresh-button" />
+ <input type="submit" name="UPDATE_SUBMIT" value="Change"
+ i18n:attributes="value submit-button"/>
+ </td>
+ </tr>
+ </table>
+
+ </form>
+
+</div>
+
+</html>
Property changes on: Zope3/trunk/src/zope/app/apidoc/preference/index.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/apidoc/preference/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/interfaces.py 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/interfaces.py 2005-04-01 16:08:47 UTC (rev 29783)
@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""User Preferences API
+"""User Preferences Interfaces
$Id$
"""
@@ -21,32 +21,60 @@
import zope.schema
from zope.app.container.interfaces import IReadContainer
+from zope.app.location.interfaces import ILocation
-class IPreferencesGroup(zope.interface.Interface):
+
+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.
"""
- name = zope.schema.TextLine(
- title=u"Name",
- description=u"The name of the group.",
+ 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=True)
+ 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 IUserPreferences(IReadContainer):
- """Component that manages all preference groups."""
-
+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 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")
Added: Zope3/trunk/src/zope/app/apidoc/preference/macros.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/macros.pt 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/macros.pt 2005-04-01 16:08:47 UTC (rev 29783)
@@ -0,0 +1,146 @@
+<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>
+ <br />
+
+ <table class="prefs" cellspacing="0" cellpadding="0">
+ <tal:block repeat="widget view/widgets" >
+ <tr class=""
+ tal:define="parity repeat/widget/parity;
+ firstrow repeat/widget/start"
+ tal:attributes="class python: parity +
+ (firstrow and ' first' or '')">
+ <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>
Property changes on: Zope3/trunk/src/zope/app/apidoc/preference/macros.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/apidoc/preference/meta.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/meta.zcml 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/meta.zcml 2005-04-01 16:08:47 UTC (rev 29783)
@@ -6,9 +6,9 @@
<meta:directives namespace="http://namespaces.zope.org/apidoc">
<meta:directive
- name="preferencesGroup"
- schema=".metadirectives.IPreferencesGroupDirective"
- handler=".metaconfigure.preferencesGroup"
+ name="preferenceGroup"
+ schema=".metadirectives.IPreferenceGroupDirective"
+ handler=".metaconfigure.preferenceGroup"
/>
</meta:directives>
Modified: Zope3/trunk/src/zope/app/apidoc/preference/metaconfigure.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/metaconfigure.py 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/metaconfigure.py 2005-04-01 16:08:47 UTC (rev 29783)
@@ -18,9 +18,13 @@
__docformat__ = 'restructuredtext'
from zope.app.component.metaconfigure import utility
-from zope.app.apidoc.preference import preference, interfaces
+from zope.app.apidoc.preference.interfaces import IPreferenceGroup
+from zope.app.apidoc.preference.preference import PreferenceGroup
-def preferencesGroup(_context, name, schema, title):
- group = preference.PreferencesGroup(name, schema, title)
- utility(_context, interfaces.IPreferencesGroup, group, name=name)
+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)
Modified: Zope3/trunk/src/zope/app/apidoc/preference/metadirectives.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/metadirectives.py 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/metadirectives.py 2005-04-01 16:08:47 UTC (rev 29783)
@@ -19,20 +19,23 @@
from zope.interface import Interface
from zope.configuration import fields
-class IPreferencesGroupDirective(Interface):
+class IPreferenceGroupDirective(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
+ # The id is not required, since the root group has an empty id.
+ id = fields.PythonIdentifier(
+ title=u"Id",
+ description=u"""
+ Id of the preference group used to access the group. The id should
+ be a valid path in the preferences tree.""",
+ required=False,
)
schema = fields.GlobalInterface(
title=u"Schema",
description=u"Schema of the preference group used defining the "
u"preferences of the group.",
- required=True
+ required=False
)
title = fields.MessageID(
@@ -40,3 +43,16 @@
description=u"Title of the preference group used in UIs.",
required=True
)
+
+ description = fields.MessageID(
+ title=u"Description",
+ description=u"Description of the preference group used in UIs.",
+ required=False
+ )
+
+ category = fields.Bool(
+ title=u"Is Group a Category",
+ description=u"Denotes whether this preferences group is a category.",
+ required=False,
+ default=False
+ )
Modified: Zope3/trunk/src/zope/app/apidoc/preference/preference.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/preference.py 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/preference.py 2005-04-01 16:08:47 UTC (rev 29783)
@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""User Preference System
+"""User Preferences System
$Id$
"""
@@ -23,107 +23,206 @@
from zope.security.checker import CheckerPublic, Checker, defineChecker
from zope.security.management import getInteraction
+from zope.app.container.interfaces import IReadContainer
from zope.app import zapi
from zope.app.container.contained import Contained
-from zope.app.location import LocationProxy, locate
+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.apidoc.utilities import ReadContainerBase
-from zope.app.apidoc.preference import interfaces
+from zope.app.apidoc.preference.interfaces import IPreferenceGroup
+from zope.app.apidoc.preference.interfaces import IPreferenceCategory
+from zope.app.apidoc.preference.interfaces import IDefaultPreferenceProvider
pref_key = 'zope.app.user.UserPreferences'
-class PreferencesGroup(object):
- zope.interface.implements(interfaces.IPreferencesGroup)
+class PreferenceGroup(Location):
+ """A feature-rich ``IPreferenceGroup`` implementation.
- name = None
+ 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, name, schema, title=u''):
- self.name = name
+ def __init__(self, id, schema=None, title=u'', description=u'',
+ isCategory=False):
+ self.id = id
self.schema = schema
self.title = title
- zope.interface.directlyProvides(self, self.schema)
+ 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)
+
+
+ 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):
- if key in self.schema:
+ # 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.annotations.get(key, marker)
+ value = self.data.get(key, marker)
if value is marker:
- return self.schema[key].default
+ # Try to find a default preference provider
+ provider = zapi.queryUtility(IDefaultPreferenceProvider)
+ if provider is None:
+ return self.schema[key].default
+ defaultGroup = provider.getDefaultPreferenceGroup(self.id)
+ return getattr(defaultGroup, key)
return value
- raise AttributeError, "'%s' is not a preference." %key
+ # 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.annotations[key] = value
+ self.data[key] = value
else:
self.__dict__[key] = value
-
- def annotations(self):
+
+ 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)
- # TODO: what if we have multiple participations
+ # 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 self.name not in prefs.keys():
- prefs[self.name] = OOBTree()
+ # 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.name]
- annotations = property(annotations)
+ return prefs[self.id]
+ data = property(data)
-def PreferencesGroupChecker(instance):
- """A function that can be registered as a Checker in defineChecker()"""
- read_perm_dict = {'name': CheckerPublic, 'schema': CheckerPublic,
- 'title': CheckerPublic}
+
+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 = {}
- for name in getFields(instance.schema):
+ # 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(PreferencesGroup, PreferencesGroupChecker)
+defineChecker(PreferenceGroup, PreferenceGroupChecker)
-class UserPreferences(ReadContainerBase, Contained):
- zope.interface.implements(interfaces.IUserPreferences)
+class preferencesNamespace(object):
+ """Used to traverse to the root preferences group."""
- 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]
+ rootGroup = zapi.getUtility(IPreferenceGroup)
+ rootGroup = rootGroup.__bind__(self.context)
+ rootGroup.__name__ = '++preferences++'
+ zope.interface.alsoProvides(rootGroup, IContainmentRoot)
+ return name and rootGroup[name] or rootGroup
Added: Zope3/trunk/src/zope/app/apidoc/preference/subgroup.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/subgroup.pt 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/subgroup.pt 2005-04-01 16:08:47 UTC (rev 29783)
@@ -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
Property changes on: Zope3/trunk/src/zope/app/apidoc/preference/subgroup.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/app/apidoc/preference/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/preference/tests.py 2005-04-01 16:07:53 UTC (rev 29782)
+++ Zope3/trunk/src/zope/app/apidoc/preference/tests.py 2005-04-01 16:08:47 UTC (rev 29783)
@@ -17,8 +17,17 @@
"""
import unittest
from zope.testing import doctest, doctestunit
-from zope.component.testing import setUp, tearDown
+from zope.component import testing
+from zope.app.testing import setup
+def setUp(test):
+ testing.setUp(test)
+ setup.setUpTestAsModule(test, 'zope.app.apidoc.preference.README')
+
+def tearDown(test):
+ testing.tearDown(test)
+ setup.tearDownTestAsModule(test)
+
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('README.txt',
More information about the Zope3-Checkins
mailing list