--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/site.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,167 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_utils.interfaces import PYAMS_APPLICATION_SETTINGS_KEY, PYAMS_APPLICATION_DEFAULT_NAME, \
+ PYAMS_APPLICATION_FACTORY_KEY
+from pyams_utils.interfaces.site import ISiteRoot, INewLocalSiteCreatedEvent, ISiteUpgradeEvent, ISiteGenerations, \
+ SITE_GENERATIONS_KEY, IConfigurationManager
+from zope.annotation.interfaces import IAnnotations
+from zope.component.interfaces import IPossibleSite, ISite, ObjectEvent
+from zope.traversing.interfaces import IBeforeTraverseEvent, ITraversable
+
+# import packages
+from persistent.dict import PersistentDict
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import get_utilities_for, query_utility
+from pyramid.events import subscriber
+from pyramid.exceptions import NotFound
+from pyramid.path import DottedNameResolver
+from pyramid.security import Allow, ALL_PERMISSIONS
+from pyramid.threadlocal import manager, get_current_registry
+from pyramid_zodbconn import get_connection
+from zope.container.folder import Folder
+from zope.interface import implementer
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.site import hooks
+from zope.site.site import LocalSiteManager, SiteManagerContainer
+
+
+@implementer(ISiteRoot, IConfigurationManager)
+class BaseSiteRoot(Folder, SiteManagerContainer):
+ """Default site root"""
+
+ __acl__ = [(Allow, 'system:admin', ALL_PERMISSIONS)]
+
+ config_klass = None
+
+
+@adapter_config(name='etc', context=ISiteRoot, provides=ITraversable)
+class SiteRootEtcTraverser(object):
+ """Site root ++etc++ namespace traverser"""
+
+ def __init__(self, context):
+ self.context = context
+
+ def traverse(self, name, furtherpath=None):
+ if name == 'site':
+ return self.context.getSiteManager()
+ raise NotFound
+
+
+@implementer(INewLocalSiteCreatedEvent)
+class NewLocalSiteCreatedEvent(ObjectEvent):
+ """New local site created event"""
+
+
+def site_factory(request):
+ """Build a new site including registered utilities"""
+ conn = get_connection(request)
+ root = conn.root()
+ application_key = request.registry.settings.get(PYAMS_APPLICATION_SETTINGS_KEY,
+ PYAMS_APPLICATION_DEFAULT_NAME)
+ application = root.get(application_key)
+ if application is None:
+ factory = request.registry.settings.get(PYAMS_APPLICATION_FACTORY_KEY)
+ if factory:
+ resolver = DottedNameResolver()
+ factory = resolver.maybe_resolve(factory)
+ else:
+ factory = BaseSiteRoot
+ application = root[application_key] = factory()
+ if IPossibleSite.providedBy(application):
+ sm = LocalSiteManager(application, default_folder=False)
+ application.setSiteManager(sm)
+ try:
+ # if some components require a valid and complete registry
+ # with all registered utilities, they can subscribe to
+ # INewLocalSiteCreatedEvent event interface
+ hooks.setSite(application)
+ get_current_registry().notify(NewLocalSiteCreatedEvent(application))
+ finally:
+ hooks.setSite(None)
+ import transaction
+ transaction.commit()
+ return application
+
+
+@implementer(ISiteUpgradeEvent)
+class SiteUpgradeEvent(ObjectEvent):
+ """Site upgrade request event"""
+
+
+def site_upgrade(request):
+ """Upgrade site when needed
+
+ This function is executed by pyams_upgrade console script.
+ Site generations are registered as named utilities providing
+ ISiteGenerations interface.
+ Current site generations are stored into annotations.
+ """
+ application = site_factory(request)
+ if application is not None:
+ try:
+ hooks.setSite(application)
+ annotations = IAnnotations(application)
+ generations = annotations.get(SITE_GENERATIONS_KEY)
+ if generations is None:
+ generations = annotations[SITE_GENERATIONS_KEY] = PersistentDict()
+ for name, utility in get_utilities_for(ISiteGenerations):
+ if not name:
+ name = '.'.join((utility.__module__, utility.__class__.__name__))
+ current = generations.get(name)
+ if (not current) or (current < utility.generation):
+ print("Upgrading {0} from generation {1} to {2}...".format(name, current, utility.generation))
+ utility.evolve(application, current)
+ generations[name] = utility.generation
+ finally:
+ hooks.setSite(None)
+ import transaction
+ transaction.commit()
+ return application
+
+
+@subscriber(IBeforeTraverseEvent, context_selector=ISite)
+def handle_site_before_traverse(event):
+ """Push registry and request to threadlocal manager when an
+ object implementing ISite is traversed
+ """
+ manager.push({'registry': event.object.getSiteManager(),
+ 'request': event.request})
+ hooks.setSite(event.object)
+
+
+def check_required_utilities(site, utilities):
+ """Utility function to check for required utilities
+
+ utilities argument is a tuple made of:
+ - the utility interface
+ - the utility name
+ - the utility factory
+ - the default name when creating the utility
+ """
+ registry = get_current_registry()
+ for interface, name, factory, default_id in utilities:
+ utility = query_utility(interface, name=name)
+ if utility is None:
+ sm = site.getSiteManager()
+ if default_id in sm:
+ continue
+ utility = factory()
+ registry.notify(ObjectCreatedEvent(utility))
+ sm[default_id] = utility
+ sm.registerUtility(utility, interface, name=name)