[Checkins] SVN: z3ext.preferences/ initial import

Nikolay Kim fafhrd at datacom.kz
Tue Mar 25 07:09:06 EDT 2008


Log message for revision 84917:
  initial import

Changed:
  A   z3ext.preferences/
  A   z3ext.preferences/branches/
  A   z3ext.preferences/tags/
  A   z3ext.preferences/trunk/
  A   z3ext.preferences/trunk/AUTHOR.txt
  A   z3ext.preferences/trunk/CHANGES.txt
  A   z3ext.preferences/trunk/LICENSE.txt
  A   z3ext.preferences/trunk/bootstrap.py
  A   z3ext.preferences/trunk/buildout.cfg
  A   z3ext.preferences/trunk/setup.py
  A   z3ext.preferences/trunk/src/
  A   z3ext.preferences/trunk/src/z3ext/
  A   z3ext.preferences/trunk/src/z3ext/__init__.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/
  A   z3ext.preferences/trunk/src/z3ext/preferences/README.txt
  A   z3ext.preferences/trunk/src/z3ext/preferences/__init__.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/__init__.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/breadcrumb.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/configure.zcml
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/edit.pt
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/group.pt
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/group.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/index.pt
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/index.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/interfaces.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/layout.pt
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/layoutcontent.pt
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.pt
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/preferences.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/view.pt
  A   z3ext.preferences/trunk/src/z3ext/preferences/browser/view.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/configure.zcml
  A   z3ext.preferences/trunk/src/z3ext/preferences/i18n.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/interfaces.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/meta.zcml
  A   z3ext.preferences/trunk/src/z3ext/preferences/preference.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/preferencetype.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/publisher.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/root.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/tests.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/utils.py
  A   z3ext.preferences/trunk/src/z3ext/preferences/zcml.py

-=-

Property changes on: z3ext.preferences/trunk
___________________________________________________________________
Name: svn:ignore
   + bin
develop-eggs
eggs
parts
coverage
.installed.cfg


Added: z3ext.preferences/trunk/AUTHOR.txt
===================================================================
--- z3ext.preferences/trunk/AUTHOR.txt	                        (rev 0)
+++ z3ext.preferences/trunk/AUTHOR.txt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1 @@
+Nikolay Kim (fafhrd91 <at> gmail <dot> com)

Added: z3ext.preferences/trunk/CHANGES.txt
===================================================================
--- z3ext.preferences/trunk/CHANGES.txt	                        (rev 0)
+++ z3ext.preferences/trunk/CHANGES.txt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,30 @@
+=======
+CHANGES
+=======
+
+1.0.0 (2008-03-25)
+------------------
+
+- Tests added
+
+- Code moved to svn.zope.org
+
+
+0.9.2 (2008-02-29)
+------------------
+
+- Use z3c.autoinclude
+
+- Added z3c.baseregistry support
+
+
+0.9.1 (2008-02-20)
+------------------
+
+- Use z3ext.layoutform
+
+
+0.9.0 (2008-02-01)
+------------------
+
+- Initial release.

Added: z3ext.preferences/trunk/LICENSE.txt
===================================================================
--- z3ext.preferences/trunk/LICENSE.txt	                        (rev 0)
+++ z3ext.preferences/trunk/LICENSE.txt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+   accompanying copyright notice, this list of conditions,
+   and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+   copyright notice, this list of conditions, and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from the copyright
+   holders.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use
+   Servicemarks (sm) or Trademarks (tm) of the copyright
+   holders. Use of them is covered by separate agreement
+   with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+  AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+  NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+  DAMAGE.

Added: z3ext.preferences/trunk/bootstrap.py
===================================================================
--- z3ext.preferences/trunk/bootstrap.py	                        (rev 0)
+++ z3ext.preferences/trunk/bootstrap.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)


Property changes on: z3ext.preferences/trunk/bootstrap.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/buildout.cfg
===================================================================
--- z3ext.preferences/trunk/buildout.cfg	                        (rev 0)
+++ z3ext.preferences/trunk/buildout.cfg	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,18 @@
+[buildout]
+develop = .
+parts = test coverage-test coverage-report
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3ext.preferences [test]
+
+[coverage-test]
+recipe = zc.recipe.testrunner
+eggs = z3ext.preferences [test]
+defaults = ['--coverage', '../../coverage']
+
+[coverage-report]
+recipe = zc.recipe.egg
+eggs = z3c.coverage
+scripts = coverage=coverage-report
+arguments = ('coverage', 'coverage/report')

Added: z3ext.preferences/trunk/setup.py
===================================================================
--- z3ext.preferences/trunk/setup.py	                        (rev 0)
+++ z3ext.preferences/trunk/setup.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,85 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+"""Setup for z3ext.preferences package
+
+$Id$
+"""
+import sys, os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+version='1.0.0dev'
+
+
+setup(name = 'z3ext.preferences',
+      version = version,
+      author = 'Nikolay Kim',
+      author_email = 'fafhrd91 at gmail.com',
+      description = "z3ext principal preferences",
+      long_description = (
+        'Detailed Documentation\n' +
+        '======================\n'
+        + '\n\n' +
+        read('src', 'z3ext', 'preferences', 'README.txt')
+        + '\n\n' +
+        read('CHANGES.txt')
+        ),
+      classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Zope Public License',
+        'Programming Language :: Python',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Topic :: Internet :: WWW/HTTP',
+        'Framework :: Zope3'],
+      url='http://z3ext.net/',
+      license='ZPL 2.1',
+      packages=find_packages('src'),
+      package_dir = {'':'src'},
+      namespace_packages=['z3ext'],
+      install_requires = ['setuptools',
+                          'ZODB3',
+                          'zope.component',
+                          'zope.interface',
+                          'zope.annotation',
+                          'zope.publisher',
+                          'zope.configuration',
+                          'zope.schema',
+                          'zope.location',
+                          'zope.security',
+                          'zope.securitypolicy',
+			  'zope.cachedescriptors',
+			  'zope.pagetemplate',
+                          'zope.i18nmessageid',
+			  'zope.app.security',
+                          'zope.app.component',
+			  'zope.app.publisher',
+			  'zope.app.pagetemplate',
+			  'zope.app.principalannotation',
+                          'z3c.traverser',
+			  'z3c.autoinclude',
+			  'z3ext.layout',
+			  'z3ext.layoutform',
+                          'z3ext.statusmessage',
+                          ],
+      extras_require = dict(test=['zope.app.testing',
+                                  'zope.testing',
+                                  ]),
+      include_package_data = True,
+      zip_safe = False
+      )


Property changes on: z3ext.preferences/trunk/setup.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/__init__.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/__init__.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/__init__.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,6 @@
+# namespace package boilerplate
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError, e:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)


