1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # |
3 # |
4 # This file is part of CubicWeb. |
4 # This file is part of CubicWeb. |
5 # |
5 # |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
231 :class:`~cubicweb.server.session.hooks_control` context manager to |
231 :class:`~cubicweb.server.session.hooks_control` context manager to |
232 filter them in or out. Note that ending the transaction with commit() |
232 filter them in or out. Note that ending the transaction with commit() |
233 or rollback() will restore the hooks. |
233 or rollback() will restore the hooks. |
234 |
234 |
235 |
235 |
236 Hooks specific selector |
236 Hooks specific predicate |
237 ~~~~~~~~~~~~~~~~~~~~~~~ |
237 ~~~~~~~~~~~~~~~~~~~~~~~ |
238 .. autoclass:: cubicweb.server.hook.match_rtype |
238 .. autoclass:: cubicweb.server.hook.match_rtype |
239 .. autoclass:: cubicweb.server.hook.match_rtype_sets |
239 .. autoclass:: cubicweb.server.hook.match_rtype_sets |
240 |
240 |
241 |
241 |
256 from itertools import chain |
256 from itertools import chain |
257 |
257 |
258 from logilab.common.decorators import classproperty, cached |
258 from logilab.common.decorators import classproperty, cached |
259 from logilab.common.deprecation import deprecated, class_renamed |
259 from logilab.common.deprecation import deprecated, class_renamed |
260 from logilab.common.logging_ext import set_log_methods |
260 from logilab.common.logging_ext import set_log_methods |
|
261 from logilab.common.registry import (Predicate, NotPredicate, OrPredicate, |
|
262 classid, objectify_predicate, yes) |
261 |
263 |
262 from cubicweb import RegistryNotFound |
264 from cubicweb import RegistryNotFound |
263 from cubicweb.vregistry import classid |
265 from cubicweb.cwvreg import CWRegistry, CWRegistryStore |
264 from cubicweb.cwvreg import CWRegistry, VRegistry |
266 from cubicweb.predicates import ExpectedValuePredicate, is_instance |
265 from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector, |
267 from cubicweb.appobject import AppObject |
266 is_instance) |
|
267 from cubicweb.appobject import AppObject, NotSelector, OrSelector |
|
268 from cubicweb.server.session import security_enabled |
268 from cubicweb.server.session import security_enabled |
269 |
269 |
270 ENTITIES_HOOKS = set(('before_add_entity', 'after_add_entity', |
270 ENTITIES_HOOKS = set(('before_add_entity', 'after_add_entity', |
271 'before_update_entity', 'after_update_entity', |
271 'before_update_entity', 'after_update_entity', |
272 'before_delete_entity', 'after_delete_entity')) |
272 'before_delete_entity', 'after_delete_entity')) |
336 the idea is to make a first pass over all the hooks in the |
336 the idea is to make a first pass over all the hooks in the |
337 registry and to mark put some of them in a pruned list. The |
337 registry and to mark put some of them in a pruned list. The |
338 pruned hooks are the one which: |
338 pruned hooks are the one which: |
339 |
339 |
340 * are disabled at the session level |
340 * are disabled at the session level |
341 * have a match_rtype or an is_instance selector which does not |
341 |
342 match the rtype / etype of the relations / entities for |
342 * have a selector containing a :class:`match_rtype` or an |
343 which we are calling the hooks. This works because the |
343 :class:`is_instance` predicate which does not match the rtype / etype |
344 repository calls the hooks grouped by rtype or by etype when |
344 of the relations / entities for which we are calling the hooks. This |
345 using the entities or eids_to_from keyword arguments |
345 works because the repository calls the hooks grouped by rtype or by |
346 |
346 etype when using the entities or eids_to_from keyword arguments |
347 Only hooks with a simple selector or an AndSelector of simple |
347 |
348 selectors are considered for disabling. |
348 Only hooks with a simple predicate or an AndPredicate of simple |
|
349 predicates are considered for disabling. |
349 |
350 |
350 """ |
351 """ |
351 if 'entity' in kwargs: |
352 if 'entity' in kwargs: |
352 entities = [kwargs['entity']] |
353 entities = [kwargs['entity']] |
353 if len(entities): |
354 if len(entities): |
408 return # no hooks for this event |
409 return # no hooks for this event |
409 registry.call_hooks(event, session, **kwargs) |
410 registry.call_hooks(event, session, **kwargs) |
410 |
411 |
411 |
412 |
412 for event in ALL_HOOKS: |
413 for event in ALL_HOOKS: |
413 VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry |
414 CWRegistryStore.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry |
414 |
415 |
415 @deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)') |
416 @deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)') |
416 def entity_oldnewvalue(entity, attr): |
417 def entity_oldnewvalue(entity, attr): |
417 return entity.cw_edited.oldnewvalue(attr) |
418 return entity.cw_edited.oldnewvalue(attr) |
418 |
419 |
419 |
420 |
420 # some hook specific selectors ################################################# |
421 # some hook specific predicates ################################################# |
421 |
422 |
422 @objectify_selector |
423 @objectify_predicate |
423 @lltrace |
|
424 def enabled_category(cls, req, **kwargs): |
424 def enabled_category(cls, req, **kwargs): |
425 if req is None: |
425 if req is None: |
426 return True # XXX how to deactivate server startup / shutdown event |
426 return True # XXX how to deactivate server startup / shutdown event |
427 return req.is_hook_activated(cls) |
427 return req.is_hook_activated(cls) |
428 |
428 |
429 @objectify_selector |
429 @objectify_predicate |
430 @lltrace |
|
431 def from_dbapi_query(cls, req, **kwargs): |
430 def from_dbapi_query(cls, req, **kwargs): |
432 if req.running_dbapi_query: |
431 if req.running_dbapi_query: |
433 return 1 |
432 return 1 |
434 return 0 |
433 return 0 |
435 |
434 |
438 self.iterators = iterators |
437 self.iterators = iterators |
439 def __iter__(self): |
438 def __iter__(self): |
440 return iter(chain(*self.iterators)) |
439 return iter(chain(*self.iterators)) |
441 |
440 |
442 |
441 |
443 class match_rtype(ExpectedValueSelector): |
442 class match_rtype(ExpectedValuePredicate): |
444 """accept if parameters specified as initializer arguments are specified |
443 """accept if parameters specified as initializer arguments are specified |
445 in named arguments given to the selector |
444 in named arguments given to the predicate |
446 |
445 |
447 :param \*expected: parameters (eg `basestring`) which are expected to be |
446 :param \*expected: parameters (eg `basestring`) which are expected to be |
448 found in named arguments (kwargs) |
447 found in named arguments (kwargs) |
449 """ |
448 """ |
450 def __init__(self, *expected, **more): |
449 def __init__(self, *expected, **more): |
451 self.expected = expected |
450 self.expected = expected |
452 self.frometypes = more.pop('frometypes', None) |
451 self.frometypes = more.pop('frometypes', None) |
453 self.toetypes = more.pop('toetypes', None) |
452 self.toetypes = more.pop('toetypes', None) |
454 assert not more, "unexpected kwargs in match_rtype: %s" % more |
453 assert not more, "unexpected kwargs in match_rtype: %s" % more |
455 |
454 |
456 @lltrace |
|
457 def __call__(self, cls, req, *args, **kwargs): |
455 def __call__(self, cls, req, *args, **kwargs): |
458 if kwargs.get('rtype') not in self.expected: |
456 if kwargs.get('rtype') not in self.expected: |
459 return 0 |
457 return 0 |
460 if self.frometypes is not None and \ |
458 if self.frometypes is not None and \ |
461 req.describe(kwargs['eidfrom'])[0] not in self.frometypes: |
459 req.describe(kwargs['eidfrom'])[0] not in self.frometypes: |
464 req.describe(kwargs['eidto'])[0] not in self.toetypes: |
462 req.describe(kwargs['eidto'])[0] not in self.toetypes: |
465 return 0 |
463 return 0 |
466 return 1 |
464 return 1 |
467 |
465 |
468 |
466 |
469 class match_rtype_sets(ExpectedValueSelector): |
467 class match_rtype_sets(ExpectedValuePredicate): |
470 """accept if the relation type is in one of the sets given as initializer |
468 """accept if the relation type is in one of the sets given as initializer |
471 argument. The goal of this selector is that it keeps reference to original sets, |
469 argument. The goal of this predicate is that it keeps reference to original sets, |
472 so modification to thoses sets are considered by the selector. For instance |
470 so modification to thoses sets are considered by the predicate. For instance |
473 |
471 |
474 MYSET = set() |
472 MYSET = set() |
475 |
473 |
476 class Hook1(Hook): |
474 class Hook1(Hook): |
477 __regid__ = 'hook1' |
475 __regid__ = 'hook1' |
487 """ |
485 """ |
488 |
486 |
489 def __init__(self, *expected): |
487 def __init__(self, *expected): |
490 self.expected = expected |
488 self.expected = expected |
491 |
489 |
492 @lltrace |
|
493 def __call__(self, cls, req, *args, **kwargs): |
490 def __call__(self, cls, req, *args, **kwargs): |
494 for rel_set in self.expected: |
491 for rel_set in self.expected: |
495 if kwargs.get('rtype') in rel_set: |
492 if kwargs.get('rtype') in rel_set: |
496 return 1 |
493 return 1 |
497 return 0 |
494 return 0 |
533 |
530 |
534 @classmethod |
531 @classmethod |
535 @cached |
532 @cached |
536 def filterable_selectors(cls): |
533 def filterable_selectors(cls): |
537 search = cls.__select__.search_selector |
534 search = cls.__select__.search_selector |
538 if search((NotSelector, OrSelector)): |
535 if search((NotPredicate, OrPredicate)): |
539 return None, None |
536 return None, None |
540 enabled_cat = search(enabled_category) |
537 enabled_cat = search(enabled_category) |
541 main_filter = search((is_instance, match_rtype)) |
538 main_filter = search((is_instance, match_rtype)) |
542 return enabled_cat, main_filter |
539 return enabled_cat, main_filter |
543 |
540 |
581 This hook ensure that when one of the watched relation is added, the |
578 This hook ensure that when one of the watched relation is added, the |
582 `main_rtype` relation is added to the target entity of the relation. |
579 `main_rtype` relation is added to the target entity of the relation. |
583 Notice there are no default behaviour defined when a watched relation is |
580 Notice there are no default behaviour defined when a watched relation is |
584 deleted, you'll have to handle this by yourself. |
581 deleted, you'll have to handle this by yourself. |
585 |
582 |
586 You usually want to use the :class:`match_rtype_sets` selector on concrete |
583 You usually want to use the :class:`match_rtype_sets` predicate on concrete |
587 classes. |
584 classes. |
588 """ |
585 """ |
589 events = ('after_add_relation',) |
586 events = ('after_add_relation',) |
590 |
587 |
591 # to set in concrete class |
588 # to set in concrete class |