vendor/Five/1.2b-r20590

view browser/metaconfigure.py @ 0:3673ed425f80

Vendor import of Five 1.2b+ (r20590)
author fguillaume
date Fri, 02 Dec 2005 20:25:42 +0000
parents
children
line source
1 ##############################################################################
2 #
3 # Copyright (c) 2004, 2005 Zope Corporation and Contributors.
4 # All Rights Reserved.
5 #
6 # This software is subject to the provisions of the Zope Public License,
7 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
8 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11 # FOR A PARTICULAR PURPOSE.
12 #
13 ##############################################################################
14 """Browser directives
16 Directives to emulate the 'http://namespaces.zope.org/browser'
17 namespace in ZCML known from zope.app.
19 $Id: metaconfigure.py 18911 2005-10-25 09:36:24Z regebro $
20 """
21 import os
23 from zope.interface import Interface
24 from zope.component import getGlobalService, ComponentLookupError
25 from zope.configuration.exceptions import ConfigurationError
26 from zope.component.servicenames import Presentation
27 from zope.publisher.interfaces.browser import IBrowserRequest
28 from zope.app.publisher.browser.viewmeta import pages as zope_app_pages
29 from zope.app.publisher.browser.viewmeta import view as zope_app_view
30 from zope.app.publisher.browser.viewmeta import providesCallable
31 from zope.app.publisher.browser.globalbrowsermenuservice import\
32 menuItemDirective
33 from zope.app.component.metaconfigure import handler
34 from zope.app.component.interface import provideInterface
35 from zope.app.container.interfaces import IAdding
37 from Products.Five.browser import BrowserView
38 from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory
39 from Products.Five.browser.resource import PageTemplateResourceFactory
40 from Products.Five.browser.resource import DirectoryResourceFactory
41 from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
42 from Products.Five.metaclass import makeClass
43 from Products.Five.security import getSecurityInfo, protectClass, \
44 protectName, initializeClass
46 import ExtensionClass
48 def page(_context, name, permission, for_,
49 layer='default', template=None, class_=None,
50 allowed_interface=None, allowed_attributes=None,
51 attribute='__call__', menu=None, title=None,
52 ):
54 _handle_menu(_context, menu, title, [for_], name, permission)
56 if not (class_ or template):
57 raise ConfigurationError("Must specify a class or template")
58 if allowed_attributes is None:
59 allowed_attributes = []
60 if allowed_interface is not None:
61 for interface in allowed_interface:
62 attrs = [n for n, d in interface.namesAndDescriptions(1)]
63 allowed_attributes.extend(attrs)
65 if attribute != '__call__':
66 if template:
67 raise ConfigurationError(
68 "Attribute and template cannot be used together.")
70 if not class_:
71 raise ConfigurationError(
72 "A class must be provided if attribute is used")
74 if template:
75 template = os.path.abspath(str(_context.path(template)))
76 if not os.path.isfile(template):
77 raise ConfigurationError("No such file", template)
79 if class_:
80 # new-style classes do not work with Five. As we want to import
81 # packages from z3 directly, we ignore new-style classes for now.
82 if type(class_) == type:
83 return
84 if attribute != '__call__':
85 if not hasattr(class_, attribute):
86 raise ConfigurationError(
87 "The provided class doesn't have the specified attribute "
88 )
89 cdict = getSecurityInfo(class_)
90 if template:
91 new_class = makeClassForTemplate(template, bases=(class_, ),
92 cdict=cdict)
93 elif attribute != "__call__":
94 # we're supposed to make a page for an attribute (read:
95 # method) and it's not __call__. We thus need to create a
96 # new class using our mixin for attributes.
97 cdict.update({'__page_attribute__': attribute})
98 new_class = makeClass(class_.__name__,
99 (class_, ViewMixinForAttributes),
100 cdict)
102 # in case the attribute does not provide a docstring,
103 # ZPublisher refuses to publish it. So, as a workaround,
104 # we provide a stub docstring
105 func = getattr(new_class, attribute)
106 if not func.__doc__:
107 # cannot test for MethodType/UnboundMethod here
108 # because of ExtensionClass
109 if hasattr(func, 'im_func'):
110 # you can only set a docstring on functions, not
111 # on method objects
112 func = func.im_func
113 func.__doc__ = "Stub docstring to make ZPublisher work"
114 else:
115 # we could use the class verbatim here, but we'll execute
116 # some security declarations on it so we really shouldn't
117 # modify the original. So, instead we make a new class
118 # with just one base class -- the original
119 new_class = makeClass(class_.__name__, (class_,), cdict)
121 else:
122 # template
123 new_class = makeClassForTemplate(template)
125 _handle_for(_context, for_)
127 _context.action(
128 discriminator = ('view', for_, name, IBrowserRequest, layer),
129 callable = handler,
130 args = (Presentation, 'provideAdapter',
131 IBrowserRequest, new_class, name, [for_], Interface, layer,
132 _context.info),
133 )
134 _context.action(
135 discriminator = ('five:protectClass', new_class),
136 callable = protectClass,
137 args = (new_class, permission)
138 )
139 if allowed_attributes:
140 for attr in allowed_attributes:
141 _context.action(
142 discriminator = ('five:protectName', new_class, attr),
143 callable = protectName,
144 args = (new_class, attr, permission)
145 )
146 _context.action(
147 discriminator = ('five:initialize:class', new_class),
148 callable = initializeClass,
149 args = (new_class,)
150 )
152 class pages(zope_app_pages):
154 def page(self, _context, name, attribute='__call__', template=None,
155 menu=None, title=None):
156 return page(_context,
157 name=name,
158 attribute=attribute,
159 template=template,
160 menu=menu, title=title,
161 **(self.opts))
163 def defaultView(_context, name, for_=None):
165 type = IBrowserRequest
167 _context.action(
168 discriminator = ('defaultViewName', for_, type, name),
169 callable = handler,
170 args = (Presentation,
171 'setDefaultViewName', for_, type, name),
172 )
174 _handle_for(_context, for_)
176 # view (named view with pages)
178 class view(zope_app_view):
180 def __call__(self):
181 (_context, name, for_, permission, layer, class_,
182 allowed_interface, allowed_attributes) = self.args
184 required = {}
186 cdict = {}
187 pages = {}
189 for pname, attribute, template in self.pages:
190 try:
191 s = getGlobalService(Presentation)
192 except ComponentLookupError, err:
193 pass
195 if template:
196 cdict[pname] = ZopeTwoPageTemplateFile(template)
197 if attribute and attribute != name:
198 cdict[attribute] = cdict[pname]
199 else:
200 if not hasattr(class_, attribute):
201 raise ConfigurationError("Undefined attribute",
202 attribute)
204 attribute = attribute or pname
205 required[pname] = permission
207 pages[pname] = attribute
209 # This should go away, but noone seems to remember what to do. :-(
210 if hasattr(class_, 'publishTraverse'):
212 def publishTraverse(self, request, name,
213 pages=pages, getattr=getattr):
215 if name in pages:
216 return getattr(self, pages[name])
217 view = zapi.queryView(self, name, request)
218 if view is not None:
219 return view
221 m = class_.publishTraverse.__get__(self)
222 return m(request, name)
224 else:
225 def publishTraverse(self, request, name,
226 pages=pages, getattr=getattr):
228 if name in pages:
229 return getattr(self, pages[name])
230 view = zapi.queryView(self, name, request)
231 if view is not None:
232 return view
234 raise NotFoundError(self, name, request)
236 cdict['publishTraverse'] = publishTraverse
238 if not hasattr(class_, 'browserDefault'):
239 if self.default or self.pages:
240 default = self.default or self.pages[0][0]
241 cdict['browserDefault'] = (
242 lambda self, request, default=default:
243 (self, (default, ))
244 )
245 elif providesCallable(class_):
246 cdict['browserDefault'] = (
247 lambda self, request: (self, ())
248 )
250 if class_ is not None:
251 bases = (class_, ViewMixinForTemplates)
252 else:
253 bases = (ViewMixinForTemplates)
255 try:
256 cname = str(name)
257 except:
258 cname = "GeneratedClass"
260 newclass = makeClass(cname, bases, cdict)
262 _handle_for(_context, for_)
264 if self.provides is not None:
265 _context.action(
266 discriminator = None,
267 callable = provideInterface,
268 args = ('', self.provides)
269 )
271 _context.action(
272 discriminator = ('view', for_, name, IBrowserRequest, layer,
273 self.provides),
274 callable = handler,
275 args = (Presentation, 'provideAdapter',
276 IBrowserRequest, newclass, name, [for_], self.provides,
277 layer, _context.info),
278 )
280 def _handle_for(_context, for_):
281 if for_ is not None:
282 _context.action(
283 discriminator = None,
284 callable = provideInterface,
285 args = ('', for_)
286 )
288 def _handle_menu(_context, menu, title, for_, name, permission):
289 if menu or title:
290 if not (menu and title):
291 raise ConfigurationError(
292 "If either menu or title are specified, they must "
293 "both be specified.")
295 if len(for_) != 1:
296 raise ConfigurationError(
297 "Menus can be specified only for single-view, not for "
298 "multi-views.")
300 return menuItemDirective(
301 _context, menu, for_[0], '@@' + str(name), title,
302 permission=permission)
304 return []
306 _factory_map = {'image':{'prefix':'ImageResource',
307 'count':0,
308 'factory':ImageResourceFactory},
309 'file':{'prefix':'FileResource',
310 'count':0,
311 'factory':FileResourceFactory},
312 'template':{'prefix':'PageTemplateResource',
313 'count':0,
314 'factory':PageTemplateResourceFactory}
315 }
317 def resource(_context, name, layer='default', permission='zope.Public',
318 file=None, image=None, template=None):
320 if ((file and image) or (file and template) or
321 (image and template) or not (file or image or template)):
322 raise ConfigurationError(
323 "Must use exactly one of file or image or template"
324 "attributes for resource directives"
325 )
327 res = file or image or template
328 res_type = ((file and 'file') or
329 (image and 'image') or
330 (template and 'template'))
331 factory_info = _factory_map.get(res_type)
332 factory_info['count'] += 1
333 res_factory = factory_info['factory']
334 class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
335 new_class = makeClass(class_name, (res_factory.resource,), {})
336 factory = res_factory(name, res, resource_factory=new_class)
338 _context.action(
339 discriminator = ('resource', name, IBrowserRequest, layer),
340 callable = handler,
341 args = (Presentation, 'provideResource',
342 name, IBrowserRequest, factory, layer),
343 )
344 _context.action(
345 discriminator = ('five:protectClass', new_class),
346 callable = protectClass,
347 args = (new_class, permission)
348 )
349 _context.action(
350 discriminator = ('five:initialize:class', new_class),
351 callable = initializeClass,
352 args = (new_class,)
353 )
355 _rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource',
356 'count':0},
357 FileResourceFactory:{'prefix':'DirContainedFileResource',
358 'count':0},
359 PageTemplateResourceFactory:{'prefix':'DirContainedPTResource',
360 'count':0},
361 DirectoryResourceFactory:{'prefix':'DirectoryResource',
362 'count':0}
363 }
365 def resourceDirectory(_context, name, directory, layer='default',
366 permission='zope.Public'):
368 if not os.path.isdir(directory):
369 raise ConfigurationError(
370 "Directory %s does not exist" % directory
371 )
373 resource = DirectoryResourceFactory.resource
374 f_cache = {}
375 resource_factories = dict(resource.resource_factories)
376 resource_factories['default'] = resource.default_factory
377 for ext, factory in resource_factories.items():
378 if f_cache.get(factory) is not None:
379 continue
380 factory_info = _rd_map.get(factory)
381 factory_info['count'] += 1
382 class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
383 factory_name = '%s%s' % (factory.__name__, factory_info['count'])
384 f_resource = makeClass(class_name, (factory.resource,), {})
385 f_cache[factory] = makeClass(factory_name, (factory,),
386 {'resource':f_resource})
387 for ext, factory in resource_factories.items():
388 resource_factories[ext] = f_cache[factory]
389 default_factory = resource_factories['default']
390 del resource_factories['default']
392 cdict = {'resource_factories':resource_factories,
393 'default_factory':default_factory}
395 factory_info = _rd_map.get(DirectoryResourceFactory)
396 factory_info['count'] += 1
397 class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
398 dir_factory = makeClass(class_name, (resource,), cdict)
399 factory = DirectoryResourceFactory(name, directory,
400 resource_factory=dir_factory)
402 new_classes = [dir_factory,
403 ] + [f.resource for f in f_cache.values()]
405 _context.action(
406 discriminator = ('resource', name, IBrowserRequest, layer),
407 callable = handler,
408 args = (Presentation, 'provideResource',
409 name, IBrowserRequest, factory, layer),
410 )
411 for new_class in new_classes:
412 _context.action(
413 discriminator = ('five:protectClass', new_class),
414 callable = protectClass,
415 args = (new_class, permission)
416 )
417 _context.action(
418 discriminator = ('five:initialize:class', new_class),
419 callable = initializeClass,
420 args = (new_class,)
421 )
423 #
424 # mixin classes / class factories
425 #
427 class ViewMixinForAttributes(BrowserView):
429 # we have an attribute that we can simply tell ZPublisher to go to
430 def __browser_default__(self, request):
431 return self, (self.__page_attribute__,)
433 # this is technically not needed because ZPublisher finds our
434 # attribute through __browser_default__; but we also want to be
435 # able to call pages from python modules, PythonScripts or ZPT
436 def __call__(self, *args, **kw):
437 attr = self.__page_attribute__
438 meth = getattr(self, attr)
439 return meth(*args, **kw)
441 class ViewMixinForTemplates(BrowserView):
443 # short cut to get to macros more easily
444 def __getitem__(self, name):
445 if name == 'macros':
446 return self.index.macros
447 return self.index.macros[name]
449 # make the template publishable
450 def __call__(self, *args, **kw):
451 return self.index(self, *args, **kw)
453 def makeClassForTemplate(filename, globals=None, used_for=None,
454 bases=(), cdict=None):
455 # XXX needs to deal with security from the bases?
456 if cdict is None:
457 cdict = {}
458 cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals)})
459 bases += (ViewMixinForTemplates,)
460 class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict)
462 if used_for is not None:
463 class_.__used_for__ = used_for
465 return class_