selectors.py
changeset 5877 0c7b7b76a84f
parent 5858 384d34e76d6d
child 5881 57387070f612
equal deleted inserted replaced
5876:e77aa963fb19 5877:0c7b7b76a84f
   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):
   676 
   651 
   677     def score_class(self, eclass, req):
   652     def score_class(self, eclass, req):
   678         return 1 # necessarily true if we're there
   653         return 1 # necessarily true if we're there
   679 
   654 
   680 
   655 
   681 class implements(ImplementsMixIn, EClassSelector):
   656 class implements(EClassSelector):
   682     """Return non-zero score for entity that are of the given type(s) or
   657     """Return non-zero score for entity that are of the given type(s) or
   683     implements at least one of the given interface(s). If multiple arguments are
   658     implements at least one of the given interface(s). If multiple arguments are
   684     given, matching one of them is enough.
   659     given, matching one of them is enough.
   685 
   660 
   686     Entity types should be given as string, the corresponding class will be
   661     Entity types should be given as string, the corresponding class will be
   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     """
  1290     """Return 1 if running in debug mode"""
  1348     """Return 1 if running in debug mode"""
  1291     return req.vreg.config.debugmode and 1 or 0
  1349     return req.vreg.config.debugmode and 1 or 0
  1292 
  1350 
  1293 ## deprecated stuff ############################################################
  1351 ## deprecated stuff ############################################################
  1294 
  1352 
  1295 entity_implements = class_renamed('entity_implements', implements)
  1353 entity_implements = class_renamed('entity_implements', is_instance)
  1296 
  1354 
  1297 class _but_etype(EntitySelector):
  1355 class _but_etype(EntitySelector):
  1298     """accept if the given entity types are not found in the result set.
  1356     """accept if the given entity types are not found in the result set.
  1299 
  1357 
  1300     See `EntitySelector` documentation for behaviour when row is not specified.
  1358     See `EntitySelector` documentation for behaviour when row is not specified.
  1308     def score(self, req, rset, row, col):
  1366     def score(self, req, rset, row, col):
  1309         if rset.description[row][col] in self.but_etypes:
  1367         if rset.description[row][col] in self.but_etypes:
  1310             return 0
  1368             return 0
  1311         return 1
  1369         return 1
  1312 
  1370 
  1313 but_etype = class_renamed('but_etype', _but_etype, 'use ~implements(*etypes) instead')
  1371 but_etype = class_renamed('but_etype', _but_etype, 'use ~is_instance(*etypes) instead')
  1314 
  1372 
  1315 
  1373 
  1316 # XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)?
  1374 # XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)?
  1317 #     take care at the implementation though (looking for the 'row' argument's
  1375 #     take care at the implementation though (looking for the 'row' argument's
  1318 #     value)
  1376 #     value)