|
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) |