src/pyams_utils/site.py
changeset 289 c8e21d7dd685
child 292 b338586588ad
equal deleted inserted replaced
-1:000000000000 289:c8e21d7dd685
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 
       
    18 # import interfaces
       
    19 from pyams_utils.interfaces import PYAMS_APPLICATION_SETTINGS_KEY, PYAMS_APPLICATION_DEFAULT_NAME, \
       
    20     PYAMS_APPLICATION_FACTORY_KEY, PUBLIC_PERMISSION
       
    21 from pyams_utils.interfaces.site import ISiteRoot, ISiteRootFactory, INewLocalSiteCreatedEvent, ISiteUpgradeEvent, \
       
    22     ISiteGenerations, SITE_GENERATIONS_KEY, IConfigurationManager
       
    23 from zope.component.interfaces import IPossibleSite, ObjectEvent
       
    24 from zope.traversing.interfaces import ITraversable
       
    25 
       
    26 # import packages
       
    27 from persistent.dict import PersistentDict
       
    28 from pyams_utils.adapter import adapter_config, ContextAdapter, get_annotation_adapter
       
    29 from pyams_utils.registry import get_utilities_for, query_utility
       
    30 from pyramid.exceptions import NotFound
       
    31 from pyramid.path import DottedNameResolver
       
    32 from pyramid.security import Allow, Everyone, ALL_PERMISSIONS
       
    33 from pyramid.threadlocal import get_current_registry
       
    34 from pyramid_zodbconn import get_connection
       
    35 from zope.container.folder import Folder
       
    36 from zope.interface import implementer
       
    37 from zope.lifecycleevent import ObjectCreatedEvent
       
    38 from zope.site import hooks
       
    39 from zope.site.site import LocalSiteManager, SiteManagerContainer
       
    40 
       
    41 
       
    42 @implementer(ISiteRoot, IConfigurationManager)
       
    43 class BaseSiteRoot(Folder, SiteManagerContainer):
       
    44     """Default site root
       
    45 
       
    46     A site root can be used as base application root in your ZODB.
       
    47     It's also site root responsibility to manage your local site manager.
       
    48 
       
    49     BaseSiteRoot defines a basic ACL which gives all permissions to system administrator,
       
    50     and 'public' permission to everyone. But this ACL is generally overriden in subclasses
       
    51     which also inherit from :class:`pyams_security.security.ProtectedObject`.
       
    52     """
       
    53 
       
    54     __acl__ = [(Allow, 'system:admin', ALL_PERMISSIONS),
       
    55                (Allow, Everyone, {PUBLIC_PERMISSION})]
       
    56 
       
    57     config_klass = None
       
    58 
       
    59 
       
    60 @adapter_config(name='etc', context=ISiteRoot, provides=ITraversable)
       
    61 class SiteRootEtcTraverser(ContextAdapter):
       
    62     """Site root ++etc++ namespace traverser
       
    63 
       
    64     Gives access to local site manager from */++etc++site* URL
       
    65     """
       
    66 
       
    67     def traverse(self, name, furtherpath=None):
       
    68         if name == 'site':
       
    69             return self.context.getSiteManager()
       
    70         raise NotFound
       
    71 
       
    72 
       
    73 @implementer(INewLocalSiteCreatedEvent)
       
    74 class NewLocalSiteCreatedEvent(ObjectEvent):
       
    75     """New local site creation event"""
       
    76 
       
    77 
       
    78 def site_factory(request):
       
    79     """Application site factory
       
    80 
       
    81     On application startup, this factory checks configuration to get application name and
       
    82     load it from the ZODB; if the application can't be found, configuration is scanned to
       
    83     get application factory, create a new one and create a local site manager.
       
    84     """
       
    85     conn = get_connection(request)
       
    86     root = conn.root()
       
    87     application_key = request.registry.settings.get(PYAMS_APPLICATION_SETTINGS_KEY,
       
    88                                                     PYAMS_APPLICATION_DEFAULT_NAME)
       
    89     application = root.get(application_key)
       
    90     if application is None:
       
    91         factory = request.registry.settings.get(PYAMS_APPLICATION_FACTORY_KEY)
       
    92         if factory:
       
    93             resolver = DottedNameResolver()
       
    94             factory = resolver.maybe_resolve(factory)
       
    95         else:
       
    96             factory = request.registry.queryUtility(ISiteRootFactory, default=BaseSiteRoot)
       
    97         application = root[application_key] = factory()
       
    98         if IPossibleSite.providedBy(application):
       
    99             sm = LocalSiteManager(application, default_folder=False)
       
   100             application.setSiteManager(sm)
       
   101         try:
       
   102             # if some components require a valid and complete registry
       
   103             # with all registered utilities, they can subscribe to
       
   104             # INewLocalSiteCreatedEvent event interface
       
   105             hooks.setSite(application)
       
   106             get_current_registry().notify(NewLocalSiteCreatedEvent(application))
       
   107         finally:
       
   108             hooks.setSite(None)
       
   109         import transaction
       
   110         transaction.commit()
       
   111     return application
       
   112 
       
   113 
       
   114 @implementer(ISiteUpgradeEvent)
       
   115 class SiteUpgradeEvent(ObjectEvent):
       
   116     """Site upgrade request event"""
       
   117 
       
   118 
       
   119 def site_upgrade(request):
       
   120     """Upgrade site when needed
       
   121 
       
   122     This function is executed by *pyams_upgrade* console script.
       
   123     Site generations are registered named utilities providing
       
   124     :py:class:`ISiteGenerations <pyams_utils.interfaces.site.ISiteGenerations>` interface.
       
   125 
       
   126     Current site generations are stored into annotations for each generation adapter.
       
   127     """
       
   128     application = site_factory(request)
       
   129     if application is not None:
       
   130         try:
       
   131             hooks.setSite(application)
       
   132             generations = get_annotation_adapter(application, SITE_GENERATIONS_KEY, PersistentDict,
       
   133                                                  notify=False, locate=False)
       
   134             for name, utility in sorted(get_utilities_for(ISiteGenerations),
       
   135                                         key=lambda x: x[1].order):
       
   136                 if not name:
       
   137                     name = '.'.join((utility.__module__, utility.__class__.__name__))
       
   138                 current = generations.get(name)
       
   139                 if not current:
       
   140                     print("Upgrading {0} to generation {1}...".format(name, utility.generation))
       
   141                 elif current < utility.generation:
       
   142                     print("Upgrading {0} from generation {1} to {2}...".format(name, current, utility.generation))
       
   143                 utility.evolve(application, current)
       
   144                 generations[name] = utility.generation
       
   145         finally:
       
   146             hooks.setSite(None)
       
   147         import transaction
       
   148         transaction.commit()
       
   149     return application
       
   150 
       
   151 
       
   152 def check_required_utilities(site, utilities):
       
   153     """Utility function to check for required utilities
       
   154 
       
   155     :param object site: the site manager into which configuration may be checked
       
   156     :param tuple utilities: each element of the tuple is another tuple made of the utility interface,
       
   157         the utility registration name, the utility factory and the object name when creating the utility, as in:
       
   158 
       
   159     .. code-block:: python
       
   160 
       
   161         REQUIRED_UTILITIES = ((ISecurityManager, '', SecurityManager, 'Security manager'),
       
   162                               (IPrincipalAnnotationUtility, '', PrincipalAnnotationUtility, 'User profiles'))
       
   163     """
       
   164     registry = get_current_registry()
       
   165     for interface, name, factory, default_id in utilities:
       
   166         utility = query_utility(interface, name=name)
       
   167         if utility is None:
       
   168             sm = site.getSiteManager()
       
   169             if default_id in sm:
       
   170                 continue
       
   171             utility = factory()
       
   172             registry.notify(ObjectCreatedEvent(utility))
       
   173             sm[default_id] = utility
       
   174             sm.registerUtility(utility, interface, name=name)