240 cf. `cubicweb.web.action.LinkToEntityAction` for instance |
240 cf. `cubicweb.web.action.LinkToEntityAction` for instance |
241 """ |
241 """ |
242 def __call__(self, cls, *args, **kwargs): |
242 def __call__(self, cls, *args, **kwargs): |
243 self.complete(cls) |
243 self.complete(cls) |
244 return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs) |
244 return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs) |
245 |
|
246 |
|
247 class ImplementsMixIn(object): |
|
248 """mix-in class for selectors checking implemented interfaces of something |
|
249 """ |
|
250 def __init__(self, *expected_ifaces, **kwargs): |
|
251 super(ImplementsMixIn, self).__init__(**kwargs) |
|
252 self.expected_ifaces = expected_ifaces |
|
253 |
|
254 def __str__(self): |
|
255 return '%s(%s)' % (self.__class__.__name__, |
|
256 ','.join(str(s) for s in self.expected_ifaces)) |
|
257 |
|
258 def score_interfaces(self, req, cls_or_inst, cls): |
|
259 score = 0 |
|
260 etypesreg = req.vreg['etypes'] |
|
261 for iface in self.expected_ifaces: |
|
262 if isinstance(iface, basestring): |
|
263 # entity type |
|
264 try: |
|
265 iface = etypesreg.etype_class(iface) |
|
266 except KeyError: |
|
267 continue # entity type not in the schema |
|
268 score += score_interface(etypesreg, cls_or_inst, cls, iface) |
|
269 return score |
|
270 |
245 |
271 |
246 |
272 class EClassSelector(Selector): |
247 class EClassSelector(Selector): |
273 """abstract class for selectors working on *entity class(es)* specified |
248 """abstract class for selectors working on *entity class(es)* specified |
274 explicitly or found of the result set. |
249 explicitly or found of the result set. |
409 |
384 |
410 class ExpectedValueSelector(Selector): |
385 class ExpectedValueSelector(Selector): |
411 """Take a list of expected values as initializer argument and store them |
386 """Take a list of expected values as initializer argument and store them |
412 into the :attr:`expected` set attribute. |
387 into the :attr:`expected` set attribute. |
413 |
388 |
414 You should implements the :meth:`_get_value(cls, req, **kwargs)` method |
389 You should implement the :meth:`_get_value(cls, req, **kwargs)` method |
415 which should return the value for the given context. The selector will then |
390 which should return the value for the given context. The selector will then |
416 return 1 if the value is expected, else 0. |
391 return 1 if the value is expected, else 0. |
417 """ |
392 """ |
418 def __init__(self, *expected): |
393 def __init__(self, *expected): |
419 assert expected, self |
394 assert expected, self |
482 |
457 |
483 * `regids`, adapter identifiers (e.g. interface names) to which the context |
458 * `regids`, adapter identifiers (e.g. interface names) to which the context |
484 (usually entities) should be adaptable. One of them should be selectable |
459 (usually entities) should be adaptable. One of them should be selectable |
485 when multiple identifiers are given. |
460 when multiple identifiers are given. |
486 """ |
461 """ |
487 # implementing an interface takes precedence other special Any interface, |
462 # being adaptable to an interface takes precedence other is_instance('Any'), |
488 # hence return 2 (implements('Any') score is 1) |
463 # hence return 2 (is_instance('Any') score is 1) |
489 selectable_score = 2 |
464 selectable_score = 2 |
490 def __init__(self, *regids): |
465 def __init__(self, *regids): |
491 super(adaptable, self).__init__('adapters', *regids) |
466 super(adaptable, self).__init__('adapters', *regids) |
492 |
467 |
493 def __call__(self, cls, req, **kwargs): |
468 def __call__(self, cls, req, **kwargs): |
662 # entity selectors ############################################################# |
637 # entity selectors ############################################################# |
663 |
638 |
664 class non_final_entity(EClassSelector): |
639 class non_final_entity(EClassSelector): |
665 """Return 1 for entity of a non final entity type(s). Remember, "final" |
640 """Return 1 for entity of a non final entity type(s). Remember, "final" |
666 entity types are String, Int, etc... This is equivalent to |
641 entity types are String, Int, etc... This is equivalent to |
667 `implements('Any')` but more optimized. |
642 `is_instance('Any')` but more optimized. |
668 |
643 |
669 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
644 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
670 class lookup / score rules according to the input context. |
645 class lookup / score rules according to the input context. |
671 """ |
646 """ |
672 def score(self, cls, req, etype): |
647 def score(self, cls, req, etype): |
690 class lookup / score rules according to the input context. |
665 class lookup / score rules according to the input context. |
691 |
666 |
692 .. note:: when interface is an entity class, the score will reflect class |
667 .. note:: when interface is an entity class, the score will reflect class |
693 proximity so the most specific object will be selected. |
668 proximity so the most specific object will be selected. |
694 |
669 |
695 .. note:: with cubicweb >= 3.9, you should use adapters instead of |
670 .. note:: deprecated in cubicweb >= 3.9, use either |
696 interface, so no interface should be given to this selector. Use |
671 :class:`~cubicweb.selectors.is_instance` or |
697 :class:`adaptable` instead. |
672 :class:`~cubicweb.selectors.adaptable`. |
698 """ |
673 """ |
|
674 |
|
675 def __init__(self, *expected_ifaces, **kwargs): |
|
676 super(implements, self).__init__(**kwargs) |
|
677 self.expected_ifaces = expected_ifaces |
|
678 warn('[3.9] implements selector is deprecated, use either is_instance ' |
|
679 'or adaptable', DeprecationWarning, stacklevel=1) |
|
680 |
|
681 def __str__(self): |
|
682 return '%s(%s)' % (self.__class__.__name__, |
|
683 ','.join(str(s) for s in self.expected_ifaces)) |
699 |
684 |
700 def score_class(self, eclass, req): |
685 def score_class(self, eclass, req): |
701 return self.score_interfaces(req, eclass, eclass) |
686 return self.score_interfaces(req, eclass, eclass) |
|
687 |
|
688 def score_interfaces(self, req, cls_or_inst, cls): |
|
689 score = 0 |
|
690 etypesreg = req.vreg['etypes'] |
|
691 for iface in self.expected_ifaces: |
|
692 if isinstance(iface, basestring): |
|
693 # entity type |
|
694 try: |
|
695 iface = etypesreg.etype_class(iface) |
|
696 except KeyError: |
|
697 continue # entity type not in the schema |
|
698 score += score_interface(etypesreg, cls_or_inst, cls, iface) |
|
699 return score |
|
700 |
|
701 |
|
702 class is_instance(EClassSelector): |
|
703 """Return non-zero score for entity that is an instance of the one of given |
|
704 type(s). If multiple arguments are given, matching one of them is enough. |
|
705 |
|
706 Entity types should be given as string, the corresponding class will be |
|
707 fetched from the registry at selection time. |
|
708 |
|
709 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
|
710 class lookup / score rules according to the input context. |
|
711 |
|
712 .. note:: the score will reflect class proximity so the most specific object |
|
713 will be selected. |
|
714 """ |
|
715 |
|
716 def __init__(self, *expected_etypes, **kwargs): |
|
717 super(is_instance, self).__init__(**kwargs) |
|
718 self.expected_etypes = expected_etypes |
|
719 for etype in self.expected_etypes: |
|
720 assert isinstance(etype, basestring), etype |
|
721 |
|
722 def __str__(self): |
|
723 return '%s(%s)' % (self.__class__.__name__, |
|
724 ','.join(str(s) for s in self.expected_etypes)) |
|
725 |
|
726 def score_class(self, eclass, req): |
|
727 return self.score_etypes(req, eclass, eclass) |
|
728 |
|
729 def score_etypes(self, req, cls_or_inst, cls): |
|
730 # cache on vreg to avoid reloading issues |
|
731 try: |
|
732 cache = req.vreg.__is_instance_cache |
|
733 except AttributeError: |
|
734 cache = req.vreg.__is_instance_cache = {} |
|
735 try: |
|
736 expected_eclasses = cache[self] |
|
737 except KeyError: |
|
738 # turn list of entity types as string into a list of |
|
739 # (entity class, parent classes) |
|
740 etypesreg = req.vreg['etypes'] |
|
741 expected_eclasses = cache[self] = [] |
|
742 for etype in self.expected_etypes: |
|
743 try: |
|
744 expected_eclasses.append( |
|
745 (etypesreg.etype_class(etype), |
|
746 etypesreg.parent_classes(etype)) |
|
747 ) |
|
748 except KeyError: |
|
749 continue # entity type not in the schema |
|
750 score = 0 |
|
751 for iface, parents in expected_eclasses: |
|
752 # adjust score according to class proximity |
|
753 if iface is cls: |
|
754 score += len(parents) + 4 |
|
755 elif iface is parents[-1]: # Any |
|
756 score += 1 |
|
757 else: |
|
758 for index, basecls in enumerate(reversed(parents[:-1])): |
|
759 if iface is basecls: |
|
760 score += index + 3 |
|
761 break |
|
762 return score |
702 |
763 |
703 |
764 |
704 class score_entity(EntitySelector): |
765 class score_entity(EntitySelector): |
705 """Return score according to an arbitrary function given as argument which |
766 """Return score according to an arbitrary function given as argument which |
706 will be called with input content entity as argument. |
767 will be called with input content entity as argument. |
1200 if not param in req.form: |
1261 if not param in req.form: |
1201 return 0 |
1262 return 0 |
1202 return len(self.expected) |
1263 return len(self.expected) |
1203 |
1264 |
1204 |
1265 |
1205 class specified_etype_implements(implements): |
1266 class specified_etype_implements(is_instance): |
1206 """Return non-zero score if the entity type specified by an 'etype' key |
1267 """Return non-zero score if the entity type specified by an 'etype' key |
1207 searched in (by priority) input context kwargs and request form parameters |
1268 searched in (by priority) input context kwargs and request form parameters |
1208 match a known entity type (case insensitivly), and it's associated entity |
1269 match a known entity type (case insensitivly), and it's associated entity |
1209 class is of one of the type(s) given to the initializer or implements at |
1270 class is of one of the type(s) given to the initializer. If multiple |
1210 least one of the given interfaces. If multiple arguments are given, matching |
1271 arguments are given, matching one of them is enough. |
1211 one of them is enough. |
1272 |
1212 |
1273 .. note:: as with :class:`~cubicweb.selectors.is_instance`, entity types |
1213 Entity types should be given as string, the corresponding class will be |
1274 should be given as string and the score will reflect class |
1214 fetched from the entity types registry at selection time. |
|
1215 |
|
1216 .. note:: when interface is an entity class, the score will reflect class |
|
1217 proximity so the most specific object will be selected. |
1275 proximity so the most specific object will be selected. |
1218 |
1276 |
1219 This selector is usually used by views holding entity creation forms (since |
1277 This selector is usually used by views holding entity creation forms (since |
1220 we've no result set to work on). |
1278 we've no result set to work on). |
1221 """ |
1279 """ |