src/pyams_utils/site.py
changeset 1 3f89629b9e54
child 3 2919bac002e3
--- /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)