|
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 packages |
|
17 import logging |
|
18 logger = logging.getLogger('PyAMS (viewlet)') |
|
19 |
|
20 import venusian |
|
21 |
|
22 # import interfaces |
|
23 from pyams_viewlet.interfaces import IViewletManager, IViewlet |
|
24 from pyramid.interfaces import IRequest, IView |
|
25 from zope.component.interfaces import ComponentLookupError |
|
26 from zope.contentprovider.interfaces import BeforeUpdateEvent |
|
27 from zope.location.interfaces import ILocation |
|
28 |
|
29 # import packages |
|
30 from pyams_template.template import get_view_template |
|
31 from pyams_utils.request import check_request |
|
32 from pyramid.exceptions import ConfigurationError |
|
33 from pyramid.httpexceptions import HTTPUnauthorized |
|
34 from pyramid.threadlocal import get_current_registry |
|
35 from zope.interface import implementer, classImplements, Interface |
|
36 |
|
37 |
|
38 @implementer(IViewletManager) |
|
39 class ViewletManager(object): |
|
40 """The Viewlet Manager base |
|
41 |
|
42 A generic manager class which can be instantiated |
|
43 """ |
|
44 |
|
45 permission = None |
|
46 template = None |
|
47 |
|
48 def __init__(self, context, request, view): |
|
49 self.__updated = False |
|
50 self.__parent__ = view |
|
51 self.context = context |
|
52 self.request = request |
|
53 |
|
54 def __getitem__(self, name): |
|
55 """See zope.interface.common.mapping.IReadMapping""" |
|
56 # Find the viewlet |
|
57 registry = get_current_registry() |
|
58 viewlet = registry.queryMultiAdapter((self.context, self.request, self.__parent__, self), |
|
59 IViewlet, name=name) |
|
60 |
|
61 # If the viewlet was not found, then raise a lookup error |
|
62 if viewlet is None: |
|
63 raise ComponentLookupError('No provider with name `%s` found.' % name) |
|
64 |
|
65 # If the viewlet cannot be accessed, then raise an |
|
66 # unauthorized error |
|
67 if viewlet.permission and not self.request.has_permission(viewlet.permission, context=self.context): |
|
68 raise HTTPUnauthorized('You are not authorized to access the provider called `%s`.' % name) |
|
69 |
|
70 # Return the viewlet. |
|
71 return viewlet |
|
72 |
|
73 def get(self, name, default=None): |
|
74 """See zope.interface.common.mapping.IReadMapping""" |
|
75 try: |
|
76 return self[name] |
|
77 except (ComponentLookupError, HTTPUnauthorized): |
|
78 return default |
|
79 |
|
80 def __contains__(self, name): |
|
81 """See zope.interface.common.mapping.IReadMapping""" |
|
82 return bool(self.get(name, False)) |
|
83 |
|
84 def filter(self, viewlets): |
|
85 """Sort out all content providers |
|
86 |
|
87 ``viewlets`` is a list of tuples of the form (name, viewlet). |
|
88 """ |
|
89 # Only return viewlets accessible to the principal |
|
90 request = self.request |
|
91 return [(name, viewlet) for name, viewlet in viewlets |
|
92 if (not viewlet.permission) or request.has_permission(viewlet.permission, context=self.context)] |
|
93 |
|
94 def sort(self, viewlets): |
|
95 """Sort the viewlets. |
|
96 |
|
97 ``viewlets`` is a list of tuples of the form (name, viewlet). |
|
98 """ |
|
99 # By default, we are not sorting by viewlet name. |
|
100 return sorted(viewlets, key=lambda x: x[0]) |
|
101 |
|
102 def update(self): |
|
103 """See zope.contentprovider.interfaces.IContentProvider""" |
|
104 self.__updated = True |
|
105 |
|
106 # check permission |
|
107 if self.permission and not self.request.has_permission(self.permission, context=self.context): |
|
108 return |
|
109 # Find all content providers for the region |
|
110 viewlets = self._get_viewlets() |
|
111 # Just use the viewlets from now on |
|
112 self.viewlets = [] |
|
113 for name, viewlet in viewlets: |
|
114 if ILocation.providedBy(viewlet): |
|
115 viewlet.__name__ = name |
|
116 self.viewlets.append(viewlet) |
|
117 self._update_viewlets() |
|
118 |
|
119 def _get_viewlets(self): |
|
120 """Find all content providers for the region""" |
|
121 registry = self.request.registry |
|
122 viewlets = registry.getAdapters((self.context, self.request, self.__parent__, self), |
|
123 IViewlet) |
|
124 viewlets = self.filter(viewlets) |
|
125 viewlets = self.sort(viewlets) |
|
126 return viewlets |
|
127 |
|
128 def _update_viewlets(self): |
|
129 """Calls update on all viewlets and fires events""" |
|
130 registry = self.request.registry |
|
131 for viewlet in self.viewlets: |
|
132 registry.notify(BeforeUpdateEvent(viewlet, self.request)) |
|
133 viewlet.update() |
|
134 |
|
135 def render(self): |
|
136 """See zope.contentprovider.interfaces.IContentProvider""" |
|
137 # Now render the view |
|
138 if self.permission and not self.request.has_permission(self.permission, context=self.context): |
|
139 return '' |
|
140 if not self.viewlets: |
|
141 return '' |
|
142 if self.template: |
|
143 return self.template(viewlets=self.viewlets) |
|
144 else: |
|
145 return '\n'.join([viewlet.render() for viewlet in self.viewlets]) |
|
146 |
|
147 |
|
148 def ViewletManagerFactory(name, interface, bases=(), cdict=None): |
|
149 """Viewlet manager factory""" |
|
150 |
|
151 attr_dict = {'__name__': name} |
|
152 attr_dict.update(cdict or {}) |
|
153 |
|
154 if ViewletManager not in bases: |
|
155 # Make sure that we do not get a default viewlet manager mixin, if the |
|
156 # provided base is already a full viewlet manager implementation. |
|
157 if not (len(bases) == 1 and IViewletManager.implementedBy(bases[0])): |
|
158 bases = bases + (ViewletManager,) |
|
159 |
|
160 viewlet_manager_class = type('<ViewletManager providing %s>' % interface.getName(), bases, attr_dict) |
|
161 classImplements(viewlet_manager_class, interface) |
|
162 return viewlet_manager_class |
|
163 |
|
164 |
|
165 def get_weight(item): |
|
166 """Get sort weight of a given viewlet""" |
|
167 name, viewlet = item |
|
168 try: |
|
169 return int(viewlet.weight) |
|
170 except (TypeError, AttributeError): |
|
171 return 0 |
|
172 |
|
173 |
|
174 def get_label(item, request=None): |
|
175 """Get sort label of a given viewlet""" |
|
176 name, viewlet = item |
|
177 try: |
|
178 if request is None: |
|
179 request = check_request() |
|
180 return request.localizer.translate(viewlet.label) |
|
181 except AttributeError: |
|
182 return '--' |
|
183 |
|
184 |
|
185 def get_weight_and_label(item, request=None): |
|
186 """Get sort weight and label of a given viewlet""" |
|
187 return get_weight(item), get_label(item, request) |
|
188 |
|
189 |
|
190 class WeightOrderedViewletManager(ViewletManager): |
|
191 """Weight ordered viewlet managers. |
|
192 |
|
193 Viewlets with the same weight are sorted by label |
|
194 """ |
|
195 |
|
196 def sort(self, viewlets): |
|
197 return sorted(viewlets, key=lambda x: get_weight_and_label(x, request=self.request)) |
|
198 |
|
199 |
|
200 def is_available(viewlet): |
|
201 try: |
|
202 return ((not viewlet.permission) or |
|
203 viewlet.request.has_permission(viewlet.permission, context=viewlet.context)) and \ |
|
204 viewlet.available |
|
205 except AttributeError: |
|
206 return True |
|
207 |
|
208 |
|
209 class ConditionalViewletManager(WeightOrderedViewletManager): |
|
210 """Conditional weight ordered viewlet managers.""" |
|
211 |
|
212 def filter(self, viewlets): |
|
213 """Sort out all viewlets which are explicit not available |
|
214 |
|
215 ``viewlets`` is a list of tuples of the form (name, viewlet). |
|
216 """ |
|
217 return [(name, viewlet) for name, viewlet in viewlets if is_available(viewlet)] |
|
218 |
|
219 |
|
220 class TemplateBasedViewletManager(object): |
|
221 """Template based viewlet manager mixin class""" |
|
222 |
|
223 template = get_view_template() |
|
224 |
|
225 |
|
226 class viewletmanager_config(object): |
|
227 """Class or interface decorator used to declare a viewlet manager |
|
228 |
|
229 You can provide same arguments as in 'viewletManager' ZCML directive: |
|
230 @name = name of the viewlet; may be unique for a given viewlet manager |
|
231 @view = the view class or interface for which viewlet is displayed |
|
232 @for_ = the context class or interface for which viewlet is displayed |
|
233 @permission = name of a permission required to display the viewlet |
|
234 @layer = request interface required to display the viewlet |
|
235 @class_ = the class handling the viewlet manager; if the decorator is applied |
|
236 on an interface and if this argument is not provided, the viewlet manager |
|
237 will be handled by a default ViewletManager class |
|
238 @provides = an interface the viewlet manager provides; if the decorator is |
|
239 applied on an Interface, this will be the decorated interface; if the |
|
240 decorated is applied on a class and if this argument is not specified, |
|
241 the manager will provide IViewletManager interface. |
|
242 """ |
|
243 |
|
244 venusian = venusian # for testing injection |
|
245 |
|
246 def __init__(self, **settings): |
|
247 if not settings.get('name'): |
|
248 raise ConfigurationError("You must provide a name for a ViewletManager") |
|
249 if 'for_' in settings: |
|
250 if settings.get('context') is None: |
|
251 settings['context'] = settings['for_'] |
|
252 self.__dict__.update(settings) |
|
253 |
|
254 def __call__(self, wrapped): |
|
255 settings = self.__dict__.copy() |
|
256 |
|
257 def callback(context, name, ob): |
|
258 cdict = {'__name__': settings.get('name')} |
|
259 if 'permission' in settings: |
|
260 cdict['permission'] = settings.get('permission') |
|
261 |
|
262 if issubclass(ob, Interface): |
|
263 class_ = settings.get('class_', ViewletManager) |
|
264 provides = ob |
|
265 else: |
|
266 class_ = ob |
|
267 provides = settings.get('provides', IViewletManager) |
|
268 new_class = ViewletManagerFactory(settings.get('name'), provides, (class_,), cdict) |
|
269 |
|
270 logger.debug("Registering viewlet manager {0} ({1})".format(settings.get('name'), |
|
271 str(new_class))) |
|
272 config = context.config.with_package(info.module) |
|
273 config.registry.registerAdapter(new_class, |
|
274 (settings.get('context', Interface), |
|
275 settings.get('layer', IRequest), |
|
276 settings.get('view', IView)), |
|
277 provides, settings.get('name')) |
|
278 |
|
279 info = self.venusian.attach(wrapped, callback, category='pyams_viewlet') |
|
280 |
|
281 if info.scope == 'class': |
|
282 # if the decorator was attached to a method in a class, or |
|
283 # otherwise executed at class scope, we need to set an |
|
284 # 'attr' into the settings if one isn't already in there |
|
285 if settings.get('attr') is None: |
|
286 settings['attr'] = wrapped.__name__ |
|
287 |
|
288 settings['_info'] = info.codeinfo # fbo "action_method" |
|
289 return wrapped |