selectors.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4719 aaed3f813ef8
child 4833 41a78fb4107c
equal deleted inserted replaced
4563:c25da7573ebd 4985:02b52bf9f5f8
    41 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    41 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    42 """
    42 """
    43 __docformat__ = "restructuredtext en"
    43 __docformat__ = "restructuredtext en"
    44 
    44 
    45 import logging
    45 import logging
    46 from warnings import warn, filterwarnings
    46 from warnings import warn
    47 
    47 
       
    48 from logilab.common.deprecation import class_renamed
    48 from logilab.common.compat import all, any
    49 from logilab.common.compat import all, any
    49 from logilab.common.deprecation import deprecated
       
    50 from logilab.common.interface import implements as implements_iface
    50 from logilab.common.interface import implements as implements_iface
    51 
    51 
    52 from yams import BASE_TYPES
    52 from yams import BASE_TYPES
    53 
    53 
    54 from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
    54 from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role
    55                       role, typed_eid)
       
    56 # even if not used, let yes here so it's importable through this module
    55 # even if not used, let yes here so it's importable through this module
    57 from cubicweb.appobject import Selector, objectify_selector, yes
    56 from cubicweb.appobject import Selector, objectify_selector, yes
       
    57 from cubicweb.vregistry import class_regid
    58 from cubicweb.cwconfig import CubicWebConfiguration
    58 from cubicweb.cwconfig import CubicWebConfiguration
    59 from cubicweb.schema import split_expression
    59 from cubicweb.schema import split_expression
    60 
    60 
    61 # helpers for debugging selectors
    61 # helpers for debugging selectors
    62 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
    62 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
    73             selname = str(cls)
    73             selname = str(cls)
    74             vobj = args[0]
    74             vobj = args[0]
    75         else:
    75         else:
    76             selname = selector.__name__
    76             selname = selector.__name__
    77             vobj = cls
    77             vobj = cls
    78         oid = vobj.id
    78         oid = class_regid(vobj)
    79         ret = selector(cls, *args, **kwargs)
    79         ret = selector(cls, *args, **kwargs)
    80         if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
    80         if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
    81             #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
    81             #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
    82             print '%s -> %s for %s' % (selname, ret, vobj)
    82             print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
    83         return ret
    83         return ret
    84     traced.__name__ = selector.__name__
    84     traced.__name__ = selector.__name__
       
    85     traced.__doc__ = selector.__doc__
    85     return traced
    86     return traced
    86 
    87 
    87 class traced_selection(object):
    88 class traced_selection(object):
    88     """selector debugging helper.
    89     """selector debugging helper.
    89 
    90 
   111         global TRACED_OIDS
   112         global TRACED_OIDS
   112         TRACED_OIDS = ()
   113         TRACED_OIDS = ()
   113         return traceback is None
   114         return traceback is None
   114 
   115 
   115 
   116 
   116 def score_interface(cls_or_inst, cls, iface):
   117 def score_interface(etypesreg, cls_or_inst, cls, iface):
   117     """Return XXX if the give object (maybe an instance or class) implements
   118     """Return XXX if the give object (maybe an instance or class) implements
   118     the interface.
   119     the interface.
   119     """
   120     """
   120     if getattr(iface, '__registry__', None) == 'etypes':
   121     if getattr(iface, '__registry__', None) == 'etypes':
   121         # adjust score if the interface is an entity class
   122         # adjust score if the interface is an entity class
   122         parents = cls_or_inst.parent_classes()
   123         parents = etypesreg.parent_classes(cls_or_inst.__regid__)
   123         if iface is cls:
   124         if iface is cls:
   124             return len(parents) + 4
   125             return len(parents) + 4
   125         if iface is parents[-1]: # Any
   126         if iface is parents[-1]: # Any
   126             return 1
   127             return 1
   127         for index, basecls in enumerate(reversed(parents[:-1])):
   128         for index, basecls in enumerate(reversed(parents[:-1])):
   132         # implenting an interface takes precedence other special Any interface
   133         # implenting an interface takes precedence other special Any interface
   133         return 2
   134         return 2
   134     return 0
   135     return 0
   135 
   136 
   136 
   137 
   137 # abstract selectors ##########################################################
   138 # abstract selectors / mixin helpers ###########################################
   138 
   139 
   139 class PartialSelectorMixIn(object):
   140 class PartialSelectorMixIn(object):
   140     """convenience mix-in for selectors that will look into the containing
   141     """convenience mix-in for selectors that will look into the containing
   141     class to find missing information.
   142     class to find missing information.
   142 
   143 
   156 
   157 
   157     def __str__(self):
   158     def __str__(self):
   158         return '%s(%s)' % (self.__class__.__name__,
   159         return '%s(%s)' % (self.__class__.__name__,
   159                            ','.join(str(s) for s in self.expected_ifaces))
   160                            ','.join(str(s) for s in self.expected_ifaces))
   160 
   161 
   161     def score_interfaces(self, cls_or_inst, cls):
   162     def score_interfaces(self, req, cls_or_inst, cls):
   162         score = 0
   163         score = 0
   163         vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
   164         etypesreg = req.vreg['etypes']
   164         for iface in self.expected_ifaces:
   165         for iface in self.expected_ifaces:
   165             if isinstance(iface, basestring):
   166             if isinstance(iface, basestring):
   166                 # entity type
   167                 # entity type
   167                 try:
   168                 try:
   168                     iface = vreg['etypes'].etype_class(iface)
   169                     iface = etypesreg.etype_class(iface)
   169                 except KeyError:
   170                 except KeyError:
   170                     continue # entity type not in the schema
   171                     continue # entity type not in the schema
   171             score += score_interface(cls_or_inst, cls, iface)
   172             score += score_interface(etypesreg, cls_or_inst, cls, iface)
   172         return score
   173         return score
   173 
   174 
   174 
   175 
   175 class EClassSelector(Selector):
   176 class EClassSelector(Selector):
   176     """abstract class for selectors working on the entity classes of the result
   177     """abstract class for selectors working on *entity class(es)* specified
   177     set. Its __call__ method has the following behaviour:
   178     explicitly or found of the result set.
   178 
   179 
   179     * if row is specified, return the score returned by the score_class method
   180     Here are entity lookup / scoring rules:
   180       called with the entity class found in the specified cell
   181 
   181     * else return the sum of score returned by the score_class method for each
   182     * if `entity` is specified, return score for this entity's class
   182       entity type found in the specified column, unless:
   183 
       
   184     * elif `row` is specified, return score for the class of the entity
       
   185       found in the specified cell, using column specified by `col` or 0
       
   186 
       
   187     * else return the sum of scores for each entity class found in the column
       
   188       specified specified by the `col` argument or in column 0 if not specified,
       
   189       unless:
       
   190 
       
   191       - `once_is_enough` is False (the default) and some entity class is scored
       
   192         to 0, in which case 0 is returned
       
   193 
   183       - `once_is_enough` is True, in which case the first non-zero score is
   194       - `once_is_enough` is True, in which case the first non-zero score is
   184         returned
   195         returned
   185       - `once_is_enough` is False, in which case if score_class return 0, 0 is
   196 
   186         returned
   197       - `accept_none` is False and some cell in the column has a None value
       
   198         (this may occurs with outer join)
   187     """
   199     """
   188     def __init__(self, once_is_enough=False, accept_none=True):
   200     def __init__(self, once_is_enough=False, accept_none=True):
   189         self.once_is_enough = once_is_enough
   201         self.once_is_enough = once_is_enough
   190         self.accept_none = accept_none
   202         self.accept_none = accept_none
   191 
   203 
   192     @lltrace
   204     @lltrace
   193     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   205     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   206         if kwargs.get('entity'):
       
   207             return self.score_class(kwargs['entity'].__class__, req)
   194         if not rset:
   208         if not rset:
   195             return 0
   209             return 0
   196         score = 0
   210         score = 0
   197         if row is None:
   211         if row is None:
   198             if not self.accept_none:
   212             if not self.accept_none:
   214         return score
   228         return score
   215 
   229 
   216     def score(self, cls, req, etype):
   230     def score(self, cls, req, etype):
   217         if etype in BASE_TYPES:
   231         if etype in BASE_TYPES:
   218             return 0
   232             return 0
   219         return self.score_class(cls.vreg['etypes'].etype_class(etype), req)
   233         return self.score_class(req.vreg['etypes'].etype_class(etype), req)
   220 
   234 
   221     def score_class(self, eclass, req):
   235     def score_class(self, eclass, req):
   222         raise NotImplementedError()
   236         raise NotImplementedError()
   223 
   237 
   224 
   238 
   225 class EntitySelector(EClassSelector):
   239 class EntitySelector(EClassSelector):
   226     """abstract class for selectors working on the entity instances of the
   240     """abstract class for selectors working on *entity instance(s)* specified
   227     result set. Its __call__ method has the following behaviour:
   241     explicitly or found of the result set.
   228 
   242 
   229     * if 'entity' find in kwargs, return the score returned by the score_entity
   243     Here are entity lookup / scoring rules:
   230       method for this entity
   244 
   231     * if row is specified, return the score returned by the score_entity method
   245     * if `entity` is specified, return score for this entity
   232       called with the entity instance found in the specified cell
   246 
   233     * else return the sum of score returned by the score_entity method for each
   247     * elif `row` is specified, return score for the entity found in the
   234       entity found in the specified column, unless:
   248       specified cell, using column specified by `col` or 0
       
   249 
       
   250     * else return the sum of scores for each entity found in the column
       
   251       specified specified by the `col` argument or in column 0 if not specified,
       
   252       unless:
       
   253 
       
   254       - `once_is_enough` is False (the default) and some entity is scored
       
   255         to 0, in which case 0 is returned
       
   256 
   235       - `once_is_enough` is True, in which case the first non-zero score is
   257       - `once_is_enough` is True, in which case the first non-zero score is
   236         returned
   258         returned
   237       - `once_is_enough` is False, in which case if score_class return 0, 0 is
   259 
   238         returned
   260       - `accept_none` is False and some cell in the column has a None value
   239 
   261         (this may occurs with outer join)
   240     note: None values (resulting from some outer join in the query) are not
   262 
   241           considered.
   263     .. note::
       
   264        using EntitySelector or EClassSelector as base selector class impacts
       
   265        performance, since when no entity or row is specified the later works on
       
   266        every different *entity class* found in the result set, while the former
       
   267        works on each *entity* (eg each row of the result set), which may be much
       
   268        more costly.
   242     """
   269     """
   243 
   270 
   244     @lltrace
   271     @lltrace
   245     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   272     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   246         if not rset and not kwargs.get('entity'):
   273         if not rset and not kwargs.get('entity'):
   250             score = self.score_entity(kwargs['entity'])
   277             score = self.score_entity(kwargs['entity'])
   251         elif row is None:
   278         elif row is None:
   252             col = col or 0
   279             col = col or 0
   253             for row, rowvalue in enumerate(rset.rows):
   280             for row, rowvalue in enumerate(rset.rows):
   254                 if rowvalue[col] is None: # outer join
   281                 if rowvalue[col] is None: # outer join
       
   282                     if not self.accept_none:
       
   283                         return 0
   255                     continue
   284                     continue
   256                 escore = self.score(req, rset, row, col)
   285                 escore = self.score(req, rset, row, col)
   257                 if not escore and not self.once_is_enough:
   286                 if not escore and not self.once_is_enough:
   258                     return 0
   287                     return 0
   259                 elif self.once_is_enough:
   288                 elif self.once_is_enough:
   274 
   303 
   275     def score_entity(self, entity):
   304     def score_entity(self, entity):
   276         raise NotImplementedError()
   305         raise NotImplementedError()
   277 
   306 
   278 
   307 
   279 # very basic selectors ########################################################
   308 class ExpectedValueSelector(Selector):
       
   309     """Take a list of expected values as initializer argument, check
       
   310     _get_value method return one of these expected values.
       
   311     """
       
   312     def __init__(self, *expected):
       
   313         assert expected, self
       
   314         self.expected = frozenset(expected)
       
   315 
       
   316     def __str__(self):
       
   317         return '%s(%s)' % (self.__class__.__name__,
       
   318                            ','.join(sorted(str(s) for s in self.expected)))
       
   319 
       
   320     @lltrace
       
   321     def __call__(self, cls, req, **kwargs):
       
   322         if self._get_value(cls, req, **kwargs) in self.expected:
       
   323             return 1
       
   324         return 0
       
   325 
       
   326     def _get_value(self, cls, req, **kwargs):
       
   327         raise NotImplementedError()
       
   328 
       
   329 
       
   330 # bare selectors ##############################################################
       
   331 
       
   332 class match_kwargs(ExpectedValueSelector):
       
   333     """Return non-zero score if parameter names specified as initializer
       
   334     arguments are specified in the input context. When multiple parameters are
       
   335     specified, all of them should be specified in the input context. Return a
       
   336     score corresponding to the number of expected parameters.
       
   337     """
       
   338 
       
   339     @lltrace
       
   340     def __call__(self, cls, req, **kwargs):
       
   341         for arg in self.expected:
       
   342             if not arg in kwargs:
       
   343                 return 0
       
   344         return len(self.expected)
       
   345 
       
   346 
       
   347 class appobject_selectable(Selector):
       
   348     """return 1 if another appobject is selectable using the same input context.
       
   349 
       
   350     Initializer arguments:
       
   351     * `registry`, a registry name
       
   352     * `regid`, an object identifier in this registry
       
   353     """
       
   354     def __init__(self, registry, regid):
       
   355         self.registry = registry
       
   356         self.regid = regid
       
   357 
       
   358     def __call__(self, cls, req, **kwargs):
       
   359         try:
       
   360             req.vreg[self.registry].select(self.regid, req, **kwargs)
       
   361             return 1
       
   362         except NoSelectableObject:
       
   363             return 0
       
   364 
       
   365 
       
   366 # rset selectors ##############################################################
   280 
   367 
   281 @objectify_selector
   368 @objectify_selector
   282 @lltrace
   369 @lltrace
   283 def none_rset(cls, req, rset=None, **kwargs):
   370 def none_rset(cls, req, rset=None, **kwargs):
   284     """accept no result set (e.g. given rset is None)"""
   371     """Return 1 if the result set is None (eg usually not specified)."""
   285     if rset is None:
   372     if rset is None:
   286         return 1
   373         return 1
   287     return 0
   374     return 0
   288 
   375 
       
   376 
       
   377 # XXX == ~ none_rset
   289 @objectify_selector
   378 @objectify_selector
   290 @lltrace
   379 @lltrace
   291 def any_rset(cls, req, rset=None, **kwargs):
   380 def any_rset(cls, req, rset=None, **kwargs):
   292     """accept result set, whatever the number of result it contains"""
   381     """Return 1 for any result set, whatever the number of rows in it, even 0."""
   293     if rset is not None:
   382     if rset is not None:
   294         return 1
   383         return 1
   295     return 0
   384     return 0
       
   385 
   296 
   386 
   297 @objectify_selector
   387 @objectify_selector
   298 @lltrace
   388 @lltrace
   299 def nonempty_rset(cls, req, rset=None, **kwargs):
   389 def nonempty_rset(cls, req, rset=None, **kwargs):
   300     """accept any non empty result set"""
   390     """Return 1 for result set containing one ore more rows."""
   301     if rset is not None and rset.rowcount:
   391     if rset is not None and rset.rowcount:
   302         return 1
   392         return 1
   303     return 0
   393     return 0
   304 
   394 
       
   395 
       
   396 # XXX == ~ nonempty_rset
   305 @objectify_selector
   397 @objectify_selector
   306 @lltrace
   398 @lltrace
   307 def empty_rset(cls, req, rset=None, **kwargs):
   399 def empty_rset(cls, req, rset=None, **kwargs):
   308     """accept empty result set"""
   400     """Return 1 for result set which doesn't contain any row."""
   309     if rset is not None and rset.rowcount == 0:
   401     if rset is not None and rset.rowcount == 0:
   310         return 1
   402         return 1
   311     return 0
   403     return 0
   312 
   404 
       
   405 
       
   406 # XXX == multi_lines_rset(1)
   313 @objectify_selector
   407 @objectify_selector
   314 @lltrace
   408 @lltrace
   315 def one_line_rset(cls, req, rset=None, row=None, **kwargs):
   409 def one_line_rset(cls, req, rset=None, row=None, **kwargs):
   316     """if row is specified, accept result set with a single line of result,
   410     """Return 1 if the result set is of size 1 or if a specific row in the
   317     else accepts anyway
   411     result set is specified ('row' argument).
   318     """
   412     """
   319     if rset is not None and (row is not None or rset.rowcount == 1):
   413     if rset is not None and (row is not None or rset.rowcount == 1):
   320         return 1
   414         return 1
   321     return 0
   415     return 0
   322 
   416 
   323 @objectify_selector
   417 
   324 @lltrace
   418 class multi_lines_rset(Selector):
   325 def two_lines_rset(cls, req, rset=None, **kwargs):
   419     """If `nb`is specified, return 1 if the result set has exactly `nb` row of
   326     """accept result set with *at least* two lines of result"""
   420     result. Else (`nb` is None), return 1 if the result set contains *at least*
   327     if rset is not None and rset.rowcount > 1:
   421     two rows.
   328         return 1
   422     """
   329     return 0
   423     def __init__(self, nb=None):
   330 
   424         self.expected = nb
   331 @objectify_selector
   425 
   332 @lltrace
   426     def match_expected(self, num):
   333 def two_cols_rset(cls, req, rset=None, **kwargs):
   427         if self.expected is None:
   334     """accept result set with at least one line and two columns of result"""
   428             return num > 1
   335     if rset is not None and rset.rowcount and len(rset.rows[0]) > 1:
   429         return num == self.expected
   336         return 1
   430 
   337     return 0
   431     @lltrace
       
   432     def __call__(self, cls, req, rset=None, **kwargs):
       
   433         return rset is not None and self.match_expected(rset.rowcount)
       
   434 
       
   435 
       
   436 class multi_columns_rset(multi_lines_rset):
       
   437     """If `nb`is specified, return 1 if the result set has exactly `nb` column
       
   438     per row. Else (`nb` is None), return 1 if the result set contains *at least*
       
   439     two columns per row. Return 0 for empty result set.
       
   440     """
       
   441 
       
   442     @lltrace
       
   443     def __call__(self, cls, req, rset=None, **kwargs):
       
   444         # 'or 0' since we *must not* return None
       
   445         return rset and self.match_expected(len(rset.rows[0])) or 0
       
   446 
   338 
   447 
   339 @objectify_selector
   448 @objectify_selector
   340 @lltrace
   449 @lltrace
   341 def paginated_rset(cls, req, rset=None, **kwargs):
   450 def paginated_rset(cls, req, rset=None, **kwargs):
   342     """accept result set with more lines than the page size.
   451     """Return 1 for result set with more rows than a page size.
   343 
   452 
   344     Page size is searched in (respecting order):
   453     Page size is searched in (respecting order):
   345     * a page_size argument
   454     * a `page_size` argument
   346     * a page_size form parameters
   455     * a `page_size` form parameters
   347     * the navigation.page-size property
   456     * the :ref:`navigation.page-size` property
   348     """
   457     """
       
   458     if rset is None:
       
   459         return 0
   349     page_size = kwargs.get('page_size')
   460     page_size = kwargs.get('page_size')
   350     if page_size is None:
   461     if page_size is None:
   351         page_size = req.form.get('page_size')
   462         page_size = req.form.get('page_size')
   352         if page_size is None:
   463         if page_size is None:
   353             page_size = req.property_value('navigation.page-size')
   464             page_size = req.property_value('navigation.page-size')
   354         else:
   465         else:
   355             page_size = int(page_size)
   466             page_size = int(page_size)
   356     if rset is None or rset.rowcount <= page_size:
   467     if rset.rowcount <= page_size:
   357         return 0
   468         return 0
   358     return 1
   469     return 1
       
   470 
   359 
   471 
   360 @objectify_selector
   472 @objectify_selector
   361 @lltrace
   473 @lltrace
   362 def sorted_rset(cls, req, rset=None, **kwargs):
   474 def sorted_rset(cls, req, rset=None, **kwargs):
   363     """accept sorted result set"""
   475     """Return 1 for sorted result set (e.g. from an RQL query containing an
       
   476     :ref:ORDERBY clause.
       
   477     """
       
   478     if rset is None:
       
   479         return 0
   364     rqlst = rset.syntax_tree()
   480     rqlst = rset.syntax_tree()
   365     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
   481     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
   366         return 0
   482         return 0
   367     return 2
   483     return 2
   368 
   484 
       
   485 
       
   486 # XXX == multi_etypes_rset(1)
   369 @objectify_selector
   487 @objectify_selector
   370 @lltrace
   488 @lltrace
   371 def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
   489 def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
   372     """accept result set where entities in the specified column (or 0) are all
   490     """Return 1 if the result set contains entities which are all of the same
   373     of the same type
   491     type in the column specified by the `col` argument of the input context, or
       
   492     in column 0.
   374     """
   493     """
   375     if rset is None:
   494     if rset is None:
   376         return 0
   495         return 0
   377     if len(rset.column_types(col)) != 1:
   496     if len(rset.column_types(col)) != 1:
   378         return 0
   497         return 0
   379     return 1
   498     return 1
   380 
   499 
   381 @objectify_selector
   500 
   382 @lltrace
   501 class multi_etypes_rset(multi_lines_rset):
   383 def two_etypes_rset(cls, req, rset=None, col=0, **kwargs):
   502     """If `nb` is specified, return 1 if the result set contains `nb` different
   384     """accept result set where entities in the specified column (or 0) are not
   503     types of entities in the column specified by the `col` argument of the input
   385     of the same type
   504     context, or in column 0. If `nb` is None, return 1 if the result set contains
   386     """
   505     *at least* two different types of entities.
   387     if rset:
   506     """
   388         etypes = rset.column_types(col)
   507 
   389         if len(etypes) > 1:
   508     @lltrace
   390             return 1
   509     def __call__(self, cls, req, rset=None, col=0, **kwargs):
   391     return 0
   510         # 'or 0' since we *must not* return None
       
   511         return rset and self.match_expected(len(rset.column_types(col))) or 0
       
   512 
       
   513 
       
   514 # entity selectors #############################################################
   392 
   515 
   393 class non_final_entity(EClassSelector):
   516 class non_final_entity(EClassSelector):
   394     """accept if entity type found in the result set is non final.
   517     """Return 1 for entity of a non final entity type(s). Remember, "final"
   395 
   518     entity types are String, Int, etc... This is equivalent to
   396     See `EClassSelector` documentation for behaviour when row is not specified.
   519     `implements('Any')` but more optimized.
       
   520 
       
   521     See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
       
   522     class lookup / score rules according to the input context.
   397     """
   523     """
   398     def score(self, cls, req, etype):
   524     def score(self, cls, req, etype):
   399         if etype in BASE_TYPES:
   525         if etype in BASE_TYPES:
   400             return 0
   526             return 0
   401         return 1
   527         return 1
   402 
   528 
   403 @objectify_selector
   529     def score_class(self, eclass, req):
   404 @lltrace
   530         return 1 # necessarily true if we're there
   405 def authenticated_user(cls, req, *args, **kwargs):
   531 
   406     """accept if user is authenticated"""
   532 
   407     if req.cnx.anonymous_connection:
   533 class implements(ImplementsMixIn, EClassSelector):
   408         return 0
   534     """Return non-zero score for entity that are of the given type(s) or
   409     return 1
   535     implements at least one of the given interface(s). If multiple arguments are
   410 
   536     given, matching one of them is enough.
   411 def anonymous_user():
   537 
   412     return ~ authenticated_user()
   538     Entity types should be given as string, the corresponding class will be
   413 
   539     fetched from the entity types registry at selection time.
   414 @objectify_selector
   540 
   415 @lltrace
   541     See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
   416 def primary_view(cls, req, rset=None, row=None, col=0, view=None, **kwargs):
   542     class lookup / score rules according to the input context.
   417     """accept if view given as named argument is a primary view, or if no view
   543 
   418     is given
   544     .. note:: when interface is an entity class, the score will reflect class
   419     """
   545               proximity so the most specific object will be selected.
   420     if view is not None and not view.is_primary():
   546     """
   421         return 0
   547     def score_class(self, eclass, req):
   422     return 1
   548         return self.score_interfaces(req, eclass, eclass)
   423 
   549 
   424 @objectify_selector
   550 
   425 @lltrace
   551 class score_entity(EntitySelector):
   426 def match_context_prop(cls, req, rset=None, row=None, col=0, context=None,
   552     """Return score according to an arbitrary function given as argument which
   427                        **kwargs):
   553     will be called with input content entity as argument.
   428     """accept if:
   554 
   429     * no context given
   555     This is a very useful selector that will usually interest you since it
   430     * context (`basestring`) is matching the context property value for the
   556     allows a lot of things without having to write a specific selector.
   431       given cls
   557 
   432     """
   558     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
   433     propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
   559     lookup / score rules according to the input context.
   434     if not propval:
   560     """
   435         propval = cls.context
   561     def __init__(self, scorefunc, once_is_enough=False):
   436     if context is not None and propval and context != propval:
   562         super(score_entity, self).__init__(once_is_enough)
   437         return 0
   563         def intscore(*args, **kwargs):
   438     return 1
   564             score = scorefunc(*args, **kwargs)
   439 
   565             if not score:
   440 
       
   441 class match_search_state(Selector):
       
   442     """accept if the current request search state is in one of the expected
       
   443     states given to the initializer
       
   444 
       
   445     :param expected: either 'normal' or 'linksearch' (eg searching for an
       
   446                      object to create a relation with another)
       
   447     """
       
   448     def __init__(self, *expected):
       
   449         assert expected, self
       
   450         self.expected = frozenset(expected)
       
   451 
       
   452     def __str__(self):
       
   453         return '%s(%s)' % (self.__class__.__name__,
       
   454                            ','.join(sorted(str(s) for s in self.expected)))
       
   455 
       
   456     @lltrace
       
   457     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   458         try:
       
   459             if not req.search_state[0] in self.expected:
       
   460                 return 0
   566                 return 0
   461         except AttributeError:
   567             if isinstance(score, (int, long)):
   462             return 1 # class doesn't care about search state, accept it
   568                 return score
   463         return 1
       
   464 
       
   465 
       
   466 class match_form_params(match_search_state):
       
   467     """accept if parameters specified as initializer arguments are specified
       
   468     in request's form parameters
       
   469 
       
   470     :param *expected: parameters (eg `basestring`) which are expected to be
       
   471                       found in request's form parameters
       
   472     """
       
   473 
       
   474     @lltrace
       
   475     def __call__(self, cls, req, *args, **kwargs):
       
   476         score = 0
       
   477         for param in self.expected:
       
   478             if not param in req.form:
       
   479                 return 0
       
   480             score += 1
       
   481         return len(self.expected)
       
   482 
       
   483 
       
   484 class match_kwargs(match_search_state):
       
   485     """accept if parameters specified as initializer arguments are specified
       
   486     in named arguments given to the selector
       
   487 
       
   488     :param *expected: parameters (eg `basestring`) which are expected to be
       
   489                       found in named arguments (kwargs)
       
   490     """
       
   491 
       
   492     @lltrace
       
   493     def __call__(self, cls, req, *args, **kwargs):
       
   494         for arg in self.expected:
       
   495             if not arg in kwargs:
       
   496                 return 0
       
   497         return len(self.expected)
       
   498 
       
   499 
       
   500 class match_user_groups(match_search_state):
       
   501     """accept if logged users is in at least one of the given groups. Returned
       
   502     score is the number of groups in which the user is.
       
   503 
       
   504     If the special 'owners' group is given:
       
   505     * if row is specified check the entity at the given row/col is owned by the
       
   506       logged user
       
   507     * if row is not specified check all entities in col are owned by the logged
       
   508       user
       
   509 
       
   510     :param *required_groups: name of groups (`basestring`) in which the logged
       
   511                              user should be
       
   512     """
       
   513 
       
   514     @lltrace
       
   515     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   516         user = req.user
       
   517         if user is None:
       
   518             return int('guests' in self.expected)
       
   519         score = user.matching_groups(self.expected)
       
   520         if not score and 'owners' in self.expected and rset:
       
   521             if row is not None:
       
   522                 if not user.owns(rset[row][col]):
       
   523                     return 0
       
   524                 score = 1
       
   525             else:
       
   526                 score = all(user.owns(r[col]) for r in rset)
       
   527         return score
       
   528 
       
   529 
       
   530 class match_transition(match_search_state):
       
   531     @lltrace
       
   532     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   533         try:
       
   534             # XXX check this is a transition that apply to the object?
       
   535             if not kwargs['transition'].name in self.expected:
       
   536                 return 0
       
   537         except KeyError:
       
   538             return 0
       
   539         return 1
       
   540 
       
   541 
       
   542 class match_view(match_search_state):
       
   543     """accept if the current view is in one of the expected vid given to the
       
   544     initializer
       
   545     """
       
   546     @lltrace
       
   547     def __call__(self, cls, req, rset=None, row=None, col=0, view=None, **kwargs):
       
   548         if view is None or not view.id in self.expected:
       
   549             return 0
       
   550         return 1
       
   551 
       
   552 
       
   553 class appobject_selectable(Selector):
       
   554     """accept with another appobject is selectable using selector's input
       
   555     context.
       
   556 
       
   557     :param registry: a registry name (`basestring`)
       
   558     :param oid: an object identifier (`basestring`)
       
   559     """
       
   560     def __init__(self, registry, oid):
       
   561         self.registry = registry
       
   562         self.oid = oid
       
   563 
       
   564     def __call__(self, cls, req, **kwargs):
       
   565         try:
       
   566             cls.vreg[self.registry].select(self.oid, req, **kwargs)
       
   567             return 1
   569             return 1
   568         except NoSelectableObject:
   570         self.score_entity = intscore
   569             return 0
   571 
   570 
   572 
   571 
   573 class relation_possible(EntitySelector):
   572 # not so basic selectors ######################################################
   574     """Return 1 for entity that supports the relation, provided that the
   573 
   575     request's user may do some `action` on it (see below).
   574 class implements(ImplementsMixIn, EClassSelector):
   576 
   575     """accept if entity classes found in the result set implements at least one
   577     The relation is specified by the following initializer arguments:
   576     of the interfaces given as argument. Returned score is the number of
   578 
   577     implemented interfaces.
   579     * `rtype`, the name of the relation
   578 
   580 
   579     See `EClassSelector` documentation for behaviour when row is not specified.
   581     * `role`, the role of the entity in the relation, either 'subject' or
   580 
   582       'object', default to 'subject'
   581     :param *expected_ifaces: expected interfaces. An interface may be a class
   583 
   582                              or an entity type (e.g. `basestring`) in which case
   584     * `target_etype`, optional name of an entity type that should be supported
   583                              the associated class will be searched in the
   585       at the other end of the relation
   584                              registry (at selection time)
   586 
   585 
   587     * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete',
   586     note: when interface is an entity class, the score will reflect class
   588       default to 'read') which must be granted to the user, else a 0 score will
   587           proximity so the most specific object'll be selected
   589       be returned
   588     """
   590 
   589     def score_class(self, eclass, req):
   591     * `strict`, boolean (default to False) telling what to do when the user has
   590         return self.score_interfaces(eclass, eclass)
   592       not globally the permission for the action (eg the action is not granted
   591 
   593       to one of the user's groups)
   592 
   594 
   593 class specified_etype_implements(implements):
   595       - when strict is False, if there are some local role defined for this
   594     """accept if entity class specified using an 'etype' parameters in name
   596         action (e.g. using rql expressions), then the permission will be
   595     argument or request form implements at least one of the interfaces given as
   597         considered as granted
   596     argument. Returned score is the number of implemented interfaces.
   598 
   597 
   599       - when strict is True, then the permission will be actually checked for
   598     :param *expected_ifaces: expected interfaces. An interface may be a class
   600         each entity
   599                              or an entity type (e.g. `basestring`) in which case
   601 
   600                              the associated class will be searched in the
   602     Setting `strict` to True impacts performance for large result set since
   601                              registry (at selection time)
   603     you'll then get the :class:`~cubicweb.selectors.EntitySelector` behaviour
   602 
   604     while otherwise you get the :class:`~cubicweb.selectors.EClassSelector`'s
   603     note: when interface is an entity class, the score will reflect class
   605     one. See those classes documentation for entity lookup / score rules
   604           proximity so the most specific object'll be selected
   606     according to the input context.
   605     """
   607     """
   606 
   608 
   607     @lltrace
       
   608     def __call__(self, cls, req, *args, **kwargs):
       
   609         try:
       
   610             etype = kwargs['etype']
       
   611         except KeyError:
       
   612             try:
       
   613                 etype = req.form['etype']
       
   614             except KeyError:
       
   615                 return 0
       
   616             else:
       
   617                 # only check this is a known type if etype comes from req.form,
       
   618                 # else we want the error to propagate
       
   619                 try:
       
   620                     etype = cls.vreg.case_insensitive_etypes[etype.lower()]
       
   621                     req.form['etype'] = etype
       
   622                 except KeyError:
       
   623                     return 0
       
   624         score = self.score_class(cls.vreg['etypes'].etype_class(etype), req)
       
   625         if score:
       
   626             eschema = req.vreg.schema.eschema(etype)
       
   627             if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
       
   628                 return score
       
   629         return 0
       
   630 
       
   631 
       
   632 class entity_implements(ImplementsMixIn, EntitySelector):
       
   633     """accept if entity instances found in the result set implements at least one
       
   634     of the interfaces given as argument. Returned score is the number of
       
   635     implemented interfaces.
       
   636 
       
   637     See `EntitySelector` documentation for behaviour when row is not specified.
       
   638 
       
   639     :param *expected_ifaces: expected interfaces. An interface may be a class
       
   640                              or an entity type (e.g. `basestring`) in which case
       
   641                              the associated class will be searched in the
       
   642                              registry (at selection time)
       
   643 
       
   644     note: when interface is an entity class, the score will reflect class
       
   645           proximity so the most specific object'll be selected
       
   646     """
       
   647     def score_entity(self, entity):
       
   648         return self.score_interfaces(entity, entity.__class__)
       
   649 
       
   650 
       
   651 class relation_possible(EClassSelector):
       
   652     """accept if entity class found in the result set support the relation.
       
   653 
       
   654     See `EClassSelector` documentation for behaviour when row is not specified.
       
   655 
       
   656     :param rtype: a relation type (`basestring`)
       
   657     :param role: the role of the result set entity in the relation. 'subject' or
       
   658                  'object', default to 'subject'.
       
   659     :param target_type: if specified, check the relation's end may be of this
       
   660                         target type (`basestring`)
       
   661     :param action: a relation schema action (one of 'read', 'add', 'delete')
       
   662                    which must be granted to the logged user, else a 0 score will
       
   663                    be returned
       
   664     """
       
   665     def __init__(self, rtype, role='subject', target_etype=None,
   609     def __init__(self, rtype, role='subject', target_etype=None,
   666                  action='read', once_is_enough=False):
   610                  action='read', strict=False, **kwargs):
   667         super(relation_possible, self).__init__(once_is_enough)
   611         super(relation_possible, self).__init__(**kwargs)
   668         self.rtype = rtype
   612         self.rtype = rtype
   669         self.role = role
   613         self.role = role
   670         self.target_etype = target_etype
   614         self.target_etype = target_etype
   671         self.action = action
   615         self.action = action
   672 
   616         self.strict = strict
   673     @lltrace
   617 
   674     def __call__(self, cls, req, *args, **kwargs):
   618     # hack hack hack
   675         rschema = cls.schema.rschema(self.rtype)
   619     def __call__(self, cls, req, **kwargs):
   676         if not (rschema.has_perm(req, self.action)
   620         if self.strict:
   677                 or rschema.has_local_role(self.action)):
   621             return EntitySelector.__call__(self, cls, req, **kwargs)
   678             return 0
   622         return EClassSelector.__call__(self, cls, req, **kwargs)
   679         if self.action != 'read':
   623 
   680             if not (rschema.has_perm(req, 'read')
   624     def score(self, *args):
   681                     or rschema.has_local_role('read')):
   625         if self.strict:
   682                 return 0
   626             return EntitySelector.score(self, *args)
   683         score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
   627         return EClassSelector.score(self, *args)
   684         return score
   628 
   685 
   629     def _get_rschema(self, eclass):
   686     def score_class(self, eclass, req):
       
   687         eschema = eclass.e_schema
   630         eschema = eclass.e_schema
   688         try:
   631         try:
   689             if self.role == 'object':
   632             if self.role == 'object':
   690                 rschema = eschema.objrels[self.rtype]
   633                 return eschema.objrels[self.rtype]
   691             else:
   634             else:
   692                 rschema = eschema.subjrels[self.rtype]
   635                 return eschema.subjrels[self.rtype]
   693         except KeyError:
   636         except KeyError:
   694             return 0
   637             return None
       
   638 
       
   639     def score_class(self, eclass, req):
       
   640         rschema = self._get_rschema(eclass)
       
   641         if rschema is None:
       
   642             return 0 # relation not supported
       
   643         eschema = eclass.e_schema
   695         if self.target_etype is not None:
   644         if self.target_etype is not None:
   696             try:
   645             try:
   697                 if self.role == 'subject':
   646                 rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
   698                     return int(self.target_etype in rschema.objects(eschema))
   647                 if not rdef.may_have_permission(self.action, req):
   699                 else:
   648                     return 0
   700                     return int(self.target_etype in rschema.subjects(eschema))
       
   701             except KeyError:
   649             except KeyError:
   702                 return 0
   650                 return 0
       
   651         else:
       
   652             return rschema.may_have_permission(self.action, req, eschema, self.role)
       
   653         return 1
       
   654 
       
   655     def score_entity(self, entity):
       
   656         rschema = self._get_rschema(entity)
       
   657         if rschema is None:
       
   658             return 0 # relation not supported
       
   659         if self.target_etype is not None:
       
   660             rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
       
   661         if self.role == 'subject':
       
   662             if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid):
       
   663                 return 0
       
   664         elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid):
       
   665             return 0
   703         return 1
   666         return 1
   704 
   667 
   705 
   668 
   706 class partial_relation_possible(PartialSelectorMixIn, relation_possible):
   669 class partial_relation_possible(PartialSelectorMixIn, relation_possible):
   707     """partial version of the relation_possible selector
   670     """Same as :class:~`cubicweb.selectors.relation_possible`, but will look for
   708 
   671     attributes of the selected class to get information which is otherwise
   709     The selector will look for class attributes to find its missing
   672     expected by the initializer, except for `action` and `strict` which are kept
   710     information. The list of attributes required on the class
   673     as initializer arguments.
   711     for this selector are:
   674 
   712 
   675     This is useful to predefine selector of an abstract class designed to be
   713     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   676     customized.
   714 
   677     """
   715     - `role`: this attribute will be passed to the `cubicweb.role` function
   678     def __init__(self, action='read', **kwargs):
   716       to determine the role of class in the relation
       
   717 
       
   718     - `etype` (optional): the entity type on the other side of the relation
       
   719 
       
   720     :param action: a relation schema action (one of 'read', 'add', 'delete')
       
   721                    which must be granted to the logged user, else a 0 score will
       
   722                    be returned
       
   723     """
       
   724     def __init__(self, action='read', once_is_enough=False):
       
   725         super(partial_relation_possible, self).__init__(None, None, None,
   679         super(partial_relation_possible, self).__init__(None, None, None,
   726                                                         action, once_is_enough)
   680                                                         action, **kwargs)
   727 
   681 
   728     def complete(self, cls):
   682     def complete(self, cls):
   729         self.rtype = cls.rtype
   683         self.rtype = cls.rtype
   730         self.role = role(cls)
   684         self.role = role(cls)
   731         self.target_etype = getattr(cls, 'etype', None)
   685         self.target_etype = getattr(cls, 'etype', None)
   732 
   686         if self.target_etype is not None:
   733 
   687             warn('[3.6] please rename etype to target_etype on %s' % cls,
   734 class may_add_relation(EntitySelector):
   688                  DeprecationWarning)
   735     """accept if the relation can be added to an entity found in the result set
   689         else:
   736     by the logged user.
   690             self.target_etype = getattr(cls, 'target_etype', None)
   737 
       
   738     See `EntitySelector` documentation for behaviour when row is not specified.
       
   739 
       
   740     :param rtype: a relation type (`basestring`)
       
   741     :param role: the role of the result set entity in the relation. 'subject' or
       
   742                  'object', default to 'subject'.
       
   743     """
       
   744 
       
   745     def __init__(self, rtype, role='subject', once_is_enough=False):
       
   746         super(may_add_relation, self).__init__(once_is_enough)
       
   747         self.rtype = rtype
       
   748         self.role = role
       
   749 
       
   750     def score_entity(self, entity):
       
   751         rschema = entity.schema.rschema(self.rtype)
       
   752         if self.role == 'subject':
       
   753             if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid):
       
   754                 return 0
       
   755         elif not rschema.has_perm(entity.req, 'add', toeid=entity.eid):
       
   756             return 0
       
   757         return 1
       
   758 
       
   759 
       
   760 class partial_may_add_relation(PartialSelectorMixIn, may_add_relation):
       
   761     """partial version of the may_add_relation selector
       
   762 
       
   763     The selector will look for class attributes to find its missing
       
   764     information. The list of attributes required on the class
       
   765     for this selector are:
       
   766 
       
   767     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
       
   768 
       
   769     - `role`: this attribute will be passed to the `cubicweb.role` function
       
   770       to determine the role of class in the relation.
       
   771 
       
   772     :param action: a relation schema action (one of 'read', 'add', 'delete')
       
   773                    which must be granted to the logged user, else a 0 score will
       
   774                    be returned
       
   775     """
       
   776     def __init__(self, once_is_enough=False):
       
   777         super(partial_may_add_relation, self).__init__(None, None, once_is_enough)
       
   778 
       
   779     def complete(self, cls):
       
   780         self.rtype = cls.rtype
       
   781         self.role = role(cls)
       
   782 
   691 
   783 
   692 
   784 class has_related_entities(EntitySelector):
   693 class has_related_entities(EntitySelector):
   785     """accept if entity found in the result set has some linked entities using
   694     """Return 1 if entity support the specified relation and has some linked
   786     the specified relation (optionaly filtered according to the specified target
   695     entities by this relation , optionaly filtered according to the specified
   787     type). Checks first if the relation is possible.
   696     target type.
   788 
   697 
   789     See `EntitySelector` documentation for behaviour when row is not specified.
   698     The relation is specified by the following initializer arguments:
   790 
   699 
   791     :param rtype: a relation type (`basestring`)
   700     * `rtype`, the name of the relation
   792     :param role: the role of the result set entity in the relation. 'subject' or
   701 
   793                  'object', default to 'subject'.
   702     * `role`, the role of the entity in the relation, either 'subject' or
   794     :param target_type: if specified, check the relation's end may be of this
   703       'object', default to 'subject'.
   795                         target type (`basestring`)
   704 
   796     """
   705     * `target_etype`, optional name of an entity type that should be found
   797     def __init__(self, rtype, role='subject', target_etype=None,
   706       at the other end of the relation
   798                  once_is_enough=False):
   707 
   799         super(has_related_entities, self).__init__(once_is_enough)
   708     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
       
   709     lookup / score rules according to the input context.
       
   710     """
       
   711     def __init__(self, rtype, role='subject', target_etype=None, **kwargs):
       
   712         super(has_related_entities, self).__init__(**kwargs)
   800         self.rtype = rtype
   713         self.rtype = rtype
   801         self.role = role
   714         self.role = role
   802         self.target_etype = target_etype
   715         self.target_etype = target_etype
   803 
   716 
   804     def score_entity(self, entity):
   717     def score_entity(self, entity):
   805         relpossel = relation_possible(self.rtype, self.role, self.target_etype)
   718         relpossel = relation_possible(self.rtype, self.role, self.target_etype)
   806         if not relpossel.score_class(entity.__class__, entity.req):
   719         if not relpossel.score_class(entity.__class__, entity._cw):
   807             return 0
   720             return 0
   808         rset = entity.related(self.rtype, self.role)
   721         rset = entity.related(self.rtype, self.role)
   809         if self.target_etype:
   722         if self.target_etype:
   810             return any(r for r in rset.description if r[0] == self.target_etype)
   723             return any(r for r in rset.description if r[0] == self.target_etype)
   811         return rset and 1 or 0
   724         return rset and 1 or 0
   812 
   725 
   813 
   726 
   814 class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
   727 class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
   815     """partial version of the has_related_entities selector
   728     """Same as :class:~`cubicweb.selectors.has_related_entity`, but will look
   816 
   729     for attributes of the selected class to get information which is otherwise
   817     The selector will look for class attributes to find its missing
   730     expected by the initializer.
   818     information. The list of attributes required on the class
   731 
   819     for this selector are:
   732     This is useful to predefine selector of an abstract class designed to be
   820 
   733     customized.
   821     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   734     """
   822 
   735     def __init__(self, **kwargs):
   823     - `role`: this attribute will be passed to the `cubicweb.role` function
   736         super(partial_has_related_entities, self).__init__(None, None, None,
   824       to determine the role of class in the relation.
   737                                                            **kwargs)
   825 
   738 
   826     - `etype` (optional): the entity type on the other side of the relation
       
   827 
       
   828     :param action: a relation schema action (one of 'read', 'add', 'delete')
       
   829                    which must be granted to the logged user, else a 0 score will
       
   830                    be returned
       
   831     """
       
   832     def __init__(self, once_is_enough=False):
       
   833         super(partial_has_related_entities, self).__init__(None, None,
       
   834                                                            None, once_is_enough)
       
   835     def complete(self, cls):
   739     def complete(self, cls):
   836         self.rtype = cls.rtype
   740         self.rtype = cls.rtype
   837         self.role = role(cls)
   741         self.role = role(cls)
   838         self.target_etype = getattr(cls, 'etype', None)
   742         self.target_etype = getattr(cls, 'etype', None)
       
   743         if self.target_etype is not None:
       
   744             warn('[3.6] please rename etype to target_etype on %s' % cls,
       
   745                  DeprecationWarning)
       
   746         else:
       
   747             self.target_etype = getattr(cls, 'target_etype', None)
   839 
   748 
   840 
   749 
   841 class has_permission(EntitySelector):
   750 class has_permission(EntitySelector):
   842     """accept if user has the permission to do the requested action on a result
   751     """Return non-zero score if request's user has the permission to do the
   843     set entity.
   752     requested action on the entity. `action` is an entity schema action (eg one
   844 
   753     of 'read', 'add', 'delete', 'update').
   845     * if row is specified, return 1 if user has the permission on the entity
   754 
   846       instance found in the specified cell
   755     Here are entity lookup / scoring rules:
   847     * else return a positive score if user has the permission for every entity
   756 
   848       in the found in the specified column
   757     * if `entity` is specified, check permission is granted for this entity
   849 
   758 
   850     note: None values (resulting from some outer join in the query) are not
   759     * elif `row` is specified, check permission is granted for the entity found
   851           considered.
   760       in the specified cell
   852 
   761 
   853     :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
   762     * else check permission is granted for each entity found in the column
   854     """
   763       specified specified by the `col` argument or in column 0
   855     def __init__(self, action, once_is_enough=False):
   764     """
   856         super(has_permission, self).__init__(once_is_enough)
   765     def __init__(self, action):
   857         self.action = action
   766         self.action = action
   858 
   767 
       
   768     # don't use EntitySelector.__call__ but this optimized implementation to
       
   769     # avoid considering each entity when it's not necessary
   859     @lltrace
   770     @lltrace
   860     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   771     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   772         if kwargs.get('entity'):
       
   773             return self.score_entity(kwargs['entity'])
   861         if rset is None:
   774         if rset is None:
   862             return 0
   775             return 0
   863         user = req.user
   776         user = req.user
   864         action = self.action
   777         action = self.action
   865         if row is None:
   778         if row is None:
   866             score = 0
   779             score = 0
   867             need_local_check = []
   780             need_local_check = []
   868             geteschema = cls.schema.eschema
   781             geteschema = req.vreg.schema.eschema
   869             for etype in rset.column_types(0):
   782             for etype in rset.column_types(0):
   870                 if etype in BASE_TYPES:
   783                 if etype in BASE_TYPES:
   871                     return 0
   784                     return 0
   872                 eschema = geteschema(etype)
   785                 eschema = geteschema(etype)
   873                 if not user.matching_groups(eschema.get_groups(action)):
   786                 if not user.matching_groups(eschema.get_groups(action)):
   895             return 1
   808             return 1
   896         return 0
   809         return 0
   897 
   810 
   898 
   811 
   899 class has_add_permission(EClassSelector):
   812 class has_add_permission(EClassSelector):
   900     """accept if logged user has the add permission on entity class found in the
   813     """Return 1 if request's user has the add permission on entity type
   901     result set, and class is not a strict subobject.
   814     specified in the `etype` initializer argument, or according to entity found
   902 
   815     in the input content if not specified.
   903     See `EClassSelector` documentation for behaviour when row is not specified.
   816 
   904     """
   817     It also check that then entity type is not a strict subobject (e.g. may only
   905     def score(self, cls, req, etype):
   818     be used as a composed of another entity).
   906         eschema = cls.schema.eschema(etype)
   819 
   907         if not (eschema.final or eschema.is_subobject(strict=True)) \
   820     See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
   908                and eschema.has_perm(req, 'add'):
   821     class lookup / score rules according to the input context when `etype` is
   909             return 1
   822     not specified.
   910         return 0
   823     """
       
   824     def __init__(self, etype=None, **kwargs):
       
   825         super(has_add_permission, self).__init__(**kwargs)
       
   826         self.etype = etype
       
   827 
       
   828     @lltrace
       
   829     def __call__(self, cls, req, **kwargs):
       
   830         if self.etype is None:
       
   831             return super(has_add_permission, self).__call__(cls, req, **kwargs)
       
   832         return self.score(cls, req, self.etype)
       
   833 
       
   834     def score_class(self, eclass, req):
       
   835         eschema = eclass.e_schema
       
   836         if eschema.final or eschema.is_subobject(strict=True) \
       
   837                or not eschema.has_perm(req, 'add'):
       
   838             return 0
       
   839         return 1
   911 
   840 
   912 
   841 
   913 class rql_condition(EntitySelector):
   842 class rql_condition(EntitySelector):
   914     """accept if an arbitrary rql return some results for an eid found in the
   843     """Return non-zero score if arbitrary rql specified in `expression`
   915     result set. Returned score is the number of items returned by the rql
   844     initializer argument return some results for entity found in the input
       
   845     context. Returned score is the number of items returned by the rql
   916     condition.
   846     condition.
   917 
   847 
   918     See `EntitySelector` documentation for behaviour when row is not specified.
   848     `expression` is expected to be a string containing an rql expression, which
   919 
   849     must use 'X' variable to represent the context entity and may use 'U' to
   920     :param expression: basestring containing an rql expression, which should use
   850     represent the request's user.
   921                        X variable to represent the context entity and may use U
   851 
   922                        to represent the logged user
   852     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
   923 
   853     lookup / score rules according to the input context.
   924     return the sum of the number of items returned by the rql condition as score
       
   925     or 0 at the first entity scoring to zero.
       
   926     """
   854     """
   927     def __init__(self, expression, once_is_enough=False):
   855     def __init__(self, expression, once_is_enough=False):
   928         super(rql_condition, self).__init__(once_is_enough)
   856         super(rql_condition, self).__init__(once_is_enough)
   929         if 'U' in frozenset(split_expression(expression)):
   857         if 'U' in frozenset(split_expression(expression)):
   930             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
   858             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
   931         else:
   859         else:
   932             rql = 'Any X WHERE X eid %%(x)s, %s' % expression
   860             rql = 'Any X WHERE X eid %%(x)s, %s' % expression
   933         self.rql = rql
   861         self.rql = rql
   934 
   862 
       
   863     def __repr__(self):
       
   864         return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
       
   865 
   935     def score(self, req, rset, row, col):
   866     def score(self, req, rset, row, col):
   936         try:
   867         try:
   937             return len(req.execute(self.rql, {'x': rset[row][col],
   868             return len(req.execute(self.rql, {'x': rset[row][col],
   938                                               'u': req.user.eid}, 'x'))
   869                                               'u': req.user.eid}, 'x'))
   939         except Unauthorized:
   870         except Unauthorized:
   940             return 0
   871             return 0
   941 
   872 
   942     def __repr__(self):
   873 # logged user selectors ########################################################
   943         return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
   874 
   944 
   875 @objectify_selector
   945 
   876 @lltrace
   946 class but_etype(EntitySelector):
   877 def authenticated_user(cls, req, **kwargs):
       
   878     """Return 1 if the user is authenticated (e.g. not the anonymous user).
       
   879 
       
   880     May only be used on the web side, not on the data repository side.
       
   881     """
       
   882     if req.cnx.anonymous_connection:
       
   883         return 0
       
   884     return 1
       
   885 
       
   886 
       
   887 # XXX == ~ authenticated_user()
       
   888 def anonymous_user():
       
   889     """Return 1 if the user is not authenticated (e.g. is the anonymous user).
       
   890 
       
   891     May only be used on the web side, not on the data repository side.
       
   892     """
       
   893     return ~ authenticated_user()
       
   894 
       
   895 
       
   896 class match_user_groups(ExpectedValueSelector):
       
   897     """Return a non-zero score if request's user is in at least one of the
       
   898     groups given as initializer argument. Returned score is the number of groups
       
   899     in which the user is.
       
   900 
       
   901     If the special 'owners' group is given and `rset` is specified in the input
       
   902     context:
       
   903 
       
   904     * if `row` is specified check the entity at the given `row`/`col` (default
       
   905       to 0) is owned by the user
       
   906 
       
   907     * else check all entities in `col` (default to 0) are owned by the user
       
   908     """
       
   909 
       
   910     @lltrace
       
   911     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   912         user = req.user
       
   913         if user is None:
       
   914             return int('guests' in self.expected)
       
   915         score = user.matching_groups(self.expected)
       
   916         if not score and 'owners' in self.expected and rset:
       
   917             if row is not None:
       
   918                 if not user.owns(rset[row][col]):
       
   919                     return 0
       
   920                 score = 1
       
   921             else:
       
   922                 score = all(user.owns(r[col]) for r in rset)
       
   923         return score
       
   924 
       
   925 
       
   926 # Web request selectors ########################################################
       
   927 
       
   928 @objectify_selector
       
   929 @lltrace
       
   930 def primary_view(cls, req, view=None, **kwargs):
       
   931     """Return 1 if:
       
   932 
       
   933     * *no view is specified* in the input context
       
   934 
       
   935     * a view is specified and its `.is_primary()` method return True
       
   936 
       
   937     This selector is usually used by contextual components that only want to
       
   938     appears for the primary view of an entity.
       
   939     """
       
   940     if view is not None and not view.is_primary():
       
   941         return 0
       
   942     return 1
       
   943 
       
   944 
       
   945 class match_view(ExpectedValueSelector):
       
   946     """Return 1 if a view is specified an as its registry id is in one of the
       
   947     expected view id given to the initializer.
       
   948     """
       
   949     @lltrace
       
   950     def __call__(self, cls, req, view=None, **kwargs):
       
   951         if view is None or not view.__regid__ in self.expected:
       
   952             return 0
       
   953         return 1
       
   954 
       
   955 
       
   956 @objectify_selector
       
   957 @lltrace
       
   958 def match_context_prop(cls, req, context=None, **kwargs):
       
   959     """Return 1 if:
       
   960 
       
   961     * no `context` is specified in input context (take care to confusion, here
       
   962       `context` refers to a string given as an argument to the input context...)
       
   963 
       
   964     * specified `context` is matching the context property value for the
       
   965       appobject using this selector
       
   966 
       
   967     * the appobject's context property value is None
       
   968 
       
   969     This selector is usually used by contextual components that want to appears
       
   970     in a configurable place.
       
   971     """
       
   972     if context is None:
       
   973         return 1
       
   974     propval = req.property_value('%s.%s.context' % (cls.__registry__,
       
   975                                                     cls.__regid__))
       
   976     if not propval:
       
   977         propval = cls.context
       
   978     if propval and context != propval:
       
   979         return 0
       
   980     return 1
       
   981 
       
   982 
       
   983 class match_search_state(ExpectedValueSelector):
       
   984     """Return 1 if the current request search state is in one of the expected
       
   985     states given to the initializer.
       
   986 
       
   987     Known search states are either 'normal' or 'linksearch' (eg searching for an
       
   988     object to create a relation with another).
       
   989 
       
   990     This selector is usually used by action that want to appears or not according
       
   991     to the ui search state.
       
   992     """
       
   993 
       
   994     @lltrace
       
   995     def __call__(self, cls, req, **kwargs):
       
   996         try:
       
   997             if not req.search_state[0] in self.expected:
       
   998                 return 0
       
   999         except AttributeError:
       
  1000             return 1 # class doesn't care about search state, accept it
       
  1001         return 1
       
  1002 
       
  1003 
       
  1004 class match_form_params(ExpectedValueSelector):
       
  1005     """Return non-zero score if parameter names specified as initializer
       
  1006     arguments are specified in request's form parameters. When multiple
       
  1007     parameters are specified, all of them should be found in req.form. Return a
       
  1008     score corresponding to the number of expected parameters.
       
  1009     """
       
  1010 
       
  1011     @lltrace
       
  1012     def __call__(self, cls, req, **kwargs):
       
  1013         for param in self.expected:
       
  1014             if not param in req.form:
       
  1015                 return 0
       
  1016         return len(self.expected)
       
  1017 
       
  1018 
       
  1019 class specified_etype_implements(implements):
       
  1020     """Return non-zero score if the entity type specified by an 'etype' key
       
  1021     searched in (by priority) input context kwargs and request form parameters
       
  1022     match a known entity type (case insensitivly), and it's associated entity
       
  1023     class is of one of the type(s) given to the initializer or implements at
       
  1024     least one of the given interfaces. If multiple arguments are given, matching
       
  1025     one of them is enough.
       
  1026 
       
  1027     Entity types should be given as string, the corresponding class will be
       
  1028     fetched from the entity types registry at selection time.
       
  1029 
       
  1030     .. note:: when interface is an entity class, the score will reflect class
       
  1031               proximity so the most specific object will be selected.
       
  1032 
       
  1033     This selector is usually used by views holding entity creation forms (since
       
  1034     we've no result set to work on).
       
  1035     """
       
  1036 
       
  1037     @lltrace
       
  1038     def __call__(self, cls, req, **kwargs):
       
  1039         try:
       
  1040             etype = kwargs['etype']
       
  1041         except KeyError:
       
  1042             try:
       
  1043                 etype = req.form['etype']
       
  1044             except KeyError:
       
  1045                 return 0
       
  1046             else:
       
  1047                 # only check this is a known type if etype comes from req.form,
       
  1048                 # else we want the error to propagate
       
  1049                 try:
       
  1050                     etype = req.vreg.case_insensitive_etypes[etype.lower()]
       
  1051                     req.form['etype'] = etype
       
  1052                 except KeyError:
       
  1053                     return 0
       
  1054         score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
       
  1055         if score:
       
  1056             eschema = req.vreg.schema.eschema(etype)
       
  1057             if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
       
  1058                 return score
       
  1059         return 0
       
  1060 
       
  1061 
       
  1062 # Other selectors ##############################################################
       
  1063 
       
  1064 
       
  1065 class match_transition(ExpectedValueSelector):
       
  1066     """Return 1 if:
       
  1067 
       
  1068     * a `transition` argument is found in the input context which
       
  1069       has a `.name` attribute matching one of the expected names given to the
       
  1070       initializer
       
  1071 
       
  1072     * no transition specified.
       
  1073     """
       
  1074     @lltrace
       
  1075     def __call__(self, cls, req, transition=None, **kwargs):
       
  1076         # XXX check this is a transition that apply to the object?
       
  1077         if transition is None:
       
  1078             return 1
       
  1079         if transition is not None and getattr(transition, 'name', None) in self.expected:
       
  1080             return 1
       
  1081         return 0
       
  1082 
       
  1083 
       
  1084 ## deprecated stuff ############################################################
       
  1085 
       
  1086 entity_implements = class_renamed('entity_implements', implements)
       
  1087 
       
  1088 class _but_etype(EntitySelector):
   947     """accept if the given entity types are not found in the result set.
  1089     """accept if the given entity types are not found in the result set.
   948 
  1090 
   949     See `EntitySelector` documentation for behaviour when row is not specified.
  1091     See `EntitySelector` documentation for behaviour when row is not specified.
   950 
  1092 
   951     :param *etypes: entity types (`basestring`) which should be refused
  1093     :param *etypes: entity types (`basestring`) which should be refused
   952     """
  1094     """
   953     def __init__(self, *etypes):
  1095     def __init__(self, *etypes):
   954         super(but_etype, self).__init__()
  1096         super(_but_etype, self).__init__()
   955         self.but_etypes = etypes
  1097         self.but_etypes = etypes
   956 
  1098 
   957     def score(self, req, rset, row, col):
  1099     def score(self, req, rset, row, col):
   958         if rset.description[row][col] in self.but_etypes:
  1100         if rset.description[row][col] in self.but_etypes:
   959             return 0
  1101             return 0
   960         return 1
  1102         return 1
   961 
  1103 
   962 
  1104 but_etype = class_renamed('but_etype', _but_etype, 'use ~implements(*etypes) instead')
   963 class score_entity(EntitySelector):
  1105 
   964     """accept if some arbitrary function return a positive score for an entity
  1106 
   965     found in the result set.
  1107 # XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)?
   966 
  1108 #     take care at the implementation though (looking for the 'row' argument's
   967     See `EntitySelector` documentation for behaviour when row is not specified.
  1109 #     value)
   968 
  1110 two_lines_rset = class_renamed('two_lines_rset', multi_lines_rset)
   969     :param scorefunc: callable expected to take an entity as argument and to
  1111 two_cols_rset = class_renamed('two_cols_rset', multi_columns_rset)
   970                       return a score >= 0
  1112 two_etypes_rset = class_renamed('two_etypes_rset', multi_etypes_rset)
   971     """
       
   972     def __init__(self, scorefunc, once_is_enough=False):
       
   973         super(score_entity, self).__init__(once_is_enough)
       
   974         def intscore(*args, **kwargs):
       
   975             score = scorefunc(*args, **kwargs)
       
   976             if not score:
       
   977                 return 0
       
   978             if isinstance(score, (int, long)):
       
   979                 return score
       
   980             return 1
       
   981         self.score_entity = intscore
       
   982 
       
   983 
       
   984 # XXX DEPRECATED ##############################################################
       
   985 # XXX remove when deprecated functions are removed
       
   986 filterwarnings('ignore',
       
   987                category=DeprecationWarning,
       
   988                module='cubicweb.selectors')
       
   989 from cubicweb.vregistry import chainall
       
   990 
       
   991 yes_selector = deprecated()(yes)
       
   992 norset_selector = deprecated()(none_rset)
       
   993 rset_selector = deprecated()(any_rset)
       
   994 anyrset_selector = deprecated()(nonempty_rset)
       
   995 emptyrset_selector = deprecated()(empty_rset)
       
   996 onelinerset_selector = deprecated()(one_line_rset)
       
   997 twolinerset_selector = deprecated()(two_lines_rset)
       
   998 twocolrset_selector = deprecated()(two_cols_rset)
       
   999 largerset_selector = deprecated()(paginated_rset)
       
  1000 sortedrset_selector = deprecated()(sorted_rset)
       
  1001 oneetyperset_selector = deprecated()(one_etype_rset)
       
  1002 multitype_selector = deprecated()(two_etypes_rset)
       
  1003 anonymous_selector = deprecated()(anonymous_user)
       
  1004 not_anonymous_selector = deprecated()(authenticated_user)
       
  1005 primaryview_selector = deprecated()(primary_view)
       
  1006 contextprop_selector = deprecated()(match_context_prop)
       
  1007 
       
  1008 @deprecated('use non_final_entity instead of %s')
       
  1009 def nfentity_selector(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1010     return non_final_entity()(cls, req, rset, row, col)
       
  1011 
       
  1012 @deprecated('use implements instead of %s')
       
  1013 def implement_interface(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1014     return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
       
  1015 _interface_selector = deprecated()(implement_interface)
       
  1016 interface_selector = deprecated()(implement_interface)
       
  1017 
       
  1018 @deprecated('use specified_etype_implements instead of %s')
       
  1019 def accept_etype(cls, req, *args, **kwargs):
       
  1020     """check etype presence in request form *and* accepts conformance"""
       
  1021     return specified_etype_implements(*cls.accepts)(cls, req, *args)
       
  1022 etype_form_selector = accept_etype
       
  1023 
       
  1024 @deprecated('use match_search_state instead of %s')
       
  1025 def searchstate_selector(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1026     return match_search_state(cls.search_states)(cls, req, rset, row, col)
       
  1027 
       
  1028 @deprecated('use match_user_groups instead of %s')
       
  1029 def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1030     return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs)
       
  1031 in_group_selector = match_user_group
       
  1032 
       
  1033 @deprecated('use relation_possible instead of %s')
       
  1034 def has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1035     return relation_possible(cls.rtype, role(cls), cls.etype,
       
  1036                              getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
       
  1037 
       
  1038 @deprecated('use relation_possible instead of %s')
       
  1039 def one_has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1040     return relation_possible(cls.rtype, role(cls), cls.etype,
       
  1041                              getattr(cls, 'require_permission', 'read',
       
  1042                                      once_is_enough=True))(cls, req, rset, row, col, **kwargs)
       
  1043 
       
  1044 @deprecated('use implements instead of %s')
       
  1045 def accept_rset(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1046     """simply delegate to cls.accept_rset method"""
       
  1047     return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
       
  1048 accept_rset_selector = accept_rset
       
  1049 
       
  1050 accept = chainall(non_final_entity(), accept_rset, name='accept')
       
  1051 accept = deprecated('use implements selector')(accept)
       
  1052 accept_selector = deprecated()(accept)
       
  1053 
       
  1054 accept_one = deprecated()(chainall(one_line_rset, accept,
       
  1055                                    name='accept_one'))
       
  1056 accept_one_selector = deprecated()(accept_one)
       
  1057 
       
  1058 
       
  1059 def _rql_condition(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1060     if cls.condition:
       
  1061         return rql_condition(cls.condition)(cls, req, rset, row, col)
       
  1062     return 1
       
  1063 _rqlcondition_selector = deprecated()(_rql_condition)
       
  1064 
       
  1065 rqlcondition_selector = deprecated()(chainall(non_final_entity(), one_line_rset, _rql_condition,
       
  1066                          name='rql_condition'))
       
  1067 
       
  1068 @deprecated('use but_etype instead of %s')
       
  1069 def but_etype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1070     return but_etype(cls.etype)(cls, req, rset, row, col)
       
  1071 
       
  1072 @lltrace
       
  1073 def etype_rtype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
       
  1074     schema = cls.schema
       
  1075     perm = getattr(cls, 'require_permission', 'read')
       
  1076     if hasattr(cls, 'etype'):
       
  1077         eschema = schema.eschema(cls.etype)
       
  1078         if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
       
  1079             return 0
       
  1080     if hasattr(cls, 'rtype'):
       
  1081         rschema = schema.rschema(cls.rtype)
       
  1082         if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
       
  1083             return 0
       
  1084     return 1
       
  1085 etype_rtype_selector = deprecated()(etype_rtype_selector)
       
  1086 
       
  1087 #req_form_params_selector = deprecated()(match_form_params) # form_params
       
  1088 #kwargs_selector = deprecated()(match_kwargs) # expected_kwargs
       
  1089 
       
  1090 # compound selectors ##########################################################
       
  1091 
       
  1092 searchstate_accept = chainall(nonempty_rset(), accept,
       
  1093                               name='searchstate_accept')
       
  1094 searchstate_accept_selector = deprecated()(searchstate_accept)
       
  1095 
       
  1096 searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
       
  1097                                   name='searchstate_accept_one')
       
  1098 searchstate_accept_one_selector = deprecated()(searchstate_accept_one)
       
  1099 
       
  1100 searchstate_accept = deprecated()(searchstate_accept)
       
  1101 searchstate_accept_one = deprecated()(searchstate_accept_one)
       
  1102 
       
  1103 # end of deprecation section ##################################################
       
  1104 
       
  1105 def unbind_method(selector):
       
  1106     def new_selector(registered):
       
  1107         # get the unbound method
       
  1108         if hasattr(registered, 'im_func'):
       
  1109             registered = registered.im_func
       
  1110         # don't rebind since it will be done automatically during
       
  1111         # the assignment, inside the destination class body
       
  1112         return selector(registered)
       
  1113     new_selector.__name__ = selector.__name__
       
  1114     return new_selector
       
  1115 
       
  1116 
       
  1117 def deprecate(registered, msg):
       
  1118     # get the unbound method
       
  1119     if hasattr(registered, 'im_func'):
       
  1120         registered = registered.im_func
       
  1121     def _deprecate(cls, vreg):
       
  1122         warn(msg, DeprecationWarning)
       
  1123         return registered(cls, vreg)
       
  1124     return _deprecate
       
  1125 
       
  1126 @unbind_method
       
  1127 def require_group_compat(registered):
       
  1128     def plug_selector(cls, vreg):
       
  1129         cls = registered(cls, vreg)
       
  1130         if getattr(cls, 'require_groups', None):
       
  1131             warn('use "match_user_groups(group1, group2)" instead of using require_groups',
       
  1132                  DeprecationWarning)
       
  1133             cls.__select__ &= match_user_groups(cls.require_groups)
       
  1134         return cls
       
  1135     return plug_selector
       
  1136 
       
  1137 @unbind_method
       
  1138 def accepts_compat(registered):
       
  1139     def plug_selector(cls, vreg):
       
  1140         cls = registered(cls, vreg)
       
  1141         if getattr(cls, 'accepts', None):
       
  1142             warn('use "implements("EntityType", IFace)" instead of using accepts on %s'
       
  1143                  % cls,
       
  1144                  DeprecationWarning)
       
  1145             cls.__select__ &= implements(*cls.accepts)
       
  1146         return cls
       
  1147     return plug_selector
       
  1148 
       
  1149 @unbind_method
       
  1150 def accepts_etype_compat(registered):
       
  1151     def plug_selector(cls, vreg):
       
  1152         cls = registered(cls, vreg)
       
  1153         if getattr(cls, 'accepts', None):
       
  1154             warn('use "specified_etype_implements("EntityType", IFace)" instead of using accepts',
       
  1155                  DeprecationWarning)
       
  1156             cls.__select__ &= specified_etype_implements(*cls.accepts)
       
  1157         return cls
       
  1158     return plug_selector
       
  1159 
       
  1160 @unbind_method
       
  1161 def condition_compat(registered):
       
  1162     def plug_selector(cls, vreg):
       
  1163         cls = registered(cls, vreg)
       
  1164         if getattr(cls, 'condition', None):
       
  1165             warn('use "use rql_condition(expression)" instead of using condition',
       
  1166                  DeprecationWarning)
       
  1167             cls.__select__ &= rql_condition(cls.condition)
       
  1168         return cls
       
  1169     return plug_selector
       
  1170 
       
  1171 @unbind_method
       
  1172 def has_relation_compat(registered):
       
  1173     def plug_selector(cls, vreg):
       
  1174         cls = registered(cls, vreg)
       
  1175         if getattr(cls, 'etype', None):
       
  1176             warn('use relation_possible selector instead of using etype_rtype',
       
  1177                  DeprecationWarning)
       
  1178             cls.__select__ &= relation_possible(cls.rtype, role(cls),
       
  1179                                                 getattr(cls, 'etype', None),
       
  1180                                                 action=getattr(cls, 'require_permission', 'read'))
       
  1181         return cls
       
  1182     return plug_selector
       
  1183