200 from logilab.common.deprecation import class_renamed |
200 from logilab.common.deprecation import class_renamed |
201 from logilab.common.compat import all, any |
201 from logilab.common.compat import all, any |
202 from logilab.common.interface import implements as implements_iface |
202 from logilab.common.interface import implements as implements_iface |
203 |
203 |
204 from yams import BASE_TYPES |
204 from yams import BASE_TYPES |
205 |
205 from rql.nodes import Function |
206 from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role |
206 |
|
207 from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity, |
|
208 CW_EVENT_MANAGER, role) |
207 # even if not used, let yes here so it's importable through this module |
209 # even if not used, let yes here so it's importable through this module |
208 from cubicweb.appobject import Selector, objectify_selector, yes |
210 from cubicweb.appobject import Selector, objectify_selector, lltrace, yes |
209 from cubicweb.vregistry import class_regid |
|
210 from cubicweb.cwconfig import CubicWebConfiguration |
|
211 from cubicweb.schema import split_expression |
211 from cubicweb.schema import split_expression |
212 |
212 |
213 # helpers for debugging selectors |
213 from cubicweb.appobject import traced_selection # XXX for bw compat |
214 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors') |
|
215 TRACED_OIDS = None |
|
216 |
|
217 def _trace_selector(cls, selector, args, ret): |
|
218 # /!\ lltrace decorates pure function or __call__ method, this |
|
219 # means argument order may be different |
|
220 if isinstance(cls, Selector): |
|
221 selname = str(cls) |
|
222 vobj = args[0] |
|
223 else: |
|
224 selname = selector.__name__ |
|
225 vobj = cls |
|
226 if TRACED_OIDS == 'all' or class_regid(vobj) in TRACED_OIDS: |
|
227 #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls) |
|
228 print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__) |
|
229 |
|
230 def lltrace(selector): |
|
231 """use this decorator on your selectors so the becomes traceable with |
|
232 :class:`traced_selection` |
|
233 """ |
|
234 # don't wrap selectors if not in development mode |
|
235 if CubicWebConfiguration.mode == 'system': # XXX config.debug |
|
236 return selector |
|
237 def traced(cls, *args, **kwargs): |
|
238 ret = selector(cls, *args, **kwargs) |
|
239 if TRACED_OIDS is not None: |
|
240 _trace_selector(cls, selector, args, ret) |
|
241 return ret |
|
242 traced.__name__ = selector.__name__ |
|
243 traced.__doc__ = selector.__doc__ |
|
244 return traced |
|
245 |
|
246 class traced_selection(object): |
|
247 """ |
|
248 Typical usage is : |
|
249 |
|
250 .. sourcecode:: python |
|
251 |
|
252 >>> from cubicweb.selectors import traced_selection |
|
253 >>> with traced_selection(): |
|
254 ... # some code in which you want to debug selectors |
|
255 ... # for all objects |
|
256 |
|
257 Don't forget the 'from __future__ import with_statement' at the module top-level |
|
258 if you're using python prior to 2.6. |
|
259 |
|
260 This will yield lines like this in the logs:: |
|
261 |
|
262 selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'> |
|
263 |
|
264 You can also give to :class:`traced_selection` the identifiers of objects on |
|
265 which you want to debug selection ('oid1' and 'oid2' in the example above). |
|
266 |
|
267 .. sourcecode:: python |
|
268 |
|
269 >>> with traced_selection( ('regid1', 'regid2') ): |
|
270 ... # some code in which you want to debug selectors |
|
271 ... # for objects with __regid__ 'regid1' and 'regid2' |
|
272 |
|
273 A potentially usefull point to set up such a tracing function is |
|
274 the `cubicweb.vregistry.Registry.select` method body. |
|
275 """ |
|
276 |
|
277 def __init__(self, traced='all'): |
|
278 self.traced = traced |
|
279 |
|
280 def __enter__(self): |
|
281 global TRACED_OIDS |
|
282 TRACED_OIDS = self.traced |
|
283 |
|
284 def __exit__(self, exctype, exc, traceback): |
|
285 global TRACED_OIDS |
|
286 TRACED_OIDS = None |
|
287 return traceback is None |
|
288 |
|
289 |
214 |
290 def score_interface(etypesreg, cls_or_inst, cls, iface): |
215 def score_interface(etypesreg, cls_or_inst, cls, iface): |
291 """Return XXX if the give object (maybe an instance or class) implements |
216 """Return XXX if the give object (maybe an instance or class) implements |
292 the interface. |
217 the interface. |
293 """ |
218 """ |
319 def __call__(self, cls, *args, **kwargs): |
245 def __call__(self, cls, *args, **kwargs): |
320 self.complete(cls) |
246 self.complete(cls) |
321 return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs) |
247 return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs) |
322 |
248 |
323 |
249 |
324 class ImplementsMixIn(object): |
|
325 """mix-in class for selectors checking implemented interfaces of something |
|
326 """ |
|
327 def __init__(self, *expected_ifaces, **kwargs): |
|
328 super(ImplementsMixIn, self).__init__(**kwargs) |
|
329 self.expected_ifaces = expected_ifaces |
|
330 |
|
331 def __str__(self): |
|
332 return '%s(%s)' % (self.__class__.__name__, |
|
333 ','.join(str(s) for s in self.expected_ifaces)) |
|
334 |
|
335 def score_interfaces(self, req, cls_or_inst, cls): |
|
336 score = 0 |
|
337 etypesreg = req.vreg['etypes'] |
|
338 for iface in self.expected_ifaces: |
|
339 if isinstance(iface, basestring): |
|
340 # entity type |
|
341 try: |
|
342 iface = etypesreg.etype_class(iface) |
|
343 except KeyError: |
|
344 continue # entity type not in the schema |
|
345 score += score_interface(etypesreg, cls_or_inst, cls, iface) |
|
346 return score |
|
347 |
|
348 |
|
349 class EClassSelector(Selector): |
250 class EClassSelector(Selector): |
350 """abstract class for selectors working on *entity class(es)* specified |
251 """abstract class for selectors working on *entity class(es)* specified |
351 explicitly or found of the result set. |
252 explicitly or found of the result set. |
352 |
253 |
353 Here are entity lookup / scoring rules: |
254 Here are entity lookup / scoring rules: |
373 def __init__(self, once_is_enough=False, accept_none=True): |
274 def __init__(self, once_is_enough=False, accept_none=True): |
374 self.once_is_enough = once_is_enough |
275 self.once_is_enough = once_is_enough |
375 self.accept_none = accept_none |
276 self.accept_none = accept_none |
376 |
277 |
377 @lltrace |
278 @lltrace |
378 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
279 def __call__(self, cls, req, rset=None, row=None, col=0, accept_none=None, |
|
280 **kwargs): |
379 if kwargs.get('entity'): |
281 if kwargs.get('entity'): |
380 return self.score_class(kwargs['entity'].__class__, req) |
282 return self.score_class(kwargs['entity'].__class__, req) |
381 if not rset: |
283 if not rset: |
382 return 0 |
284 return 0 |
383 score = 0 |
285 score = 0 |
384 if row is None: |
286 if row is None: |
385 if not self.accept_none: |
287 if accept_none is None: |
|
288 accept_none = self.accept_none |
|
289 if not accept_none: |
386 if any(rset[i][col] is None for i in xrange(len(rset))): |
290 if any(rset[i][col] is None for i in xrange(len(rset))): |
387 return 0 |
291 return 0 |
388 for etype in rset.column_types(col): |
292 for etype in rset.column_types(col): |
389 if etype is None: # outer join |
293 if etype is None: # outer join |
390 return 0 |
294 return 0 |
440 while the former works on each *entity* (eg each row of the result set), |
344 while the former works on each *entity* (eg each row of the result set), |
441 which may be much more costly. |
345 which may be much more costly. |
442 """ |
346 """ |
443 |
347 |
444 @lltrace |
348 @lltrace |
445 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
349 def __call__(self, cls, req, rset=None, row=None, col=0, accept_none=None, |
|
350 **kwargs): |
446 if not rset and not kwargs.get('entity'): |
351 if not rset and not kwargs.get('entity'): |
447 return 0 |
352 return 0 |
448 score = 0 |
353 score = 0 |
449 if kwargs.get('entity'): |
354 if kwargs.get('entity'): |
450 score = self.score_entity(kwargs['entity']) |
355 score = self.score_entity(kwargs['entity']) |
451 elif row is None: |
356 elif row is None: |
452 col = col or 0 |
357 col = col or 0 |
|
358 if accept_none is None: |
|
359 accept_none = self.accept_none |
453 for row, rowvalue in enumerate(rset.rows): |
360 for row, rowvalue in enumerate(rset.rows): |
454 if rowvalue[col] is None: # outer join |
361 if rowvalue[col] is None: # outer join |
455 if not self.accept_none: |
362 if not accept_none: |
456 return 0 |
363 return 0 |
457 continue |
364 continue |
458 escore = self.score(req, rset, row, col) |
365 escore = self.score(req, rset, row, col) |
459 if not escore and not self.once_is_enough: |
366 if not escore and not self.once_is_enough: |
460 return 0 |
367 return 0 |
480 |
387 |
481 class ExpectedValueSelector(Selector): |
388 class ExpectedValueSelector(Selector): |
482 """Take a list of expected values as initializer argument and store them |
389 """Take a list of expected values as initializer argument and store them |
483 into the :attr:`expected` set attribute. |
390 into the :attr:`expected` set attribute. |
484 |
391 |
485 You should implements the :meth:`_get_value(cls, req, **kwargs)` method |
392 You should implement the :meth:`_get_value(cls, req, **kwargs)` method |
486 which should return the value for the given context. The selector will then |
393 which should return the value for the given context. The selector will then |
487 return 1 if the value is expected, else 0. |
394 return 1 if the value is expected, else 0. |
488 """ |
395 """ |
489 def __init__(self, *expected): |
396 def __init__(self, *expected): |
490 assert expected, self |
397 assert expected, self |
526 |
433 |
527 Initializer arguments: |
434 Initializer arguments: |
528 |
435 |
529 * `registry`, a registry name |
436 * `registry`, a registry name |
530 |
437 |
531 * `regid`, an object identifier in this registry |
438 * `regids`, object identifiers in this registry, one of them should be |
532 """ |
439 selectable. |
533 def __init__(self, registry, regid): |
440 """ |
|
441 selectable_score = 1 |
|
442 def __init__(self, registry, *regids): |
534 self.registry = registry |
443 self.registry = registry |
535 self.regid = regid |
444 self.regids = regids |
536 |
445 |
|
446 @lltrace |
537 def __call__(self, cls, req, **kwargs): |
447 def __call__(self, cls, req, **kwargs): |
538 try: |
448 for regid in self.regids: |
539 req.vreg[self.registry].select(self.regid, req, **kwargs) |
449 try: |
540 return 1 |
450 req.vreg[self.registry].select(regid, req, **kwargs) |
541 except NoSelectableObject: |
451 return self.selectable_score |
542 return 0 |
452 except NoSelectableObject: |
543 |
453 return 0 |
|
454 |
|
455 |
|
456 class adaptable(appobject_selectable): |
|
457 """Return 1 if another appobject is selectable using the same input context. |
|
458 |
|
459 Initializer arguments: |
|
460 |
|
461 * `regids`, adapter identifiers (e.g. interface names) to which the context |
|
462 (usually entities) should be adaptable. One of them should be selectable |
|
463 when multiple identifiers are given. |
|
464 """ |
|
465 # being adaptable to an interface takes precedence other is_instance('Any'), |
|
466 # hence return 2 (is_instance('Any') score is 1) |
|
467 selectable_score = 2 |
|
468 def __init__(self, *regids): |
|
469 super(adaptable, self).__init__('adapters', *regids) |
|
470 |
|
471 def __call__(self, cls, req, **kwargs): |
|
472 kwargs.setdefault('accept_none', False) |
|
473 return super(adaptable, self).__call__(cls, req, **kwargs) |
544 |
474 |
545 # rset selectors ############################################################## |
475 # rset selectors ############################################################## |
546 |
476 |
547 @objectify_selector |
477 @objectify_selector |
548 @lltrace |
478 @lltrace |
584 |
514 |
585 # XXX == multi_lines_rset(1) |
515 # XXX == multi_lines_rset(1) |
586 @objectify_selector |
516 @objectify_selector |
587 @lltrace |
517 @lltrace |
588 def one_line_rset(cls, req, rset=None, row=None, **kwargs): |
518 def one_line_rset(cls, req, rset=None, row=None, **kwargs): |
589 """Return 1 if the result set is of size 1 or if a specific row in the |
519 """Return 1 if the result set is of size 1, or greater but a specific row in |
590 result set is specified ('row' argument). |
520 the result set is specified ('row' argument). |
591 """ |
521 """ |
592 if rset is not None and (row is not None or rset.rowcount == 1): |
522 if rset is not None and (row is not None or rset.rowcount == 1): |
593 return 1 |
523 return 1 |
594 return 0 |
524 return 0 |
595 |
525 |
596 |
526 |
597 class multi_lines_rset(Selector): |
527 class multi_lines_rset(Selector): |
598 """If `nb`is specified, return 1 if the result set has exactly `nb` row of |
528 """If `nb` is specified, return 1 if the result set has exactly `nb` row of |
599 result. Else (`nb` is None), return 1 if the result set contains *at least* |
529 result. Else (`nb` is None), return 1 if the result set contains *at least* |
600 two rows. |
530 two rows. |
601 """ |
531 """ |
602 def __init__(self, nb=None): |
532 def __init__(self, nb=None): |
603 self.expected = nb |
533 self.expected = nb |
607 return num > 1 |
537 return num > 1 |
608 return num == self.expected |
538 return num == self.expected |
609 |
539 |
610 @lltrace |
540 @lltrace |
611 def __call__(self, cls, req, rset=None, **kwargs): |
541 def __call__(self, cls, req, rset=None, **kwargs): |
612 return rset is not None and self.match_expected(rset.rowcount) |
542 return int(rset is not None and self.match_expected(rset.rowcount)) |
613 |
543 |
614 |
544 |
615 class multi_columns_rset(multi_lines_rset): |
545 class multi_columns_rset(multi_lines_rset): |
616 """If `nb`is specified, return 1 if the result set has exactly `nb` column |
546 """If `nb` is specified, return 1 if the result set has exactly `nb` column |
617 per row. Else (`nb` is None), return 1 if the result set contains *at least* |
547 per row. Else (`nb` is None), return 1 if the result set contains *at least* |
618 two columns per row. Return 0 for empty result set. |
548 two columns per row. Return 0 for empty result set. |
619 """ |
549 """ |
620 |
550 |
621 @lltrace |
551 @lltrace |
710 # entity selectors ############################################################# |
645 # entity selectors ############################################################# |
711 |
646 |
712 class non_final_entity(EClassSelector): |
647 class non_final_entity(EClassSelector): |
713 """Return 1 for entity of a non final entity type(s). Remember, "final" |
648 """Return 1 for entity of a non final entity type(s). Remember, "final" |
714 entity types are String, Int, etc... This is equivalent to |
649 entity types are String, Int, etc... This is equivalent to |
715 `implements('Any')` but more optimized. |
650 `is_instance('Any')` but more optimized. |
716 |
651 |
717 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
652 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
718 class lookup / score rules according to the input context. |
653 class lookup / score rules according to the input context. |
719 """ |
654 """ |
720 def score(self, cls, req, etype): |
655 def score(self, cls, req, etype): |
737 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
672 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
738 class lookup / score rules according to the input context. |
673 class lookup / score rules according to the input context. |
739 |
674 |
740 .. note:: when interface is an entity class, the score will reflect class |
675 .. note:: when interface is an entity class, the score will reflect class |
741 proximity so the most specific object will be selected. |
676 proximity so the most specific object will be selected. |
742 """ |
677 |
|
678 .. note:: deprecated in cubicweb >= 3.9, use either |
|
679 :class:`~cubicweb.selectors.is_instance` or |
|
680 :class:`~cubicweb.selectors.adaptable`. |
|
681 """ |
|
682 |
|
683 def __init__(self, *expected_ifaces, **kwargs): |
|
684 emit_warn = kwargs.pop('warn', True) |
|
685 super(implements, self).__init__(**kwargs) |
|
686 self.expected_ifaces = expected_ifaces |
|
687 if emit_warn: |
|
688 warn('[3.9] implements selector is deprecated, use either ' |
|
689 'is_instance or adaptable', DeprecationWarning, stacklevel=2) |
|
690 |
|
691 def __str__(self): |
|
692 return '%s(%s)' % (self.__class__.__name__, |
|
693 ','.join(str(s) for s in self.expected_ifaces)) |
|
694 |
743 def score_class(self, eclass, req): |
695 def score_class(self, eclass, req): |
744 return self.score_interfaces(req, eclass, eclass) |
696 return self.score_interfaces(req, eclass, eclass) |
|
697 |
|
698 def score_interfaces(self, req, cls_or_inst, cls): |
|
699 score = 0 |
|
700 etypesreg = req.vreg['etypes'] |
|
701 for iface in self.expected_ifaces: |
|
702 if isinstance(iface, basestring): |
|
703 # entity type |
|
704 try: |
|
705 iface = etypesreg.etype_class(iface) |
|
706 except KeyError: |
|
707 continue # entity type not in the schema |
|
708 score += score_interface(etypesreg, cls_or_inst, cls, iface) |
|
709 return score |
|
710 |
|
711 def _reset_is_instance_cache(vreg): |
|
712 vreg._is_instance_selector_cache = {} |
|
713 |
|
714 CW_EVENT_MANAGER.bind('before-registry-reset', _reset_is_instance_cache) |
|
715 |
|
716 class is_instance(EClassSelector): |
|
717 """Return non-zero score for entity that is an instance of the one of given |
|
718 type(s). If multiple arguments are given, matching one of them is enough. |
|
719 |
|
720 Entity types should be given as string, the corresponding class will be |
|
721 fetched from the registry at selection time. |
|
722 |
|
723 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
|
724 class lookup / score rules according to the input context. |
|
725 |
|
726 .. note:: the score will reflect class proximity so the most specific object |
|
727 will be selected. |
|
728 """ |
|
729 |
|
730 def __init__(self, *expected_etypes, **kwargs): |
|
731 super(is_instance, self).__init__(**kwargs) |
|
732 self.expected_etypes = expected_etypes |
|
733 for etype in self.expected_etypes: |
|
734 assert isinstance(etype, basestring), etype |
|
735 |
|
736 def __str__(self): |
|
737 return '%s(%s)' % (self.__class__.__name__, |
|
738 ','.join(str(s) for s in self.expected_etypes)) |
|
739 |
|
740 def score_class(self, eclass, req): |
|
741 return self.score_etypes(req, eclass, eclass) |
|
742 |
|
743 def score_etypes(self, req, cls_or_inst, cls): |
|
744 # cache on vreg to avoid reloading issues |
|
745 cache = req.vreg._is_instance_selector_cache |
|
746 try: |
|
747 expected_eclasses = cache[self] |
|
748 except KeyError: |
|
749 # turn list of entity types as string into a list of |
|
750 # (entity class, parent classes) |
|
751 etypesreg = req.vreg['etypes'] |
|
752 expected_eclasses = cache[self] = [] |
|
753 for etype in self.expected_etypes: |
|
754 try: |
|
755 expected_eclasses.append( |
|
756 (etypesreg.etype_class(etype), |
|
757 etypesreg.parent_classes(etype)) |
|
758 ) |
|
759 except KeyError: |
|
760 continue # entity type not in the schema |
|
761 score = 0 |
|
762 for iface, parents in expected_eclasses: |
|
763 # adjust score according to class proximity |
|
764 if iface is cls: |
|
765 score += len(parents) + 4 |
|
766 elif iface is parents[-1]: # Any |
|
767 score += 1 |
|
768 else: |
|
769 for index, basecls in enumerate(reversed(parents[:-1])): |
|
770 if iface is basecls: |
|
771 score += index + 3 |
|
772 break |
|
773 return score |
745 |
774 |
746 |
775 |
747 class score_entity(EntitySelector): |
776 class score_entity(EntitySelector): |
748 """Return score according to an arbitrary function given as argument which |
777 """Return score according to an arbitrary function given as argument which |
749 will be called with input content entity as argument. |
778 will be called with input content entity as argument. |
762 return 0 |
791 return 0 |
763 if isinstance(score, (int, long)): |
792 if isinstance(score, (int, long)): |
764 return score |
793 return score |
765 return 1 |
794 return 1 |
766 self.score_entity = intscore |
795 self.score_entity = intscore |
|
796 |
|
797 |
|
798 class has_mimetype(EntitySelector): |
|
799 """Return 1 if the entity adapt to IDownloadable and has the given MIME type. |
|
800 |
|
801 You can give 'image/' to match any image for instance, or 'image/png' to match |
|
802 only PNG images. |
|
803 """ |
|
804 def __init__(self, mimetype, once_is_enough=False): |
|
805 super(has_mimetype, self).__init__(once_is_enough) |
|
806 self.mimetype = mimetype |
|
807 |
|
808 def score_entity(self, entity): |
|
809 idownloadable = entity.cw_adapt_to('IDownloadable') |
|
810 if idownloadable is None: |
|
811 return 0 |
|
812 mt = idownloadable.download_content_type() |
|
813 if not (mt and mt.startswith(self.mimetype)): |
|
814 return 0 |
|
815 return 1 |
767 |
816 |
768 |
817 |
769 class relation_possible(EntitySelector): |
818 class relation_possible(EntitySelector): |
770 """Return 1 for entity that supports the relation, provided that the |
819 """Return 1 for entity that supports the relation, provided that the |
771 request's user may do some `action` on it (see below). |
820 request's user may do some `action` on it (see below). |
1231 if not param in req.form: |
1280 if not param in req.form: |
1232 return 0 |
1281 return 0 |
1233 return len(self.expected) |
1282 return len(self.expected) |
1234 |
1283 |
1235 |
1284 |
1236 class specified_etype_implements(implements): |
1285 class specified_etype_implements(is_instance): |
1237 """Return non-zero score if the entity type specified by an 'etype' key |
1286 """Return non-zero score if the entity type specified by an 'etype' key |
1238 searched in (by priority) input context kwargs and request form parameters |
1287 searched in (by priority) input context kwargs and request form parameters |
1239 match a known entity type (case insensitivly), and it's associated entity |
1288 match a known entity type (case insensitivly), and it's associated entity |
1240 class is of one of the type(s) given to the initializer or implements at |
1289 class is of one of the type(s) given to the initializer. If multiple |
1241 least one of the given interfaces. If multiple arguments are given, matching |
1290 arguments are given, matching one of them is enough. |
1242 one of them is enough. |
1291 |
1243 |
1292 .. note:: as with :class:`~cubicweb.selectors.is_instance`, entity types |
1244 Entity types should be given as string, the corresponding class will be |
1293 should be given as string and the score will reflect class |
1245 fetched from the entity types registry at selection time. |
|
1246 |
|
1247 .. note:: when interface is an entity class, the score will reflect class |
|
1248 proximity so the most specific object will be selected. |
1294 proximity so the most specific object will be selected. |
1249 |
1295 |
1250 This selector is usually used by views holding entity creation forms (since |
1296 This selector is usually used by views holding entity creation forms (since |
1251 we've no result set to work on). |
1297 we've no result set to work on). |
1252 """ |
1298 """ |
1298 return 0 |
1344 return 0 |
1299 |
1345 |
1300 class is_in_state(score_entity): |
1346 class is_in_state(score_entity): |
1301 """return 1 if entity is in one of the states given as argument list |
1347 """return 1 if entity is in one of the states given as argument list |
1302 |
1348 |
1303 you should use this instead of your own score_entity x: x.state == 'bla' |
1349 you should use this instead of your own :class:`score_entity` selector to |
1304 selector to avoid some gotchas: |
1350 avoid some gotchas: |
1305 |
1351 |
1306 * possible views gives a fake entity with no state |
1352 * possible views gives a fake entity with no state |
1307 * you must use the latest tr info, not entity.state for repository side |
1353 * you must use the latest tr info, not entity.in_state for repository side |
1308 checking of the current state |
1354 checking of the current state |
1309 """ |
1355 """ |
1310 def __init__(self, *states): |
1356 def __init__(self, *states): |
1311 def score(entity, states=set(states)): |
1357 def score(entity, states=set(states)): |
|
1358 trinfo = entity.cw_adapt_to('IWorkflowable').latest_trinfo() |
1312 try: |
1359 try: |
1313 return entity.latest_trinfo().new_state.name in states |
1360 return trinfo.new_state.name in states |
1314 except AttributeError: |
1361 except AttributeError: |
1315 return None |
1362 return None |
1316 super(is_in_state, self).__init__(score) |
1363 super(is_in_state, self).__init__(score) |
1317 |
1364 |
|
1365 @objectify_selector |
|
1366 def debug_mode(cls, req, rset=None, **kwargs): |
|
1367 """Return 1 if running in debug mode""" |
|
1368 return req.vreg.config.debugmode and 1 or 0 |
1318 |
1369 |
1319 ## deprecated stuff ############################################################ |
1370 ## deprecated stuff ############################################################ |
1320 |
1371 |
1321 entity_implements = class_renamed('entity_implements', implements) |
1372 entity_implements = class_renamed('entity_implements', is_instance) |
1322 |
1373 |
1323 class _but_etype(EntitySelector): |
1374 class _but_etype(EntitySelector): |
1324 """accept if the given entity types are not found in the result set. |
1375 """accept if the given entity types are not found in the result set. |
1325 |
1376 |
1326 See `EntitySelector` documentation for behaviour when row is not specified. |
1377 See `EntitySelector` documentation for behaviour when row is not specified. |