Property changes on: z3ext.preferences/trunk/src/z3ext/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/README.txt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/README.txt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/README.txt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,429 @@
+================
+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 z3ext.preferences import interfaces, preference, preferencetype
+
+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 are specified via the preferences group schema:
+
+  >>> import zope.schema
+  >>> import zope.interface
+
+  >>> 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)
+
+Each preference group must have an
+ID by which it can be accessed and optional title and description fields for UI
+purposes. Before create preference group we should create unique class for
+our preferences:
+
+  >>> settingsClass = preferencetype.PreferenceType(
+  ...     "ZMISettings",
+  ...     IZMIUserSettings,
+  ...     title=u"ZMI User Settings", description=u"")
+
+Now we can instantiate the preference group.
+
+  >>> settings = settingsClass()
+
+We can't change schema for preference group:
+
+  >>> settings.__schema__ = IZMIUserSettings
+  Traceback (most recent call last):
+  ...
+  AttributeError: Can't set __schema__
+
+
+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__
+  u'ZMISettings'
+  >>> settings.__schema__
+  <InterfaceClass z3ext.preferences.README.IZMIUserSettings>
+  >>> settings.__title__
+  u'ZMI User Settings'
+
+So let's ask the preference group for the `skin` setting:
+
+  >>> settings.skin
+  Traceback (most recent call last):
+  ...
+  UnboundPreferenceGroup
+
+
+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 principal:
+
+  >>> 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 an IAnnotations adapter for principals, so we can store the
+settings:
+
+  >>> from zope.annotation.interfaces import IAnnotations
+  >>> class PrincipalAnnotations(dict):
+  ...     zope.interface.implements(IAnnotations)
+  ...     data = {}
+  ...     def __new__(class_, principal, context):
+  ...         try:
+  ...             annotations = class_.data[principal.id]
+  ...         except KeyError:
+  ...             annotations = dict.__new__(class_)
+  ...             class_.data[principal.id] = annotations
+  ...         return annotations
+  ...     def __init__(self, principal, context):
+  ...         pass
+
+  >>> from zope import component
+  >>> component.provideAdapter(
+  ...     PrincipalAnnotations,
+  ...     (Principal, zope.interface.Interface), IAnnotations)
+
+And now we need bind preferences to principal. We can just call __bind__
+method in this case preference will use principal from current interaction.
+
+  >>> nsettings = settings.__bind__()
+  >>> interfaces.IBound.providedBy(nsettings)
+  True
+
+  >>> nsettings.__principal__ == principal
+  True
+
+Or we can explicitly set principal, this is usefull when we want to know
+preferences for principal.
+
+  >>> settings = settings.__bind__(principal=principal)
+  >>> interfaces.IBound.providedBy(nsettings)
+  True
+
+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')
+
+  >>> folderSettingsClass = preferencetype.PreferenceType(
+  ...     "ZMISettings.Folder",
+  ...     IFolderSettings,
+  ...     title=u"Folder Content View Settings")
+
+  >>> folderSettings = folderSettingsClass()
+
+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):
+  ...
+  KeyError: 'Folder'
+
+but not before we register the groups as utilities:
+
+  >>> from zope import component
+  >>> siteManager = component.getSiteManager()
+
+  >>> siteManager.registerUtility(
+  ...     settings, interfaces.IPreferenceGroup, 'ZMISettings')
+  >>> siteManager.registerUtility(
+  ...     folderSettings, interfaces.IPreferenceGroup, 'ZMISettings.Folder')
+
+If we now try to lookup the sub-group again, we should be successful:
+
+  >>> settings['Folder']
+  <z3ext.preferences.preferencetype.Preference<ZMISettings.Folder> ...>
+
+In z3ext.preferences we can't access to subfolder as attribute, this
+is one of difference from zope.app.preference.
+
+  >>> settings['Folder'].sortedBy = 'size'
+  >>> settings['Folder'].sortedBy
+  'size'
+
+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
+
+Clear:
+
+  >>> t = siteManager.unregisterUtility(
+  ...     settings, interfaces.IPreferenceGroup, 'ZMISettings')
+  >>> t = siteManager.unregisterUtility(
+  ...     folderSettings, interfaces.IPreferenceGroup, 'ZMISettings.Folder')
+
+
+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 z3ext.preferences
+  >>> context = xmlconfig.file('meta.zcml', z3ext.preferences)
+
+Second we need root preference group:
+
+  >>> from z3ext.preferences.root import PersonalPreferences
+
+  >>> siteManager.registerUtility(
+  ...     PersonalPreferences(), interfaces.IPreferenceGroup)
+
+Then the system sets up a root preference group:
+
+  >>> context = xmlconfig.string('''
+  ... <configure
+  ...    xmlns:z3ext="http://namespaces.zope.org/z3ext" i18n_domain="test">
+  ... 
+  ...   <z3ext:preferenceGroup
+  ...     id="ZMISettings"
+  ...     schema="z3ext.preferences.README.IZMIUserSettings"
+  ...     title="ZMI User Settings" />
+  ... 
+  ...   <z3ext:preferenceGroup
+  ...     id="ZMISettings.Folder"
+  ...     schema="z3ext.preferences.README.IFolderSettings"
+  ...     title="Folder Content View Settings" />
+  ...
+  ... </configure>''', context)
+
+Now we can use the preference system in its intended way. We access the folder
+settings as follows:
+
+  >>> prefs = component.getUtility(interfaces.IPreferenceGroup)
+
+Don't forget to bind preferences to principal
+
+  >>> prefs = prefs.__bind__(principal=principal)
+  >>> prefs['ZMISettings']['Folder'].sortedBy
+  'size'
+
+  >>> prefs.items()
+  [(u'ZMISettings', <z3ext.preferences.preferencetype.Preference<ZMISettings> ...>)]
+
+  >>> u'ZMISettings' in prefs
+  True
+
+  >>> prefs.keys()
+  (u'ZMISettings',)
+
+  >>> prefs.values()
+  [<z3ext.preferences.preferencetype.Preference<ZMISettings> ...>]
+
+  >>> list(iter(prefs))
+  [<z3ext.preferences.preferencetype.Preference<ZMISettings> ...>]
+
+  >>> len(prefs)
+  1
+
+
+Let's register the ZMI settings again under a new name via ZCML:
+
+  >>> context = xmlconfig.string('''
+  ... <configure
+  ...   xmlns:z3ext="http://namespaces.zope.org/z3ext"
+  ...   i18n_domain="test">
+  ...
+  ...   <z3ext:preferenceGroup
+  ...      id="ZMISettings2"
+  ...      title="ZMI Settings NG"
+  ...      schema="z3ext.preferences.README.IZMIUserSettings"
+  ...      provides="z3ext.preferences.interfaces.IPreferenceCategory" />
+  ...
+  ...     </configure>''', context)
+
+  >>> prefs['ZMISettings2']
+  <z3ext.preferences.preferencetype.Preference<ZMISettings2> ...>
+
+  >>> 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 constructing the id:
+
+  >>> context = xmlconfig.string('''
+  ... <configure
+  ...   xmlns:z3ext="http://namespaces.zope.org/z3ext"
+  ...   i18n_domain="test">
+  ...
+  ...   <z3ext:preferenceGroup
+  ...     id="ZMISettings2.Folder"
+  ...     title="Folder Settings"
+  ...     schema="z3ext.preferences.README.IFolderSettings" />
+  ...
+  ...     </configure>''', context)
+
+  >>> prefs['ZMISettings2']
+  <z3ext.preferences.preferencetype.Preference<ZMISettings2> ...>
+
+  >>> prefs['ZMISettings2'].items()
+  [(u'ZMISettings2.Folder', <z3ext.preferences.preferencetype.Preference<ZMISettings2.Folder> ...)]
+
+  >>> list(iter(prefs['ZMISettings2']))
+  [<z3ext.preferences.preferencetype.Preference<ZMISettings2.Folder> ...>]
+
+  >>> 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:
+
+  >>> prefs2 = component.getUtility(interfaces.IPreferenceGroup, 'ZMISettings.Folder')
+  >>> prefs2 = prefs2.__bind__(principal=principal)
+
+  >>> prefs2.sortedBy
+  'size'
+
+
+Security
+--------
+
+You might already wonder under which permissions the preferences are
+available. They are actually available publicly (`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? But sometimes we need preferences which can be changed
+only by manager. In this case we can provide default permission or
+even set security checks on field level, like in <class /> directive.
+
+  >>> import zope.security
+  >>> context = xmlconfig.file('meta.zcml', zope.security, context)
+
+  >>> context = xmlconfig.string('''
+  ... <configure
+  ...   xmlns="http://namespaces.zope.org/zope"
+  ...   xmlns:z3ext="http://namespaces.zope.org/z3ext"
+  ...   i18n_domain="z3ext">
+  ... 
+  ...   <permission id="zope.View" title="zope view" />
+  ...   <permission id="zope.Manage" title="zope manage" />
+  ... 
+  ...   <z3ext:preferenceGroup
+  ...      id="ZMISettings3"
+  ...      title="ZMI Settings 3"
+  ...      schema="z3ext.preferences.README.IZMIUserSettings"
+  ...      provides="z3ext.preferences.interfaces.IPreferenceCategory"
+  ...      permission="zope.View">
+  ...    <require
+  ...      attributes="showZopeLogo" permission="zope.Manage" />
+  ...   </z3ext:preferenceGroup>
+  ...
+  ... </configure>''', context)
+

Added: z3ext.preferences/trunk/src/z3ext/preferences/__init__.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/__init__.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/__init__.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1 @@
+# This file is necessary to make this directory a package.


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/__init__.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/__init__.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/__init__.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1 @@
+# This file is necessary to make this directory a package.


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/breadcrumb.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/breadcrumb.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/breadcrumb.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,33 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+""" custom IBreadcrumb implementation for IPreferencesGroup
+
+$Id$
+"""
+from zope import interface, component
+from z3c.breadcrumb.browser import GenericBreadcrumb
+
+from z3ext.preferences.i18n import _
+from z3ext.preferences.interfaces import IPreferenceGroup
+
+
+class PreferenceGroupBreadcrumb(GenericBreadcrumb):
+    component.adapts(IPreferenceGroup, interface.Interface)
+    
+    @property
+    def name(self):
+        name = self.context.__title__ or self.context.__id__
+        if not name:
+            name = _(u'Preferences')
+        return name


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/breadcrumb.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/configure.zcml
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/configure.zcml	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/configure.zcml	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,68 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:zcml="http://namespaces.zope.org/zcml"
+   xmlns:z3ext="http://namespaces.zope.org/z3ext"
+   xmlns:browser="http://namespaces.zope.org/browser"
+   i18n_domain="z3ext">
+
+  <z3ext:layout
+     layout="portal"
+     name="workspace"
+     for="..interfaces.IRootPreferences"
+     template="layout.pt"
+     hidden="yes" />
+
+  <z3ext:layout
+     layout="workspace"
+     for="..interfaces.IPreferenceCategory"
+     template="layoutcontent.pt"
+     hidden="yes" />
+
+  <!-- browser view -->
+  <adapter
+     name="preferences"
+     provides="zope.publisher.interfaces.browser.IBrowserView"
+     factory=".preferences.getPreferences" />
+
+  <browser:defaultView
+     name="index.html"
+     for="..interfaces.IPreferenceGroup" />
+
+  <browser:menuItem
+     action="index.html"
+     for="..interfaces.IPreferenceGroup"
+     menu="zmi_views"
+     title="View"
+     permission="zope.Public" />
+
+  <z3ext:pagelet
+     for="..interfaces.IRootPreferences"
+     name="index.html"
+     template="index.pt"
+     class=".index.PreferencesView"
+     permission="zope.View" />
+
+  <browser:page
+     name="index.html"
+     for="..interfaces.IPreferenceGroup"
+     class=".group.PreferenceGroup"
+     permission="zope.Public" />
+
+  <browser:view
+     for="..interfaces.IPreferenceGroup"
+     class=".view.PreferenceGroupView"
+     provides=".interfaces.IPreferenceGroupView"
+     permission="zope.Public" />
+
+  <adapter
+     zcml:condition="installed z3c.breadcrumb"
+     factory=".breadcrumb.PreferenceGroupBreadcrumb" />
+
+  <browser:page
+     name="navigation"
+     for="..interfaces.IPreferenceGroup"
+     template="navigation.pt"
+     class=".navigation.Navigation"
+     permission="zope.Public" />
+
+</configure>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/edit.pt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/edit.pt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/edit.pt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,13 @@
+<div class="frame">
+  <h1 tal:content="view/label|nothing">Do something</h1>
+  <div class="discreet" tal:content="view/description|nothing"></div>
+  <br />
+
+  <div class="z-form-fieldset">
+    <div>
+      <tal:block tal:repeat="widget view/widgets/values">
+	<metal:block use-macro="macro:widget-row"/>
+      </tal:block>
+    </div>
+  </div>
+</div>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/group.pt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/group.pt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/group.pt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,34 @@
+<form action="." metal:define-macro="master" i18n:domain="z3ext"
+      tal:attributes="action request/URL" method="post"
+      tal:omit-tag="view/subform"
+      class="edit-form" enctype="multipart/form-data">
+
+  <tal:block tal:content="structure view/form_result" />
+  
+  <tal:block tal:condition="view/editable">
+    <tal:block repeat="group view/subgroups">
+      <div tal:condition="nocall:group/view"
+	   tal:content="structure python:group['view'].render(True)"></div>
+      <br />
+    </tal:block>
+  </tal:block>
+
+  <div class="topframe" tal:condition="not:view/editable">
+    <tal:block repeat="group view/subgroups">
+      <div tal:condition="nocall:group/view"
+	   tal:content="structure group/view/render"></div>
+      <br />
+    </tal:block>
+  </div>
+
+  <div class="formControls"
+       tal:condition="python:view.editable and not view.subform">
+    <hr />
+    <span class="actionButtons">
+      <input type="submit" 
+	     id="form-buttons-save" name="form.buttons.save"
+	     class="z-form-savebutton button-field" value="Save"
+	     i18n:attributes="value" />
+    </span>
+  </div>
+</form>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/group.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/group.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/group.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,103 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+""" IPreferenceGroup view
+
+$Id$
+"""
+from zope import event, schema, interface
+from zope.component import getMultiAdapter, queryMultiAdapter
+from zope.cachedescriptors.property import Lazy
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.pagetemplate.interfaces import IPageTemplate
+
+from z3ext.layoutform import Fields, PageletEditForm
+from z3ext.preferences.interfaces import IPreferenceCategory
+
+from interfaces import IPreferenceGroupView
+
+
+class PreferenceGroup(PageletEditForm):
+
+    edit = None
+    editable = False
+
+    group = ViewPageTemplateFile('group.pt')
+    template = ViewPageTemplateFile('edit.pt')
+
+    @property
+    def label(self):
+        return self.context.__title__
+
+    @property
+    def description(self):
+        return self.context.__description__
+
+    @Lazy
+    def fields(self):
+        return Fields(self.context.__schema__)
+
+    def update(self):
+        context = self.context
+        request = self.request
+
+        if IPreferenceCategory.providedBy(context):
+            subgroups = []
+
+            for name, group in context.items():
+                if not group.isAvailable():
+                    continue
+
+                view = queryMultiAdapter((group, request), IPreferenceGroupView)
+                subgroups.append((group.__title__, group, view))
+
+            self.subgroups = [{'group': group, 'view': view}
+                              for t, group, view in subgroups]
+        else:
+            subgroups = []
+            for name, group in context.items():
+                if not group.isAvailable():
+                    continue
+
+                view = queryMultiAdapter((group, request), name='index.html')
+                if not view:
+                    continue
+
+                view.update()
+                subgroups.append((group.__title__, group, view))
+
+            self.subgroups = [{'group': group, 'view': view}
+                              for t, group, view in subgroups]
+
+            if subgroups:
+                self.editable = True
+
+        self.hasFields = bool(schema.getFields(context.__schema__))
+        if self.hasFields:
+            super(PreferenceGroup, self).update()
+
+    form_result = u''
+
+    def render(self, subform=False):
+        self.subform = subform
+
+        if self.hasFields:
+            self.editable = True
+            if self.template is None:
+                template = getMultiAdapter(
+                    (self, self.request), IPageTemplate)
+                self.form_result = template(self)
+            else:
+                self.form_result = self.template()
+
+        return self.group()


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/group.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/index.pt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/index.pt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/index.pt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,36 @@
+<tal:block tal:define="img context/++resource++z3ext-images/bullet2.gif|nothing">
+  <div class="frame">
+    <ul class="listing">
+      <li tal:repeat="item view/groups">
+	<tal:block tal:define="group item/group">
+	  <div class="icon" tal:define="icon group/@@zmi_icon|nothing"
+	       tal:condition="python:icon or img">
+	    <tal:block tal:condition="icon" tal:content="structure icon"/>
+	    <img tal:condition="not:icon" tal:attributes="src img" />
+	  </div>
+	  <div class="details">
+	    <a tal:attributes="href string:${group/__id__}/">
+	      <tal:block tal:content="group/__title__"/></a>
+	    <div><tal:block tal:content="group/__description__"/> &nbsp;</div>
+	    
+	    <ul class="listing" tal:condition="item/subgroups">
+	      <li tal:repeat="sgroup item/subgroups">
+		<div class="icon" tal:define="icon sgroup/group/@@zmi_icon|nothing"
+		     tal:condition="python:icon or img">
+		  <tal:block tal:condition="icon" tal:content="structure icon" />
+		  <img tal:condition="not:icon" tal:attributes="src img"/>
+		</div>
+		<div class="details">
+		  <a tal:attributes="href string:${group/__id__}/${sgroup/id}/">
+		    <tal:block tal:content="sgroup/group/__title__"/></a>
+		  <div><tal:block tal:content="sgroup/group/__description__"/> 
+		    &nbsp;</div>
+		</div>
+	      </li>
+	    </ul>
+	  </div>
+	</tal:block>
+      </li>
+    </ul>
+  </div>
+</tal:block>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/index.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/index.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/index.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from zope.interface import Interface
+from zope.component import getUtility
+from zope.security import checkPermission
+from z3ext.preferences.interfaces import IPreferenceGroup, IPreferenceCategory
+
+
+class PreferencesView(object):
+
+    def groups(self):
+        root = self.context
+        request = self.request
+
+        groups = []
+        for name, group in root.items():
+            if not group.isAvailable():
+                continue
+
+            if IPreferenceCategory.providedBy(group):
+                subgroups = [(sgroup.__title__,
+                              sgroup.__id__.split('.')[-1], sgroup)
+                             for t, sgroup in group.items() 
+                             if sgroup.isAvailable()]
+                if subgroups:
+                    groups.append((group.__title__, group,
+                                   [{'id': id, 'group': sgroup}
+                                    for t, id, sgroup in subgroups]))
+            else:
+                groups.append((group.__title__, group, ()))
+
+        return [{'group':group, 'subgroups': groups}
+                for t, group, groups in groups]


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/index.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/interfaces.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/interfaces.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/interfaces.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,40 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+""" interfaces relateds to view preference groups
+
+$Id$
+"""
+from zope import interface
+
+
+class IPreferences(interface.Interface):
+    """ preferences """
+
+
+class IPreferenceGroupView(interface.Interface):
+    """ group view """
+
+    def isAvailable():
+        """ is this view available """
+
+    def render():
+        """ render view """
+
+
+class IPreferenceGroupPreview(IPreferenceGroupView):
+    """ group preview """
+
+
+class IDefaultPreferenceGroupView(interface.Interface):
+    """ default preference view """


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/layout.pt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/layout.pt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/layout.pt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,12 @@
+<div id="z-portal-workspace">
+  <div class="page"
+       tal:define="nav maincontext/@@navigation|nothing; rendered view/render; noNav not:nav">
+    <table class="wide" tal:omit-tag="noNav">
+      <tr style="vertical-align: top" tal:omit-tag="noNav">
+	<td style="padding-right: 1em; width: 200px"
+	    tal:condition="nav" tal:content="structure nav"></td>
+	<td tal:omit-tag="noNav" tal:content="structure rendered"></td>
+      </tr>
+    </table>
+  </div>
+</div>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/layoutcontent.pt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/layoutcontent.pt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/layoutcontent.pt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,6 @@
+<h1 tal:content="layoutcontext/__title__"></h1>
+<div class="pageDescription" tal:content="layoutcontext/__description__"></div>
+
+<div id="z-viewspace" tal:content="structure view/render">
+  [rendered body]
+</div>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.pt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.pt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.pt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,20 @@
+<div class="x-listing box small">
+  <tal:block tal:repeat="item view/data">
+    <metal:block metal:define-macro="level">
+      <div class="x-listing-item"
+	   tal:attributes="class python:item['selected'] and 'x-listing-item-selected' 
+			   or 'x-listing-item'">
+	<div tal:omit-tag="not:item/level|nothing"
+	     tal:attributes="class string:level${item/level|nothing}">
+	  <a tal:attributes="href string:${item/prefs/@@absolute_url}/">
+	    <al:block tal:content="structure item/icon" />
+	    <tal:block tal:content="item/title" />
+	  </a>
+	</div>
+      </div>
+      <tal:block tal:repeat="item item/items">
+	<metal:block use-macro="view/index/macros/level"/>
+      </tal:block>
+    </metal:block>
+  </tal:block>
+</div>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,79 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from zope.component import queryMultiAdapter
+from z3ext.preferences.interfaces import IPreferenceGroup, IRootPreferences
+
+
+class Navigation(object):
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+        self.isRoot = IRootPreferences.providedBy(context)
+        if self.isRoot:
+            return
+
+        path = []
+        parent = context
+        while IPreferenceGroup.providedBy(parent):
+            path.insert(0, parent)
+            parent = parent.__parent__
+
+        self.root, path = path[0], path[1:]
+
+        self.data = self._process(self.root, path)
+
+    def _process(self, context, path, level=1):
+        request = self.request
+
+        if path:
+            data = []
+            items = getattr(context, 'items', ())
+            if callable(items):
+                items = items()
+
+            for name, prefs in items:
+                if not prefs.isAvailable():
+                    continue
+
+                info = {'name': name,
+                        'title': prefs.__title__,
+                        'icon': queryMultiAdapter(
+                             (prefs, request), name='zmi_icon'),
+                        'items': (),
+                        'selected': False,
+                        'prefs': prefs,
+                        'level': level}
+
+                if prefs.__id__ == path[0].__id__:
+                    info['items'] = self._process(prefs, path[1:], level+1)
+
+                if prefs.__id__ == self.context.__id__:
+                    info['selected'] = True
+                    info['items'] = self._process(prefs, [prefs], level+1)
+
+                data.append(info)
+
+            return data
+
+    def __call__(self):
+        if self.isRoot:
+            return ''
+        else:
+            return self.index()


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/navigation.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/preferences.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/preferences.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/preferences.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from zope import interface, component
+from zope.component import getUtility
+from zope.app.component.interfaces import ISite
+from z3ext.preferences.interfaces import IPreferenceGroup
+
+
+ at component.adapter(ISite, interface.Interface)
+def getPreferences(site, request):
+    rootGroup = getUtility(IPreferenceGroup)
+    rootGroup = rootGroup.__bind__()
+    if rootGroup.isAvailable():
+        return rootGroup
+    else:
+        return None


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/preferences.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/view.pt
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/view.pt	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/view.pt	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,3 @@
+<h2><a tal:attributes="href string:${context/@@absolute_url}/"
+       tal:content="context/__title__"></a></h2>
+<div class="discreet" tal:content="context/__description__"></div>

Added: z3ext.preferences/trunk/src/z3ext/preferences/browser/view.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/browser/view.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/browser/view.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+""" 
+
+$Id$
+"""
+from zope import interface
+from zope.app.pagetemplate import ViewPageTemplateFile
+
+from interfaces import IDefaultPreferenceGroupView
+
+
+class PreferenceGroupView(object):
+    interface.implements(IDefaultPreferenceGroupView)
+
+    render = ViewPageTemplateFile('view.pt')


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/browser/view.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/configure.zcml
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/configure.zcml	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/configure.zcml	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,56 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:z3ext="http://namespaces.zope.org/z3ext"
+   i18n_domain="z3ext">
+
+  <autoinclude package="z3ext.preferences" />
+
+  <role
+     id="preference.Owner"
+     title="Preference group owner" />
+
+  <permission
+     id="z3ext.ModifyPreference"
+     title="Modifye preference" />
+
+  <grant
+     permission="z3ext.ModifyPreference"
+     role="preference.Owner" />
+
+  <!-- root preference group -->
+  <utility
+     provides=".interfaces.IPreferenceGroup"
+     factory=".root.PersonalPreferences" />
+
+  <class class=".root.PersonalPreferences">
+    <require
+       permission="zope.Public"
+       interface=".interfaces.IPreferenceGroup
+                  zope.interface.common.mapping.IEnumerableMapping" />
+  </class>
+
+  <class class=".preference.PreferenceGroup">
+    <implements interface="zope.annotation.interfaces.IAttributeAnnotatable" />
+  </class>
+
+  <!-- preference group publisher -->
+  <adapter
+     for=".interfaces.IPreferenceGroup *"
+     factory="z3c.traverser.traverser.PluggableTraverser"
+     provides="zope.publisher.interfaces.IPublishTraverse" />
+
+  <subscriber
+     for=".interfaces.IPreferenceGroup *"
+     provides="z3c.traverser.interfaces.ITraverserPlugin"
+     factory=".publisher.PreferenceTraverserPlugin" />
+
+  <!-- predefined preference group -->
+  <z3ext:preferenceGroup
+     id="portal"
+     title="Portal preferences"
+     description="These are all the preferences related to common portal settings."
+     provides="z3ext.preferences.interfaces.IPreferenceCategory" />
+
+  <include package=".browser" />
+
+</configure>

Added: z3ext.preferences/trunk/src/z3ext/preferences/i18n.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/i18n.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/i18n.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,19 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+""" i18n
+
+$Id$
+"""
+from zope.i18nmessageid import MessageFactory
+_ = MessageFactory('z3ext')


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/i18n.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/interfaces.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/interfaces.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/interfaces.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+""" z3ext.preferences interfaces
+
+$Id$
+"""
+from zope import schema, interface
+from zope.configuration import fields
+from zope.location.interfaces import ILocation
+
+
+class UnboundPreferenceGroup(Exception):
+    """ Prefernce group is not bound to principal """
+
+
+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__ = schema.TextLine(
+        title = u"Id",
+        description = u"The id of the group.",
+        required = True)
+
+    __schema__ = schema.InterfaceField(
+        title = u"Schema",
+        description = u"Schema describing the preferences of the group.",
+        required = False,
+        readonly = True)
+
+    __title__ = fields.MessageID(
+        title = u"Title",
+        description = u"The title of the group used in the UI.",
+        required = True)
+
+    __description__ = fields.MessageID(
+        title = u"Description",
+        description = u"The description of the group used in the UI.",
+        required = False)
+
+    __principal__ = interface.Attribute('Owner principal of preferences')
+
+    def isAvailable():
+        """ is group available for bound principal """
+
+    def add(name):
+        """ add subgroup name """
+
+    def remove(name):
+        """ remove subgroup name """
+
+
+class IPreferenceCategory(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 IBound(interface.Interface):
+    """ bound to context """
+
+    __principal__ = interface.Attribute('IPrincipal object')
+
+
+class IRootPreferences(interface.Interface):
+    """ root preferences """
+
+    def __bind__(parent=None, principal=None):
+        """ bind preferences """


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/meta.zcml
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/meta.zcml	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/meta.zcml	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,23 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:meta="http://namespaces.zope.org/meta">
+  
+  <meta:directives namespace="http://namespaces.zope.org/z3ext">
+
+    <meta:complexDirective
+       name="preferenceGroup"
+       schema=".zcml.IPreferenceGroupDirective"
+       handler=".zcml.PreferenceGroupDirective">
+
+      <meta:subdirective
+         name="allow"
+         schema="zope.app.component.metadirectives.IAllowSubdirective" />
+      
+      <meta:subdirective
+         name="require"
+         schema="zope.app.component.metadirectives.IRequireSubdirective" />
+    </meta:complexDirective>
+
+  </meta:directives>
+
+</configure>

Added: z3ext.preferences/trunk/src/z3ext/preferences/preference.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/preference.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/preference.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,184 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from BTrees.OOBTree import OOBTree
+
+from zope import interface
+from zope.interface.common.mapping import IEnumerableMapping
+from zope.component import getGlobalSiteManager
+from zope.component import queryUtility, getMultiAdapter
+from zope.cachedescriptors.property import Lazy
+from zope.security.management import getInteraction
+from zope.annotation.interfaces import IAnnotations
+from zope.app.component.hooks import getSite
+from zope.app.security.interfaces import IAuthentication, PrincipalLookupError
+
+from interfaces import IPreferenceGroup, IBound, UnboundPreferenceGroup
+
+pref_key = 'zope.app.user.UserPreferences'
+
+_marker = object()
+
+
+class PreferenceGroup(object):
+    interface.implements(IPreferenceGroup, IEnumerableMapping)
+
+    __principal__ = None
+
+    def __init__(self, tests=()):
+        self.__name__ = self.__id__.rsplit('.', 1)[-1]
+        self.__tests__ = tests
+        self.__subgroups__ = ()
+
+    def __bind__(self, parent=None, principal=None):
+        clone = self.__class__.__new__(self.__class__)
+        clone.__dict__.update(self.__dict__)
+
+        if parent is None:
+            parent = getSite()
+
+        clone.__parent__ = parent
+
+        if principal is None:
+            if IBound.providedBy(parent):
+                clone.__principal__ = parent.__principal__
+            else:
+                principal = getInteraction().participations[0].principal
+
+                auth = queryUtility(IAuthentication)
+                if auth is not None:
+                    try:
+                        principal = auth.getPrincipal(principal.id)
+                    except PrincipalLookupError:
+                        pass
+
+                clone.__principal__ = principal
+        else:
+            clone.__principal__ = principal
+
+        interface.alsoProvides(clone, IBound)
+        return clone
+
+    @Lazy
+    def data(self):
+        if not IBound.providedBy(self):
+            raise UnboundPreferenceGroup()
+
+        ann = getMultiAdapter((self.__principal__, self), IAnnotations)
+
+        # 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__]
+
+    def isAvailable(self):
+        if IPreferenceGroup.providedBy(self.__parent__):
+            if not self.__parent__.isAvailable():
+                return False
+
+        for test in self.__tests__:
+            if callable(test):
+                if not test(self):
+                    return False
+            elif not bool(test):
+                return False
+
+        return True
+
+    def add(self, name):
+        if name not in self.__subgroups__:
+            self.__subgroups__ = self.__subgroups__ + (name,)
+
+            id = self.__id__
+            if id:
+                id = id + '.'
+
+            items = []
+            for grp_id in self.__subgroups__:
+                name = id + grp_id
+
+                group = queryUtility(IPreferenceGroup, name)
+                if group is None:
+                    group = getGlobalSiteManager().queryUtility(
+                        IPreferenceGroup, name)
+
+                if group is not None:
+                    items.append((group.order, group.__title__, grp_id))
+
+            items.sort()
+            self.__subgroups__ = tuple([id for o,t,id in items])
+
+    def remove(self, name):
+        if name in self.__subgroups__:
+            names = list(self.__subgroups__)
+            names.remove(name)
+            self.__subgroups__ = tuple(names)
+
+    def get(self, key, default=None):
+        id = self.__id__ and self.__id__ + '.' + key or key
+        group = queryUtility(IPreferenceGroup, id, default)
+        if group is default:
+            return default
+        return group.__bind__(self)
+
+    def items(self):
+        id = self.__id__
+        if id:
+            id = id + '.'
+
+        items = []
+        for key in self.keys():
+            name = id + key
+            group = queryUtility(IPreferenceGroup, name)
+            if group is not None:
+                items.append((name, group.__bind__(self)))
+        return items
+
+    def __getitem__(self, key):
+        obj = self.get(key, _marker)
+        if obj is _marker:
+            raise KeyError(key)
+        return obj
+
+    def __contains__(self, key):
+        return key in self.keys()
+
+    def keys(self):
+        return self.__subgroups__
+
+    def __iter__(self):
+        id = self.__id__
+        if id:
+            id = id + '.'
+
+        for key in self.keys():
+            name = id + key
+            group = queryUtility(IPreferenceGroup, name)
+            if group is not None:
+                yield group.__bind__(self)
+
+    def values(self):
+        return [group for id, group in self.items()]
+
+    def __len__(self):
+        return len(self.keys())


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/preference.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/preferencetype.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/preferencetype.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/preferencetype.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,198 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+""" PreferenceGroup metaclass
+
+$Id$
+"""
+import sys
+from zope import interface
+from zope.schema import getFields
+
+from z3ext.preferences.i18n import _
+from z3ext.preferences.preference import PreferenceGroup
+
+_marker = object()
+
+
+class PreferenceType(type):
+    """ Metaclass for all preference groups
+
+    >>> from zope import interface, schema
+    >>> from z3ext.preferences import preferencetype
+
+    >>> class IMyPreference(interface.Interface):
+    ...   title = schema.TextLine(title = u'Title')
+    
+    >>> class MyPreference(object):
+    ...   pass
+
+    >>> PreferenceClass = preferencetype.PreferenceType(
+    ...    'mypreference', IMyPreference, MyPreference, 'MyPreference', '')
+
+    New class avilable by it's cname in z3ext.preferences.preferencetype module
+
+    >>> getattr(preferencetype, 'Preference<mypreference>') is PreferenceClass
+    True
+
+    Automaticly generate schema fields to PreferenceProperty
+
+    >>> PreferenceClass.title
+    <z3ext.preferences.preferencetype.PreferenceProperty object at ...>
+
+    >>> preference = PreferenceClass()
+    >>> preference
+    <z3ext.preferences.preferencetype.Preference<mypreference> object at ...>
+
+    >>> isinstance(preference, MyPreference)
+    True
+
+    >>> isinstance(preference, preferencetype.PreferenceGroup)
+    True
+
+    We also can use number of base classes
+
+    >>> class MyPreference2(object):
+    ...   pass
+
+    >>> PreferenceClass = preferencetype.PreferenceType(
+    ...    'mypreference', IMyPreference, 
+    ...    (MyPreference, MyPreference2), 'MyPreference', '')
+
+    """
+
+    def __new__(cls, name, schema, class_=None, *args, **kw):
+        cname = 'Preference<%s>'%name
+        if type(class_) is tuple:
+            bases = class_ + (PreferenceGroup,)
+        elif class_ is not None:
+            bases = (class_, PreferenceGroup)
+        else:
+            bases = (PreferenceGroup,)
+
+        tp = type.__new__(cls, str(cname), bases, {})
+        setattr(sys.modules['z3ext.preferences.preferencetype'], cname, tp)
+        
+        return tp
+
+    def __init__(cls, name, schema, class_=None, title='', description=''):
+        for f_id in getFields(schema):
+            if not hasattr(cls, f_id):
+                setattr(cls, f_id, PreferenceProperty(schema[f_id]))
+
+        cls.__id__ = unicode(name)
+        cls.__title__ = title
+        cls.__description__ = description
+        cls.__schema__ = DataProperty(schema)
+        interface.classImplements(cls, schema)
+
+
+class DataProperty(object):
+
+    def __init__(self, schema):
+        self.schema = schema
+
+    def __get__(self, inst, klass):
+        return self.schema
+
+    def __set__(self, inst, value):
+        raise AttributeError("Can't set __schema__")
+
+
+class PreferenceProperty(object):
+    """ Special property thats reads and writes values from 
+    instance's 'data' attribute
+
+    Let's define simple schema field
+
+    >>> from zope import schema
+    >>> field = schema.TextLine(
+    ...    title = u'Test',
+    ...    default = u'default value')
+    >>> field.__name__ = 'attr1'
+
+    Now we need content class
+
+    >>> from z3ext.preferences.preferencetype import PreferenceProperty
+    >>> class Content(object):
+    ...
+    ...    attr1 = PreferenceProperty(field)
+    
+    Lets create class instance and add field values storage
+
+    >>> ob = Content()
+    >>> ob.data = {}
+    
+    By default we should get field default value
+
+    >>> ob.attr1
+    u'default value'
+
+    We can set only valid value 
+
+    >>> ob.attr1 = 'value1'
+    Traceback (most recent call last):
+    ...
+    WrongType: ('value1', <type 'unicode'>)
+
+    >>> ob.attr1 = u'value1'
+    >>> ob.attr1
+    u'value1'
+
+    If storage contains field value we shuld get it
+
+    >>> ob.data['attr1'] = u'value2'
+    >>> ob.attr1
+    u'value2'
+
+    We can't set value for readonly fields
+
+    >>> field.readonly = True
+    >>> ob.attr1 = u'value1'
+    Traceback (most recent call last):
+    ...
+    ValueError: ('attr1', u'Field is readonly')
+
+    Remove attribute
+
+    >>> del ob.attr1
+
+    """
+
+    def __init__(self, field, name=None):
+        if name is None:
+            name = field.__name__
+
+        self._field = field
+        self._name = name
+
+    def __get__(self, inst, klass):
+        if inst is None:
+            return self
+
+        value = inst.data.get(self._name, _marker)
+        if value is _marker:
+            return self._field.default
+
+        return value
+
+    def __set__(self, inst, value):
+        field = self._field.bind(inst)
+        field.validate(value)
+        if field.readonly and self._name in inst.data:
+            raise ValueError(self._name, _(u'Field is readonly'))
+        inst.data[self._name] = value
+
+    def __delete__(self, inst):
+        if self._name in inst.data:
+            del inst.data[self._name]


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/preferencetype.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/publisher.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/publisher.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/publisher.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+""" 
+
+$Id$
+"""
+from zope import interface
+from zope.publisher.interfaces import NotFound
+from z3c.traverser.interfaces import ITraverserPlugin
+
+
+class PreferenceTraverserPlugin(object):
+    """A traverser that knows how to look up objects by name in a container."""
+    interface.implements(ITraverserPlugin)
+    
+    def __init__(self, container, request):
+        self.context = container
+        self.request = request
+
+    def publishTraverse(self, request, name):
+        """See zope.publisher.interfaces.IPublishTraverse"""
+        try:
+            subob = self.context.get(name, None)
+        except:
+            subob = None
+
+        if subob is None:
+            raise NotFound(self.context, name, request)
+
+        return subob


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/publisher.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/root.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/root.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/root.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,53 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+""" Root Preference Group
+
+$Id$
+"""
+from zope import interface
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from zope.app.security.interfaces import IUnauthenticatedPrincipal
+
+from i18n import _
+from preference import PreferenceGroup
+from interfaces import IRootPreferences, IPreferenceCategory
+
+
+class PersonalPreferences(PreferenceGroup):
+    interface.implements(IRootPreferences, IPreferenceCategory)
+
+    __id__ = ''
+    __name__ = u'preferences'
+    __title__ = _(u'Personal preferences')
+    __description__ = _('This area allows you to change personal preferences.')
+    __schema__ = IRootPreferences
+    __principal__ = None
+
+    def __init__(self):
+        self.__subgroups__ = ()
+
+    def isAvailable(self):
+        if IUnauthenticatedPrincipal.providedBy(self.__principal__):
+            return False
+        return True
+
+    def __bind__(self, parent=None, principal=None):
+        clone = super(PersonalPreferences, self).__bind__(parent, principal)
+
+        rmanager = IPrincipalRoleManager(clone, None)
+        if rmanager is not None:
+            rmanager.assignRoleToPrincipal(
+                'preference.Owner', clone.__principal__.id)
+
+        return clone


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/root.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/tests.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/tests.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/tests.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# 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$
+"""
+import unittest, doctest
+from zope.testing import doctestunit
+from zope.component import testing
+from zope.app.testing import setup
+
+def setUp(test):
+    testing.setUp(test)
+    setup.setUpTestAsModule(test, 'z3ext.preferences.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|doctest.ELLIPSIS),
+        doctest.DocTestSuite(
+            'z3ext.preferences.preferencetype',
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+        ))


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/utils.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/utils.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/utils.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from zope import interface
+from zope.security.interfaces import IPrincipal, IGroup, IMemberAwareGroup
+
+
+def isUser(group):
+    principal = group.__principal__
+    return IPrincipal.providedBy(principal) and not IGroup.providedBy(principal)
+
+
+def isGroup(group):
+    return IGroup.providedBy(group.__principal__)
+
+
+def isMemberAwareGroup(group):
+    return IMemberAwareGroup.providedBy(group.__principal__)


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/utils.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3ext.preferences/trunk/src/z3ext/preferences/zcml.py
===================================================================
--- z3ext.preferences/trunk/src/z3ext/preferences/zcml.py	                        (rev 0)
+++ z3ext.preferences/trunk/src/z3ext/preferences/zcml.py	2008-03-25 11:09:04 UTC (rev 84917)
@@ -0,0 +1,226 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+""" z3ext:preferenceGroup directive implementation
+
+$Id$
+"""
+from zope import interface
+from zope.schema import Int
+from zope.component import getUtility, queryUtility, getGlobalSiteManager
+from zope.schema.interfaces import IField
+
+from zope.security.zcml import Permission
+from zope.security.checker import Checker, CheckerPublic
+
+from zope.interface.common.mapping import IEnumerableMapping
+
+from zope.component.zcml import utility
+from zope.component.interface import provideInterface
+
+from zope.configuration import fields
+from zope.configuration.exceptions import ConfigurationError
+
+from zope.app.security.protectclass import \
+    protectName, protectSetAttribute, protectLikeUnto
+
+from interfaces import IPreferenceGroup
+from preference import PreferenceGroup
+from preferencetype import PreferenceType
+
+
+class IPreferenceGroupDirective(interface.Interface):
+    """Register a preference group."""
+
+    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=True)
+
+    schema = fields.GlobalInterface(
+        title=u"Schema",
+        description=u"Schema of the preference group used defining the "
+                    u"preferences of the group.",
+        required=False)
+
+    title = fields.MessageID(
+        title=u"Title",
+        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)
+
+    class_ = fields.GlobalObject(
+        title=u"Class",
+        description=u"Custom IPreferenceGroup implementation.",
+        required=False)
+
+    provides = fields.Tokens(
+	title = u'Provides',
+        required = False,
+        value_type = fields.GlobalInterface())
+
+    permission = Permission(
+        title = u'Permission',
+        description = u'Default access permission.',
+        required = False)
+
+    tests = fields.Tokens(
+	title = u"Tests",
+        description = u'Tests for check availability.',
+        value_type = fields.GlobalObject(),
+	required = False)
+
+    order = Int(
+        title = u'Order',
+        default = 999999,
+        required = False)
+
+
+class PreferenceGroupDirective(object):
+
+    def __init__(self, _context, id=None, schema=interface.Interface,
+                 title=u'', description=u'', category=False, 
+                 class_=None, provides=[], permission=CheckerPublic,
+                 tests=(), order = 9999):
+
+        Class = PreferenceType(str(id), schema, class_, title, description)
+        Class.order = order
+
+        group = Class(tests)
+
+        utility(_context, IPreferenceGroup, group, name=id)
+
+        interface.classImplements(Class, *provides)
+
+        self._class = Class
+        self._context = _context
+        self._permission = permission
+
+        self.require(_context, permission, interface=(IPreferenceGroup, schema))
+        self.require(_context, 'z3ext.ModifyPreference', set_schema=(schema,))
+        self.require(_context, CheckerPublic, interface=(IEnumerableMapping,))
+
+        _context.action(
+            discriminator=('z3ext:preferences', group),
+            callable=addSubgroup, args=(group,))
+
+    def require(self, _context,
+                permission=None, attributes=None, interface=None,
+                like_class=None, set_attributes=None, set_schema=None):
+        """Require a permission to access a specific aspect"""
+        if like_class:
+            self.__mimic(_context, like_class)
+
+        if not (interface or attributes or set_attributes or set_schema):
+            if like_class:
+                return
+            raise ConfigurationError("Nothing required")
+
+        if not permission:
+            raise ConfigurationError("No permission specified")
+
+        if interface:
+            for i in interface:
+                if i:
+                    self.__protectByInterface(i, permission)
+
+        if attributes:
+            self.__protectNames(attributes, permission)
+
+        if set_attributes:
+            self.__protectSetAttributes(set_attributes, permission)
+
+        if set_schema:
+            for s in set_schema:
+                self.__protectSetSchema(s, permission)
+
+    def __mimic(self, _context, class_):
+        """Base security requirements on those of the given class"""
+        _context.action(
+            discriminator=('z3ext:preferences:mimic', self._class),
+            callable=protectLikeUnto,
+            args=(self._class, class_),
+            )
+
+    def allow(self, _context, attributes=None, interface=None):
+        """Like require, but with permission_id zope.Public"""
+        return self.require(_context, self._permission, attributes, interface)
+
+    def __protectByInterface(self, interface, permission_id):
+        "Set a permission on names in an interface."
+        for n, d in interface.namesAndDescriptions(1):
+            self.__protectName(n, permission_id)
+
+        self._context.action(
+            discriminator = None,
+            callable = provideInterface,
+            args = (interface.__module__+'.'+interface.getName(), interface))
+
+    def __protectName(self, name, permission_id):
+        "Set a permission on a particular name."
+        self._context.action(
+            discriminator = ('z3ext:preferences:protectName', object()),
+            callable = protectName,
+            args = (self._class, name, permission_id))
+
+    def __protectNames(self, names, permission_id):
+        "Set a permission on a bunch of names."
+        for name in names:
+            self.__protectName(name, permission_id)
+
+    def __protectSetAttributes(self, names, permission_id):
+        "Set a permission on a bunch of names."
+        for name in names:
+            self._context.action(
+                discriminator = (
+                    'z3ext:preferences:protectSetAttribute', object()),
+                callable = protectSetAttribute,
+                args = (self._class, name, permission_id))
+
+    def __protectSetSchema(self, schema, permission_id):
+        "Set a permission on a bunch of names."
+        _context = self._context
+
+        for name in schema:
+            field = schema[name]
+            if IField.providedBy(field) and not field.readonly:
+                _context.action(
+                    discriminator = (
+                        'z3ext:preferences:protectSetAttribute', object()),
+                    callable = protectSetAttribute,
+                    args = (self._class, name, permission_id))
+
+        _context.action(
+            discriminator = None,
+            callable = provideInterface,
+            args = (schema.__module__+'.'+schema.getName(), schema))
+
+
+def addSubgroup(group):
+    if '.' in group.__id__:
+        parentId = group.__id__.split('.')[0]
+    else:
+        parentId = ''
+
+    parent = queryUtility(IPreferenceGroup, parentId)
+    if parent is None:
+        parent = getGlobalSiteManager().getUtility(IPreferenceGroup, parentId)
+
+    parent.add(group.__name__)
+    group.__parent__ = parent


Property changes on: z3ext.preferences/trunk/src/z3ext/preferences/zcml.py
___________________________________________________________________
Name: svn:keywords
   + Id



More information about the Checkins mailing list