4 objects and provide a convenient api access to those objects |
4 objects and provide a convenient api access to those objects |
5 according to a context |
5 according to a context |
6 |
6 |
7 * to interact with the vregistry, object should inherit from the |
7 * to interact with the vregistry, object should inherit from the |
8 VObject abstract class |
8 VObject abstract class |
9 |
9 |
10 * the selection procedure has been generalized by delegating to a |
10 * the selection procedure has been generalized by delegating to a |
11 selector, which is responsible to score the vobject according to the |
11 selector, which is responsible to score the vobject according to the |
12 current state (req, rset, row, col). At the end of the selection, if |
12 current state (req, rset, row, col). At the end of the selection, if |
13 a vobject class has been found, an instance of this class is |
13 a vobject class has been found, an instance of this class is |
14 returned. The selector is instantiated at vobject registration |
14 returned. The selector is instantiated at vobject registration |
51 class VObject(object): |
51 class VObject(object): |
52 """visual object, use to be handled somehow by the visual components |
52 """visual object, use to be handled somehow by the visual components |
53 registry. |
53 registry. |
54 |
54 |
55 The following attributes should be set on concret vobject subclasses: |
55 The following attributes should be set on concret vobject subclasses: |
56 |
56 |
57 :__registry__: |
57 :__registry__: |
58 name of the registry for this object (string like 'views', |
58 name of the registry for this object (string like 'views', |
59 'templates'...) |
59 'templates'...) |
60 :id: |
60 :id: |
61 object's identifier in the registry (string like 'main', |
61 object's identifier in the registry (string like 'main', |
62 'primary', 'folder_box') |
62 'primary', 'folder_box') |
63 :__select__: |
63 :__select__: |
64 class'selector |
64 class'selector |
65 |
65 |
66 Moreover, the `__abstract__` attribute may be set to True to indicate |
66 Moreover, the `__abstract__` attribute may be set to True to indicate |
67 that a vobject is abstract and should not be registered |
67 that a vobject is abstract and should not be registered |
68 """ |
68 """ |
69 # necessary attributes to interact with the registry |
69 # necessary attributes to interact with the registry |
70 id = None |
70 id = None |
83 return cls |
83 return cls |
84 |
84 |
85 @classmethod |
85 @classmethod |
86 def selected(cls, *args, **kwargs): |
86 def selected(cls, *args, **kwargs): |
87 """called by the registry when the vobject has been selected. |
87 """called by the registry when the vobject has been selected. |
88 |
88 |
89 It must return the object that will be actually returned by the |
89 It must return the object that will be actually returned by the |
90 .select method (this may be the right hook to create an |
90 .select method (this may be the right hook to create an |
91 instance for example). By default the selected object is |
91 instance for example). By default the selected object is |
92 returned without any transformation. |
92 returned without any transformation. |
93 """ |
93 """ |
125 class VRegistry(object): |
125 class VRegistry(object): |
126 """class responsible to register, propose and select the various |
126 """class responsible to register, propose and select the various |
127 elements used to build the web interface. Currently, we have templates, |
127 elements used to build the web interface. Currently, we have templates, |
128 views, actions and components. |
128 views, actions and components. |
129 """ |
129 """ |
130 |
130 |
131 def __init__(self, config):#, cache_size=1000): |
131 def __init__(self, config):#, cache_size=1000): |
132 self.config = config |
132 self.config = config |
133 # dictionnary of registry (themself dictionnary) by name |
133 # dictionnary of registry (themself dictionnary) by name |
134 self._registries = {} |
134 self._registries = {} |
135 self._lastmodifs = {} |
135 self._lastmodifs = {} |
197 oid = obj.id |
197 oid = obj.id |
198 except AttributeError: |
198 except AttributeError: |
199 continue |
199 continue |
200 if oid: |
200 if oid: |
201 self.register(obj) |
201 self.register(obj) |
202 |
202 |
203 def register(self, obj, registryname=None, oid=None, clear=False): |
203 def register(self, obj, registryname=None, oid=None, clear=False): |
204 """base method to add an object in the registry""" |
204 """base method to add an object in the registry""" |
205 assert not '__abstract__' in obj.__dict__ |
205 assert not '__abstract__' in obj.__dict__ |
206 registryname = registryname or obj.__registry__ |
206 registryname = registryname or obj.__registry__ |
207 oid = oid or obj.id |
207 oid = oid or obj.id |
244 self.warning('can\'t remove %s, not in the %s registry with id %s', |
244 self.warning('can\'t remove %s, not in the %s registry with id %s', |
245 removed_id, registryname, obj.id) |
245 removed_id, registryname, obj.id) |
246 # else: |
246 # else: |
247 # # if objects is empty, remove oid from registry |
247 # # if objects is empty, remove oid from registry |
248 # if not registry[obj.id]: |
248 # if not registry[obj.id]: |
249 # del regcontent[oid] |
249 # del regcontent[oid] |
250 break |
250 break |
251 |
251 |
252 def register_and_replace(self, obj, replaced, registryname=None): |
252 def register_and_replace(self, obj, replaced, registryname=None): |
253 if hasattr(replaced, 'classid'): |
253 if hasattr(replaced, 'classid'): |
254 replaced = replaced.classid() |
254 replaced = replaced.classid() |
255 registryname = registryname or obj.__registry__ |
255 registryname = registryname or obj.__registry__ |
256 registry = self.registry(registryname) |
256 registry = self.registry(registryname) |
260 del registry[obj.id][index] |
260 del registry[obj.id][index] |
261 break |
261 break |
262 self.register(obj, registryname=registryname) |
262 self.register(obj, registryname=registryname) |
263 |
263 |
264 # dynamic selection methods ############################################### |
264 # dynamic selection methods ############################################### |
265 |
265 |
266 def select(self, vobjects, *args, **kwargs): |
266 def select(self, vobjects, *args, **kwargs): |
267 """return an instance of the most specific object according |
267 """return an instance of the most specific object according |
268 to parameters |
268 to parameters |
269 |
269 |
270 raise NoSelectableObject if not object apply |
270 raise NoSelectableObject if not object apply |
289 % (args, kwargs.keys(), |
289 % (args, kwargs.keys(), |
290 [repr(v) for v in winners])) |
290 [repr(v) for v in winners])) |
291 winner = winners[0] |
291 winner = winners[0] |
292 # return the result of the .selected method of the vobject |
292 # return the result of the .selected method of the vobject |
293 return winner.selected(*args, **kwargs) |
293 return winner.selected(*args, **kwargs) |
294 |
294 |
295 def possible_objects(self, registry, *args, **kwargs): |
295 def possible_objects(self, registry, *args, **kwargs): |
296 """return an iterator on possible objects in a registry for this result set |
296 """return an iterator on possible objects in a registry for this result set |
297 |
297 |
298 actions returned are classes, not instances |
298 actions returned are classes, not instances |
299 """ |
299 """ |
304 continue |
304 continue |
305 |
305 |
306 def select_object(self, registry, cid, *args, **kwargs): |
306 def select_object(self, registry, cid, *args, **kwargs): |
307 """return the most specific component according to the resultset""" |
307 """return the most specific component according to the resultset""" |
308 return self.select(self.registry_objects(registry, cid), *args, **kwargs) |
308 return self.select(self.registry_objects(registry, cid), *args, **kwargs) |
309 |
309 |
310 # intialization methods ################################################### |
310 # intialization methods ################################################### |
311 |
311 |
312 def init_registration(self, path): |
312 def init_registration(self, path): |
313 # compute list of all modules that have to be loaded |
313 # compute list of all modules that have to be loaded |
314 self._toloadmods, filemods = _toload_info(path) |
314 self._toloadmods, filemods = _toload_info(path) |
315 self._loadedmods = {} |
315 self._loadedmods = {} |
316 return filemods |
316 return filemods |
317 |
317 |
318 def register_objects(self, path, force_reload=None): |
318 def register_objects(self, path, force_reload=None): |
319 if force_reload is None: |
319 if force_reload is None: |
320 force_reload = self.config.mode == 'dev' |
320 force_reload = self.config.mode == 'dev' |
321 elif not force_reload: |
321 elif not force_reload: |
322 # force_reload == False usually mean modules have been reloaded |
322 # force_reload == False usually mean modules have been reloaded |
339 change = False |
339 change = False |
340 for filepath, modname in filemods: |
340 for filepath, modname in filemods: |
341 if self.load_file(filepath, modname, force_reload): |
341 if self.load_file(filepath, modname, force_reload): |
342 change = True |
342 change = True |
343 return change |
343 return change |
344 |
344 |
345 def load_file(self, filepath, modname, force_reload=False): |
345 def load_file(self, filepath, modname, force_reload=False): |
346 """load visual objects from a python file""" |
346 """load visual objects from a python file""" |
347 from logilab.common.modutils import load_module_from_name |
347 from logilab.common.modutils import load_module_from_name |
348 if modname in self._loadedmods: |
348 if modname in self._loadedmods: |
349 return |
349 return |
366 unregistered = None |
366 unregistered = None |
367 # load the module |
367 # load the module |
368 module = load_module_from_name(modname, use_sys=not force_reload) |
368 module = load_module_from_name(modname, use_sys=not force_reload) |
369 self.load_module(module) |
369 self.load_module(module) |
370 # if something was unregistered, we need to update places where it was |
370 # if something was unregistered, we need to update places where it was |
371 # referenced |
371 # referenced |
372 if unregistered: |
372 if unregistered: |
373 # oldnew_mapping = {} |
373 # oldnew_mapping = {} |
374 registered = self._loadedmods[modname] |
374 registered = self._loadedmods[modname] |
375 oldnew_mapping = dict((unregistered[name], registered[name]) |
375 oldnew_mapping = dict((unregistered[name], registered[name]) |
376 for name in unregistered if name in registered) |
376 for name in unregistered if name in registered) |
386 for objname, obj in vars(module).items(): |
386 for objname, obj in vars(module).items(): |
387 if objname.startswith('_'): |
387 if objname.startswith('_'): |
388 continue |
388 continue |
389 self._load_ancestors_then_object(module.__name__, obj) |
389 self._load_ancestors_then_object(module.__name__, obj) |
390 self.debug('loaded %s', module) |
390 self.debug('loaded %s', module) |
391 |
391 |
392 def _load_ancestors_then_object(self, modname, obj): |
392 def _load_ancestors_then_object(self, modname, obj): |
393 # imported classes |
393 # imported classes |
394 objmodname = getattr(obj, '__module__', None) |
394 objmodname = getattr(obj, '__module__', None) |
395 if objmodname != modname: |
395 if objmodname != modname: |
396 if objmodname in self._toloadmods: |
396 if objmodname in self._toloadmods: |
407 return |
407 return |
408 self._loadedmods[modname][objname] = obj |
408 self._loadedmods[modname][objname] = obj |
409 for parent in obj.__bases__: |
409 for parent in obj.__bases__: |
410 self._load_ancestors_then_object(modname, parent) |
410 self._load_ancestors_then_object(modname, parent) |
411 self.load_object(obj) |
411 self.load_object(obj) |
412 |
412 |
413 def load_object(self, obj): |
413 def load_object(self, obj): |
414 try: |
414 try: |
415 self.register_vobject_class(obj) |
415 self.register_vobject_class(obj) |
416 except Exception, ex: |
416 except Exception, ex: |
417 if self.config.mode in ('test', 'dev'): |
417 if self.config.mode in ('test', 'dev'): |
418 raise |
418 raise |
419 self.exception('vobject %s registration failed: %s', obj, ex) |
419 self.exception('vobject %s registration failed: %s', obj, ex) |
420 |
420 |
421 # old automatic registration XXX deprecated ############################### |
421 # old automatic registration XXX deprecated ############################### |
422 |
422 |
423 def register_vobject_class(self, cls): |
423 def register_vobject_class(self, cls): |
424 """handle vobject class registration |
424 """handle vobject class registration |
425 |
425 |
426 vobject class with __abstract__ == True in their local dictionnary or |
426 vobject class with __abstract__ == True in their local dictionnary or |
427 with a name starting starting by an underscore are not registered. |
427 with a name starting starting by an underscore are not registered. |
428 Also a vobject class needs to have __registry__ and id attributes set |
428 Also a vobject class needs to have __registry__ and id attributes set |
429 to a non empty string to be registered. |
429 to a non empty string to be registered. |
430 """ |
430 """ |
433 return |
433 return |
434 regname = cls.__registry__ |
434 regname = cls.__registry__ |
435 if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']: |
435 if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']: |
436 return |
436 return |
437 self.register(cls) |
437 self.register(cls) |
438 |
438 |
439 def unregister_module_vobjects(self, modname): |
439 def unregister_module_vobjects(self, modname): |
440 """removes registered objects coming from a given module |
440 """removes registered objects coming from a given module |
441 |
441 |
442 returns a dictionnary classid/class of all classes that will need |
442 returns a dictionnary classid/class of all classes that will need |
443 to be updated after reload (i.e. vobjects referencing classes defined |
443 to be updated after reload (i.e. vobjects referencing classes defined |
495 # update obj's baseclasses tuple (__bases__) if needed |
495 # update obj's baseclasses tuple (__bases__) if needed |
496 if newbases != obj.__bases__: |
496 if newbases != obj.__bases__: |
497 self.debug('updating %s.%s base classes', |
497 self.debug('updating %s.%s base classes', |
498 obj.__module__, obj.__name__) |
498 obj.__module__, obj.__name__) |
499 obj.__bases__ = newbases |
499 obj.__bases__ = newbases |
500 |
500 |
501 # init logging |
501 # init logging |
502 set_log_methods(VObject, getLogger('cubicweb')) |
502 set_log_methods(VObject, getLogger('cubicweb')) |
503 set_log_methods(VRegistry, getLogger('cubicweb.registry')) |
503 set_log_methods(VRegistry, getLogger('cubicweb.registry')) |
504 |
504 |
505 |
505 |
506 # selector base classes and operations ######################################## |
506 # selector base classes and operations ######################################## |
535 return self |
535 return self |
536 return None |
536 return None |
537 |
537 |
538 def __str__(self): |
538 def __str__(self): |
539 return self.__class__.__name__ |
539 return self.__class__.__name__ |
540 |
540 |
541 def __and__(self, other): |
541 def __and__(self, other): |
542 return AndSelector(self, other) |
542 return AndSelector(self, other) |
543 def __rand__(self, other): |
543 def __rand__(self, other): |
544 return AndSelector(other, self) |
544 return AndSelector(other, self) |
545 |
545 |
548 def __ror__(self, other): |
548 def __ror__(self, other): |
549 return OrSelector(other, self) |
549 return OrSelector(other, self) |
550 |
550 |
551 def __invert__(self): |
551 def __invert__(self): |
552 return NotSelector(self) |
552 return NotSelector(self) |
553 |
553 |
554 # XXX (function | function) or (function & function) not managed yet |
554 # XXX (function | function) or (function & function) not managed yet |
555 |
555 |
556 def __call__(self, cls, *args, **kwargs): |
556 def __call__(self, cls, *args, **kwargs): |
557 return NotImplementedError("selector %s must implement its logic " |
557 return NotImplementedError("selector %s must implement its logic " |
558 "in its __call__ method" % self.__class__) |
558 "in its __call__ method" % self.__class__) |
559 |
559 |
560 class MultiSelector(Selector): |
560 class MultiSelector(Selector): |
561 """base class for compound selector classes""" |
561 """base class for compound selector classes""" |
562 |
562 |
563 def __init__(self, *selectors): |
563 def __init__(self, *selectors): |
564 self.selectors = self.merge_selectors(selectors) |
564 self.selectors = self.merge_selectors(selectors) |
565 |
565 |
566 def __str__(self): |
566 def __str__(self): |
567 return '%s(%s)' % (self.__class__.__name__, |
567 return '%s(%s)' % (self.__class__.__name__, |
598 found = childselector.search_selector(selector) |
598 found = childselector.search_selector(selector) |
599 if found is not None: |
599 if found is not None: |
600 return found |
600 return found |
601 return None |
601 return None |
602 |
602 |
603 |
603 |
604 def objectify_selector(selector_func): |
604 def objectify_selector(selector_func): |
605 """convenience decorator for simple selectors where a class definition |
605 """convenience decorator for simple selectors where a class definition |
606 would be overkill:: |
606 would be overkill:: |
607 |
607 |
608 @objectify_selector |
608 @objectify_selector |
609 def yes(cls, *args, **kwargs): |
609 def yes(cls, *args, **kwargs): |
610 return 1 |
610 return 1 |
611 |
611 |
612 """ |
612 """ |
613 return type(selector_func.__name__, (Selector,), |
613 return type(selector_func.__name__, (Selector,), |
614 {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)}) |
614 {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)}) |
615 |
615 |
616 def _instantiate_selector(selector): |
616 def _instantiate_selector(selector): |
617 """ensures `selector` is a `Selector` instance |
617 """ensures `selector` is a `Selector` instance |
618 |
618 |
619 NOTE: This should only be used locally in build___select__() |
619 NOTE: This should only be used locally in build___select__() |
620 XXX: then, why not do it ?? |
620 XXX: then, why not do it ?? |
621 """ |
621 """ |
622 if isinstance(selector, types.FunctionType): |
622 if isinstance(selector, types.FunctionType): |
623 return objectify_selector(selector)() |
623 return objectify_selector(selector)() |