selectors.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4719 aaed3f813ef8
child 4833 41a78fb4107c
--- a/selectors.py	Fri Feb 12 15:18:00 2010 +0100
+++ b/selectors.py	Wed Mar 24 10:23:31 2010 +0100
@@ -43,18 +43,18 @@
 __docformat__ = "restructuredtext en"
 
 import logging
-from warnings import warn, filterwarnings
+from warnings import warn
 
+from logilab.common.deprecation import class_renamed
 from logilab.common.compat import all, any
-from logilab.common.deprecation import deprecated
 from logilab.common.interface import implements as implements_iface
 
 from yams import BASE_TYPES
 
-from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
-                      role, typed_eid)
+from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role
 # even if not used, let yes here so it's importable through this module
 from cubicweb.appobject import Selector, objectify_selector, yes
+from cubicweb.vregistry import class_regid
 from cubicweb.cwconfig import CubicWebConfiguration
 from cubicweb.schema import split_expression
 
@@ -75,13 +75,14 @@
         else:
             selname = selector.__name__
             vobj = cls
-        oid = vobj.id
+        oid = class_regid(vobj)
         ret = selector(cls, *args, **kwargs)
         if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
             #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
-            print '%s -> %s for %s' % (selname, ret, vobj)
+            print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
         return ret
     traced.__name__ = selector.__name__
+    traced.__doc__ = selector.__doc__
     return traced
 
 class traced_selection(object):
@@ -113,13 +114,13 @@
         return traceback is None
 
 
-def score_interface(cls_or_inst, cls, iface):
+def score_interface(etypesreg, cls_or_inst, cls, iface):
     """Return XXX if the give object (maybe an instance or class) implements
     the interface.
     """
     if getattr(iface, '__registry__', None) == 'etypes':
         # adjust score if the interface is an entity class
-        parents = cls_or_inst.parent_classes()
+        parents = etypesreg.parent_classes(cls_or_inst.__regid__)
         if iface is cls:
             return len(parents) + 4
         if iface is parents[-1]: # Any
@@ -134,7 +135,7 @@
     return 0
 
 
-# abstract selectors ##########################################################
+# abstract selectors / mixin helpers ###########################################
 
 class PartialSelectorMixIn(object):
     """convenience mix-in for selectors that will look into the containing
@@ -158,32 +159,43 @@
         return '%s(%s)' % (self.__class__.__name__,
                            ','.join(str(s) for s in self.expected_ifaces))
 
-    def score_interfaces(self, cls_or_inst, cls):
+    def score_interfaces(self, req, cls_or_inst, cls):
         score = 0
-        vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
+        etypesreg = req.vreg['etypes']
         for iface in self.expected_ifaces:
             if isinstance(iface, basestring):
                 # entity type
                 try:
-                    iface = vreg['etypes'].etype_class(iface)
+                    iface = etypesreg.etype_class(iface)
                 except KeyError:
                     continue # entity type not in the schema
-            score += score_interface(cls_or_inst, cls, iface)
+            score += score_interface(etypesreg, cls_or_inst, cls, iface)
         return score
 
 
 class EClassSelector(Selector):
-    """abstract class for selectors working on the entity classes of the result
-    set. Its __call__ method has the following behaviour:
+    """abstract class for selectors working on *entity class(es)* specified
+    explicitly or found of the result set.
+
+    Here are entity lookup / scoring rules:
+
+    * if `entity` is specified, return score for this entity's class
 
-    * if row is specified, return the score returned by the score_class method
-      called with the entity class found in the specified cell
-    * else return the sum of score returned by the score_class method for each
-      entity type found in the specified column, unless:
+    * elif `row` is specified, return score for the class of the entity
+      found in the specified cell, using column specified by `col` or 0
+
+    * else return the sum of scores for each entity class found in the column
+      specified specified by the `col` argument or in column 0 if not specified,
+      unless:
+
+      - `once_is_enough` is False (the default) and some entity class is scored
+        to 0, in which case 0 is returned
+
       - `once_is_enough` is True, in which case the first non-zero score is
         returned
-      - `once_is_enough` is False, in which case if score_class return 0, 0 is
-        returned
+
+      - `accept_none` is False and some cell in the column has a None value
+        (this may occurs with outer join)
     """
     def __init__(self, once_is_enough=False, accept_none=True):
         self.once_is_enough = once_is_enough
@@ -191,6 +203,8 @@
 
     @lltrace
     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+        if kwargs.get('entity'):
+            return self.score_class(kwargs['entity'].__class__, req)
         if not rset:
             return 0
         score = 0
@@ -216,29 +230,42 @@
     def score(self, cls, req, etype):
         if etype in BASE_TYPES:
             return 0
-        return self.score_class(cls.vreg['etypes'].etype_class(etype), req)
+        return self.score_class(req.vreg['etypes'].etype_class(etype), req)
 
     def score_class(self, eclass, req):
         raise NotImplementedError()
 
 
 class EntitySelector(EClassSelector):
-    """abstract class for selectors working on the entity instances of the
-    result set. Its __call__ method has the following behaviour:
+    """abstract class for selectors working on *entity instance(s)* specified
+    explicitly or found of the result set.
+
+    Here are entity lookup / scoring rules:
+
+    * if `entity` is specified, return score for this entity
 
-    * if 'entity' find in kwargs, return the score returned by the score_entity
-      method for this entity
-    * if row is specified, return the score returned by the score_entity method
-      called with the entity instance found in the specified cell
-    * else return the sum of score returned by the score_entity method for each
-      entity found in the specified column, unless:
+    * elif `row` is specified, return score for the entity found in the
+      specified cell, using column specified by `col` or 0
+
+    * else return the sum of scores for each entity found in the column
+      specified specified by the `col` argument or in column 0 if not specified,
+      unless:
+
+      - `once_is_enough` is False (the default) and some entity is scored
+        to 0, in which case 0 is returned
+
       - `once_is_enough` is True, in which case the first non-zero score is
         returned
-      - `once_is_enough` is False, in which case if score_class return 0, 0 is
-        returned
+
+      - `accept_none` is False and some cell in the column has a None value
+        (this may occurs with outer join)
 
-    note: None values (resulting from some outer join in the query) are not
-          considered.
+    .. note::
+       using EntitySelector or EClassSelector as base selector class impacts
+       performance, since when no entity or row is specified the later works on
+       every different *entity class* found in the result set, while the former
+       works on each *entity* (eg each row of the result set), which may be much
+       more costly.
     """
 
     @lltrace
@@ -252,6 +279,8 @@
             col = col or 0
             for row, rowvalue in enumerate(rset.rows):
                 if rowvalue[col] is None: # outer join
+                    if not self.accept_none:
+                        return 0
                     continue
                 escore = self.score(req, rset, row, col)
                 if not escore and not self.once_is_enough:
@@ -276,174 +305,9 @@
         raise NotImplementedError()
 
 
-# very basic selectors ########################################################
-
-@objectify_selector
-@lltrace
-def none_rset(cls, req, rset=None, **kwargs):
-    """accept no result set (e.g. given rset is None)"""
-    if rset is None:
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
-def any_rset(cls, req, rset=None, **kwargs):
-    """accept result set, whatever the number of result it contains"""
-    if rset is not None:
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
-def nonempty_rset(cls, req, rset=None, **kwargs):
-    """accept any non empty result set"""
-    if rset is not None and rset.rowcount:
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
-def empty_rset(cls, req, rset=None, **kwargs):
-    """accept empty result set"""
-    if rset is not None and rset.rowcount == 0:
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
-def one_line_rset(cls, req, rset=None, row=None, **kwargs):
-    """if row is specified, accept result set with a single line of result,
-    else accepts anyway
-    """
-    if rset is not None and (row is not None or rset.rowcount == 1):
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
-def two_lines_rset(cls, req, rset=None, **kwargs):
-    """accept result set with *at least* two lines of result"""
-    if rset is not None and rset.rowcount > 1:
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
-def two_cols_rset(cls, req, rset=None, **kwargs):
-    """accept result set with at least one line and two columns of result"""
-    if rset is not None and rset.rowcount and len(rset.rows[0]) > 1:
-        return 1
-    return 0
-
-@objectify_selector
-@lltrace
-def paginated_rset(cls, req, rset=None, **kwargs):
-    """accept result set with more lines than the page size.
-
-    Page size is searched in (respecting order):
-    * a page_size argument
-    * a page_size form parameters
-    * the navigation.page-size property
-    """
-    page_size = kwargs.get('page_size')
-    if page_size is None:
-        page_size = req.form.get('page_size')
-        if page_size is None:
-            page_size = req.property_value('navigation.page-size')
-        else:
-            page_size = int(page_size)
-    if rset is None or rset.rowcount <= page_size:
-        return 0
-    return 1
-
-@objectify_selector
-@lltrace
-def sorted_rset(cls, req, rset=None, **kwargs):
-    """accept sorted result set"""
-    rqlst = rset.syntax_tree()
-    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
-        return 0
-    return 2
-
-@objectify_selector
-@lltrace
-def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
-    """accept result set where entities in the specified column (or 0) are all
-    of the same type
-    """
-    if rset is None:
-        return 0
-    if len(rset.column_types(col)) != 1:
-        return 0
-    return 1
-
-@objectify_selector
-@lltrace
-def two_etypes_rset(cls, req, rset=None, col=0, **kwargs):
-    """accept result set where entities in the specified column (or 0) are not
-    of the same type
-    """
-    if rset:
-        etypes = rset.column_types(col)
-        if len(etypes) > 1:
-            return 1
-    return 0
-
-class non_final_entity(EClassSelector):
-    """accept if entity type found in the result set is non final.
-
-    See `EClassSelector` documentation for behaviour when row is not specified.
-    """
-    def score(self, cls, req, etype):
-        if etype in BASE_TYPES:
-            return 0
-        return 1
-
-@objectify_selector
-@lltrace
-def authenticated_user(cls, req, *args, **kwargs):
-    """accept if user is authenticated"""
-    if req.cnx.anonymous_connection:
-        return 0
-    return 1
-
-def anonymous_user():
-    return ~ authenticated_user()
-
-@objectify_selector
-@lltrace
-def primary_view(cls, req, rset=None, row=None, col=0, view=None, **kwargs):
-    """accept if view given as named argument is a primary view, or if no view
-    is given
-    """
-    if view is not None and not view.is_primary():
-        return 0
-    return 1
-
-@objectify_selector
-@lltrace
-def match_context_prop(cls, req, rset=None, row=None, col=0, context=None,
-                       **kwargs):
-    """accept if:
-    * no context given
-    * context (`basestring`) is matching the context property value for the
-      given cls
-    """
-    propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
-    if not propval:
-        propval = cls.context
-    if context is not None and propval and context != propval:
-        return 0
-    return 1
-
-
-class match_search_state(Selector):
-    """accept if the current request search state is in one of the expected
-    states given to the initializer
-
-    :param expected: either 'normal' or 'linksearch' (eg searching for an
-                     object to create a relation with another)
+class ExpectedValueSelector(Selector):
+    """Take a list of expected values as initializer argument, check
+    _get_value method return one of these expected values.
     """
     def __init__(self, *expected):
         assert expected, self
@@ -454,356 +318,405 @@
                            ','.join(sorted(str(s) for s in self.expected)))
 
     @lltrace
-    def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
-        try:
-            if not req.search_state[0] in self.expected:
-                return 0
-        except AttributeError:
-            return 1 # class doesn't care about search state, accept it
-        return 1
+    def __call__(self, cls, req, **kwargs):
+        if self._get_value(cls, req, **kwargs) in self.expected:
+            return 1
+        return 0
+
+    def _get_value(self, cls, req, **kwargs):
+        raise NotImplementedError()
 
 
-class match_form_params(match_search_state):
-    """accept if parameters specified as initializer arguments are specified
-    in request's form parameters
+# bare selectors ##############################################################
 
-    :param *expected: parameters (eg `basestring`) which are expected to be
-                      found in request's form parameters
+class match_kwargs(ExpectedValueSelector):
+    """Return non-zero score if parameter names specified as initializer
+    arguments are specified in the input context. When multiple parameters are
+    specified, all of them should be specified in the input context. Return a
+    score corresponding to the number of expected parameters.
     """
 
     @lltrace
-    def __call__(self, cls, req, *args, **kwargs):
-        score = 0
-        for param in self.expected:
-            if not param in req.form:
-                return 0
-            score += 1
-        return len(self.expected)
-
-
-class match_kwargs(match_search_state):
-    """accept if parameters specified as initializer arguments are specified
-    in named arguments given to the selector
-
-    :param *expected: parameters (eg `basestring`) which are expected to be
-                      found in named arguments (kwargs)
-    """
-
-    @lltrace
-    def __call__(self, cls, req, *args, **kwargs):
+    def __call__(self, cls, req, **kwargs):
         for arg in self.expected:
             if not arg in kwargs:
                 return 0
         return len(self.expected)
 
 
-class match_user_groups(match_search_state):
-    """accept if logged users is in at least one of the given groups. Returned
-    score is the number of groups in which the user is.
-
-    If the special 'owners' group is given:
-    * if row is specified check the entity at the given row/col is owned by the
-      logged user
-    * if row is not specified check all entities in col are owned by the logged
-      user
-
-    :param *required_groups: name of groups (`basestring`) in which the logged
-                             user should be
-    """
-
-    @lltrace
-    def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
-        user = req.user
-        if user is None:
-            return int('guests' in self.expected)
-        score = user.matching_groups(self.expected)
-        if not score and 'owners' in self.expected and rset:
-            if row is not None:
-                if not user.owns(rset[row][col]):
-                    return 0
-                score = 1
-            else:
-                score = all(user.owns(r[col]) for r in rset)
-        return score
-
+class appobject_selectable(Selector):
+    """return 1 if another appobject is selectable using the same input context.
 
-class match_transition(match_search_state):
-    @lltrace
-    def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
-        try:
-            # XXX check this is a transition that apply to the object?
-            if not kwargs['transition'].name in self.expected:
-                return 0
-        except KeyError:
-            return 0
-        return 1
-
-
-class match_view(match_search_state):
-    """accept if the current view is in one of the expected vid given to the
-    initializer
+    Initializer arguments:
+    * `registry`, a registry name
+    * `regid`, an object identifier in this registry
     """
-    @lltrace
-    def __call__(self, cls, req, rset=None, row=None, col=0, view=None, **kwargs):
-        if view is None or not view.id in self.expected:
-            return 0
-        return 1
-
-
-class appobject_selectable(Selector):
-    """accept with another appobject is selectable using selector's input
-    context.
-
-    :param registry: a registry name (`basestring`)
-    :param oid: an object identifier (`basestring`)
-    """
-    def __init__(self, registry, oid):
+    def __init__(self, registry, regid):
         self.registry = registry
-        self.oid = oid
+        self.regid = regid
 
     def __call__(self, cls, req, **kwargs):
         try:
-            cls.vreg[self.registry].select(self.oid, req, **kwargs)
+            req.vreg[self.registry].select(self.regid, req, **kwargs)
             return 1
         except NoSelectableObject:
             return 0
 
 
-# not so basic selectors ######################################################
+# rset selectors ##############################################################
 
-class implements(ImplementsMixIn, EClassSelector):
-    """accept if entity classes found in the result set implements at least one
-    of the interfaces given as argument. Returned score is the number of
-    implemented interfaces.
+@objectify_selector
+@lltrace
+def none_rset(cls, req, rset=None, **kwargs):
+    """Return 1 if the result set is None (eg usually not specified)."""
+    if rset is None:
+        return 1
+    return 0
 
-    See `EClassSelector` documentation for behaviour when row is not specified.
 
-    :param *expected_ifaces: expected interfaces. An interface may be a class
-                             or an entity type (e.g. `basestring`) in which case
-                             the associated class will be searched in the
-                             registry (at selection time)
+# XXX == ~ none_rset
+@objectify_selector
+@lltrace
+def any_rset(cls, req, rset=None, **kwargs):
+    """Return 1 for any result set, whatever the number of rows in it, even 0."""
+    if rset is not None:
+        return 1
+    return 0
 
-    note: when interface is an entity class, the score will reflect class
-          proximity so the most specific object'll be selected
-    """
-    def score_class(self, eclass, req):
-        return self.score_interfaces(eclass, eclass)
+
+@objectify_selector
+@lltrace
+def nonempty_rset(cls, req, rset=None, **kwargs):
+    """Return 1 for result set containing one ore more rows."""
+    if rset is not None and rset.rowcount:
+        return 1
+    return 0
 
 
-class specified_etype_implements(implements):
-    """accept if entity class specified using an 'etype' parameters in name
-    argument or request form implements at least one of the interfaces given as
-    argument. Returned score is the number of implemented interfaces.
+# XXX == ~ nonempty_rset
+@objectify_selector
+@lltrace
+def empty_rset(cls, req, rset=None, **kwargs):
+    """Return 1 for result set which doesn't contain any row."""
+    if rset is not None and rset.rowcount == 0:
+        return 1
+    return 0
+
+
+# XXX == multi_lines_rset(1)
+@objectify_selector
+@lltrace
+def one_line_rset(cls, req, rset=None, row=None, **kwargs):
+    """Return 1 if the result set is of size 1 or if a specific row in the
+    result set is specified ('row' argument).
+    """
+    if rset is not None and (row is not None or rset.rowcount == 1):
+        return 1
+    return 0
+
 
-    :param *expected_ifaces: expected interfaces. An interface may be a class
-                             or an entity type (e.g. `basestring`) in which case
-                             the associated class will be searched in the
-                             registry (at selection time)
+class multi_lines_rset(Selector):
+    """If `nb`is specified, return 1 if the result set has exactly `nb` row of
+    result. Else (`nb` is None), return 1 if the result set contains *at least*
+    two rows.
+    """
+    def __init__(self, nb=None):
+        self.expected = nb
 
-    note: when interface is an entity class, the score will reflect class
-          proximity so the most specific object'll be selected
+    def match_expected(self, num):
+        if self.expected is None:
+            return num > 1
+        return num == self.expected
+
+    @lltrace
+    def __call__(self, cls, req, rset=None, **kwargs):
+        return rset is not None and self.match_expected(rset.rowcount)
+
+
+class multi_columns_rset(multi_lines_rset):
+    """If `nb`is specified, return 1 if the result set has exactly `nb` column
+    per row. Else (`nb` is None), return 1 if the result set contains *at least*
+    two columns per row. Return 0 for empty result set.
     """
 
     @lltrace
-    def __call__(self, cls, req, *args, **kwargs):
-        try:
-            etype = kwargs['etype']
-        except KeyError:
-            try:
-                etype = req.form['etype']
-            except KeyError:
-                return 0
-            else:
-                # only check this is a known type if etype comes from req.form,
-                # else we want the error to propagate
-                try:
-                    etype = cls.vreg.case_insensitive_etypes[etype.lower()]
-                    req.form['etype'] = etype
-                except KeyError:
-                    return 0
-        score = self.score_class(cls.vreg['etypes'].etype_class(etype), req)
-        if score:
-            eschema = req.vreg.schema.eschema(etype)
-            if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
-                return score
+    def __call__(self, cls, req, rset=None, **kwargs):
+        # 'or 0' since we *must not* return None
+        return rset and self.match_expected(len(rset.rows[0])) or 0
+
+
+@objectify_selector
+@lltrace
+def paginated_rset(cls, req, rset=None, **kwargs):
+    """Return 1 for result set with more rows than a page size.
+
+    Page size is searched in (respecting order):
+    * a `page_size` argument
+    * a `page_size` form parameters
+    * the :ref:`navigation.page-size` property
+    """
+    if rset is None:
+        return 0
+    page_size = kwargs.get('page_size')
+    if page_size is None:
+        page_size = req.form.get('page_size')
+        if page_size is None:
+            page_size = req.property_value('navigation.page-size')
+        else:
+            page_size = int(page_size)
+    if rset.rowcount <= page_size:
         return 0
+    return 1
+
+
+@objectify_selector
+@lltrace
+def sorted_rset(cls, req, rset=None, **kwargs):
+    """Return 1 for sorted result set (e.g. from an RQL query containing an
+    :ref:ORDERBY clause.
+    """
+    if rset is None:
+        return 0
+    rqlst = rset.syntax_tree()
+    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
+        return 0
+    return 2
+
+
+# XXX == multi_etypes_rset(1)
+@objectify_selector
+@lltrace
+def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
+    """Return 1 if the result set contains entities which are all of the same
+    type in the column specified by the `col` argument of the input context, or
+    in column 0.
+    """
+    if rset is None:
+        return 0
+    if len(rset.column_types(col)) != 1:
+        return 0
+    return 1
+
+
+class multi_etypes_rset(multi_lines_rset):
+    """If `nb` is specified, return 1 if the result set contains `nb` different
+    types of entities in the column specified by the `col` argument of the input
+    context, or in column 0. If `nb` is None, return 1 if the result set contains
+    *at least* two different types of entities.
+    """
+
+    @lltrace
+    def __call__(self, cls, req, rset=None, col=0, **kwargs):
+        # 'or 0' since we *must not* return None
+        return rset and self.match_expected(len(rset.column_types(col))) or 0
 
 
-class entity_implements(ImplementsMixIn, EntitySelector):
-    """accept if entity instances found in the result set implements at least one
-    of the interfaces given as argument. Returned score is the number of
-    implemented interfaces.
+# entity selectors #############################################################
+
+class non_final_entity(EClassSelector):
+    """Return 1 for entity of a non final entity type(s). Remember, "final"
+    entity types are String, Int, etc... This is equivalent to
+    `implements('Any')` but more optimized.
 
-    See `EntitySelector` documentation for behaviour when row is not specified.
+    See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+    class lookup / score rules according to the input context.
+    """
+    def score(self, cls, req, etype):
+        if etype in BASE_TYPES:
+            return 0
+        return 1
+
+    def score_class(self, eclass, req):
+        return 1 # necessarily true if we're there
 
-    :param *expected_ifaces: expected interfaces. An interface may be a class
-                             or an entity type (e.g. `basestring`) in which case
-                             the associated class will be searched in the
-                             registry (at selection time)
+
+class implements(ImplementsMixIn, EClassSelector):
+    """Return non-zero score for entity that are of the given type(s) or
+    implements at least one of the given interface(s). If multiple arguments are
+    given, matching one of them is enough.
+
+    Entity types should be given as string, the corresponding class will be
+    fetched from the entity types registry at selection time.
 
-    note: when interface is an entity class, the score will reflect class
-          proximity so the most specific object'll be selected
+    See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+    class lookup / score rules according to the input context.
+
+    .. note:: when interface is an entity class, the score will reflect class
+              proximity so the most specific object will be selected.
     """
-    def score_entity(self, entity):
-        return self.score_interfaces(entity, entity.__class__)
+    def score_class(self, eclass, req):
+        return self.score_interfaces(req, eclass, eclass)
 
 
-class relation_possible(EClassSelector):
-    """accept if entity class found in the result set support the relation.
+class score_entity(EntitySelector):
+    """Return score according to an arbitrary function given as argument which
+    will be called with input content entity as argument.
+
+    This is a very useful selector that will usually interest you since it
+    allows a lot of things without having to write a specific selector.
 
-    See `EClassSelector` documentation for behaviour when row is not specified.
+    See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+    lookup / score rules according to the input context.
+    """
+    def __init__(self, scorefunc, once_is_enough=False):
+        super(score_entity, self).__init__(once_is_enough)
+        def intscore(*args, **kwargs):
+            score = scorefunc(*args, **kwargs)
+            if not score:
+                return 0
+            if isinstance(score, (int, long)):
+                return score
+            return 1
+        self.score_entity = intscore
+
+
+class relation_possible(EntitySelector):
+    """Return 1 for entity that supports the relation, provided that the
+    request's user may do some `action` on it (see below).
+
+    The relation is specified by the following initializer arguments:
+
+    * `rtype`, the name of the relation
 
-    :param rtype: a relation type (`basestring`)
-    :param role: the role of the result set entity in the relation. 'subject' or
-                 'object', default to 'subject'.
-    :param target_type: if specified, check the relation's end may be of this
-                        target type (`basestring`)
-    :param action: a relation schema action (one of 'read', 'add', 'delete')
-                   which must be granted to the logged user, else a 0 score will
-                   be returned
+    * `role`, the role of the entity in the relation, either 'subject' or
+      'object', default to 'subject'
+
+    * `target_etype`, optional name of an entity type that should be supported
+      at the other end of the relation
+
+    * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete',
+      default to 'read') which must be granted to the user, else a 0 score will
+      be returned
+
+    * `strict`, boolean (default to False) telling what to do when the user has
+      not globally the permission for the action (eg the action is not granted
+      to one of the user's groups)
+
+      - when strict is False, if there are some local role defined for this
+        action (e.g. using rql expressions), then the permission will be
+        considered as granted
+
+      - when strict is True, then the permission will be actually checked for
+        each entity
+
+    Setting `strict` to True impacts performance for large result set since
+    you'll then get the :class:`~cubicweb.selectors.EntitySelector` behaviour
+    while otherwise you get the :class:`~cubicweb.selectors.EClassSelector`'s
+    one. See those classes documentation for entity lookup / score rules
+    according to the input context.
     """
+
     def __init__(self, rtype, role='subject', target_etype=None,
-                 action='read', once_is_enough=False):
-        super(relation_possible, self).__init__(once_is_enough)
+                 action='read', strict=False, **kwargs):
+        super(relation_possible, self).__init__(**kwargs)
         self.rtype = rtype
         self.role = role
         self.target_etype = target_etype
         self.action = action
+        self.strict = strict
 
-    @lltrace
-    def __call__(self, cls, req, *args, **kwargs):
-        rschema = cls.schema.rschema(self.rtype)
-        if not (rschema.has_perm(req, self.action)
-                or rschema.has_local_role(self.action)):
-            return 0
-        if self.action != 'read':
-            if not (rschema.has_perm(req, 'read')
-                    or rschema.has_local_role('read')):
-                return 0
-        score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
-        return score
+    # hack hack hack
+    def __call__(self, cls, req, **kwargs):
+        if self.strict:
+            return EntitySelector.__call__(self, cls, req, **kwargs)
+        return EClassSelector.__call__(self, cls, req, **kwargs)
 
-    def score_class(self, eclass, req):
+    def score(self, *args):
+        if self.strict:
+            return EntitySelector.score(self, *args)
+        return EClassSelector.score(self, *args)
+
+    def _get_rschema(self, eclass):
         eschema = eclass.e_schema
         try:
             if self.role == 'object':
-                rschema = eschema.objrels[self.rtype]
+                return eschema.objrels[self.rtype]
             else:
-                rschema = eschema.subjrels[self.rtype]
+                return eschema.subjrels[self.rtype]
         except KeyError:
-            return 0
+            return None
+
+    def score_class(self, eclass, req):
+        rschema = self._get_rschema(eclass)
+        if rschema is None:
+            return 0 # relation not supported
+        eschema = eclass.e_schema
         if self.target_etype is not None:
             try:
-                if self.role == 'subject':
-                    return int(self.target_etype in rschema.objects(eschema))
-                else:
-                    return int(self.target_etype in rschema.subjects(eschema))
+                rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
+                if not rdef.may_have_permission(self.action, req):
+                    return 0
             except KeyError:
                 return 0
+        else:
+            return rschema.may_have_permission(self.action, req, eschema, self.role)
+        return 1
+
+    def score_entity(self, entity):
+        rschema = self._get_rschema(entity)
+        if rschema is None:
+            return 0 # relation not supported
+        if self.target_etype is not None:
+            rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
+        if self.role == 'subject':
+            if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid):
+                return 0
+        elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid):
+            return 0
         return 1
 
 
 class partial_relation_possible(PartialSelectorMixIn, relation_possible):
-    """partial version of the relation_possible selector
-
-    The selector will look for class attributes to find its missing
-    information. The list of attributes required on the class
-    for this selector are:
-
-    - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+    """Same as :class:~`cubicweb.selectors.relation_possible`, but will look for
+    attributes of the selected class to get information which is otherwise
+    expected by the initializer, except for `action` and `strict` which are kept
+    as initializer arguments.
 
-    - `role`: this attribute will be passed to the `cubicweb.role` function
-      to determine the role of class in the relation
-
-    - `etype` (optional): the entity type on the other side of the relation
-
-    :param action: a relation schema action (one of 'read', 'add', 'delete')
-                   which must be granted to the logged user, else a 0 score will
-                   be returned
+    This is useful to predefine selector of an abstract class designed to be
+    customized.
     """
-    def __init__(self, action='read', once_is_enough=False):
+    def __init__(self, action='read', **kwargs):
         super(partial_relation_possible, self).__init__(None, None, None,
-                                                        action, once_is_enough)
+                                                        action, **kwargs)
 
     def complete(self, cls):
         self.rtype = cls.rtype
         self.role = role(cls)
         self.target_etype = getattr(cls, 'etype', None)
-
-
-class may_add_relation(EntitySelector):
-    """accept if the relation can be added to an entity found in the result set
-    by the logged user.
-
-    See `EntitySelector` documentation for behaviour when row is not specified.
-
-    :param rtype: a relation type (`basestring`)
-    :param role: the role of the result set entity in the relation. 'subject' or
-                 'object', default to 'subject'.
-    """
-
-    def __init__(self, rtype, role='subject', once_is_enough=False):
-        super(may_add_relation, self).__init__(once_is_enough)
-        self.rtype = rtype
-        self.role = role
-
-    def score_entity(self, entity):
-        rschema = entity.schema.rschema(self.rtype)
-        if self.role == 'subject':
-            if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid):
-                return 0
-        elif not rschema.has_perm(entity.req, 'add', toeid=entity.eid):
-            return 0
-        return 1
-
-
-class partial_may_add_relation(PartialSelectorMixIn, may_add_relation):
-    """partial version of the may_add_relation selector
-
-    The selector will look for class attributes to find its missing
-    information. The list of attributes required on the class
-    for this selector are:
-
-    - `rtype`: same as `rtype` parameter of the `relation_possible` selector
-
-    - `role`: this attribute will be passed to the `cubicweb.role` function
-      to determine the role of class in the relation.
-
-    :param action: a relation schema action (one of 'read', 'add', 'delete')
-                   which must be granted to the logged user, else a 0 score will
-                   be returned
-    """
-    def __init__(self, once_is_enough=False):
-        super(partial_may_add_relation, self).__init__(None, None, once_is_enough)
-
-    def complete(self, cls):
-        self.rtype = cls.rtype
-        self.role = role(cls)
+        if self.target_etype is not None:
+            warn('[3.6] please rename etype to target_etype on %s' % cls,
+                 DeprecationWarning)
+        else:
+            self.target_etype = getattr(cls, 'target_etype', None)
 
 
 class has_related_entities(EntitySelector):
-    """accept if entity found in the result set has some linked entities using
-    the specified relation (optionaly filtered according to the specified target
-    type). Checks first if the relation is possible.
+    """Return 1 if entity support the specified relation and has some linked
+    entities by this relation , optionaly filtered according to the specified
+    target type.
 
-    See `EntitySelector` documentation for behaviour when row is not specified.
+    The relation is specified by the following initializer arguments:
+
+    * `rtype`, the name of the relation
 
-    :param rtype: a relation type (`basestring`)
-    :param role: the role of the result set entity in the relation. 'subject' or
-                 'object', default to 'subject'.
-    :param target_type: if specified, check the relation's end may be of this
-                        target type (`basestring`)
+    * `role`, the role of the entity in the relation, either 'subject' or
+      'object', default to 'subject'.
+
+    * `target_etype`, optional name of an entity type that should be found
+      at the other end of the relation
+
+    See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+    lookup / score rules according to the input context.
     """
-    def __init__(self, rtype, role='subject', target_etype=None,
-                 once_is_enough=False):
-        super(has_related_entities, self).__init__(once_is_enough)
+    def __init__(self, rtype, role='subject', target_etype=None, **kwargs):
+        super(has_related_entities, self).__init__(**kwargs)
         self.rtype = rtype
         self.role = role
         self.target_etype = target_etype
 
     def score_entity(self, entity):
         relpossel = relation_possible(self.rtype, self.role, self.target_etype)
-        if not relpossel.score_class(entity.__class__, entity.req):
+        if not relpossel.score_class(entity.__class__, entity._cw):
             return 0
         rset = entity.related(self.rtype, self.role)
         if self.target_etype:
@@ -812,52 +725,52 @@
 
 
 class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
-    """partial version of the has_related_entities selector
-
-    The selector will look for class attributes to find its missing
-    information. The list of attributes required on the class
-    for this selector are:
-
-    - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+    """Same as :class:~`cubicweb.selectors.has_related_entity`, but will look
+    for attributes of the selected class to get information which is otherwise
+    expected by the initializer.
 
-    - `role`: this attribute will be passed to the `cubicweb.role` function
-      to determine the role of class in the relation.
-
-    - `etype` (optional): the entity type on the other side of the relation
+    This is useful to predefine selector of an abstract class designed to be
+    customized.
+    """
+    def __init__(self, **kwargs):
+        super(partial_has_related_entities, self).__init__(None, None, None,
+                                                           **kwargs)
 
-    :param action: a relation schema action (one of 'read', 'add', 'delete')
-                   which must be granted to the logged user, else a 0 score will
-                   be returned
-    """
-    def __init__(self, once_is_enough=False):
-        super(partial_has_related_entities, self).__init__(None, None,
-                                                           None, once_is_enough)
     def complete(self, cls):
         self.rtype = cls.rtype
         self.role = role(cls)
         self.target_etype = getattr(cls, 'etype', None)
+        if self.target_etype is not None:
+            warn('[3.6] please rename etype to target_etype on %s' % cls,
+                 DeprecationWarning)
+        else:
+            self.target_etype = getattr(cls, 'target_etype', None)
 
 
 class has_permission(EntitySelector):
-    """accept if user has the permission to do the requested action on a result
-    set entity.
+    """Return non-zero score if request's user has the permission to do the
+    requested action on the entity. `action` is an entity schema action (eg one
+    of 'read', 'add', 'delete', 'update').
 
-    * if row is specified, return 1 if user has the permission on the entity
-      instance found in the specified cell
-    * else return a positive score if user has the permission for every entity
-      in the found in the specified column
+    Here are entity lookup / scoring rules:
+
+    * if `entity` is specified, check permission is granted for this entity
 
-    note: None values (resulting from some outer join in the query) are not
-          considered.
+    * elif `row` is specified, check permission is granted for the entity found
+      in the specified cell
 
-    :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
+    * else check permission is granted for each entity found in the column
+      specified specified by the `col` argument or in column 0
     """
-    def __init__(self, action, once_is_enough=False):
-        super(has_permission, self).__init__(once_is_enough)
+    def __init__(self, action):
         self.action = action
 
+    # don't use EntitySelector.__call__ but this optimized implementation to
+    # avoid considering each entity when it's not necessary
     @lltrace
     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+        if kwargs.get('entity'):
+            return self.score_entity(kwargs['entity'])
         if rset is None:
             return 0
         user = req.user
@@ -865,7 +778,7 @@
         if row is None:
             score = 0
             need_local_check = []
-            geteschema = cls.schema.eschema
+            geteschema = req.vreg.schema.eschema
             for etype in rset.column_types(0):
                 if etype in BASE_TYPES:
                     return 0
@@ -897,32 +810,47 @@
 
 
 class has_add_permission(EClassSelector):
-    """accept if logged user has the add permission on entity class found in the
-    result set, and class is not a strict subobject.
+    """Return 1 if request's user has the add permission on entity type
+    specified in the `etype` initializer argument, or according to entity found
+    in the input content if not specified.
 
-    See `EClassSelector` documentation for behaviour when row is not specified.
+    It also check that then entity type is not a strict subobject (e.g. may only
+    be used as a composed of another entity).
+
+    See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+    class lookup / score rules according to the input context when `etype` is
+    not specified.
     """
-    def score(self, cls, req, etype):
-        eschema = cls.schema.eschema(etype)
-        if not (eschema.final or eschema.is_subobject(strict=True)) \
-               and eschema.has_perm(req, 'add'):
-            return 1
-        return 0
+    def __init__(self, etype=None, **kwargs):
+        super(has_add_permission, self).__init__(**kwargs)
+        self.etype = etype
+
+    @lltrace
+    def __call__(self, cls, req, **kwargs):
+        if self.etype is None:
+            return super(has_add_permission, self).__call__(cls, req, **kwargs)
+        return self.score(cls, req, self.etype)
+
+    def score_class(self, eclass, req):
+        eschema = eclass.e_schema
+        if eschema.final or eschema.is_subobject(strict=True) \
+               or not eschema.has_perm(req, 'add'):
+            return 0
+        return 1
 
 
 class rql_condition(EntitySelector):
-    """accept if an arbitrary rql return some results for an eid found in the
-    result set. Returned score is the number of items returned by the rql
+    """Return non-zero score if arbitrary rql specified in `expression`
+    initializer argument return some results for entity found in the input
+    context. Returned score is the number of items returned by the rql
     condition.
 
-    See `EntitySelector` documentation for behaviour when row is not specified.
+    `expression` is expected to be a string containing an rql expression, which
+    must use 'X' variable to represent the context entity and may use 'U' to
+    represent the request's user.
 
-    :param expression: basestring containing an rql expression, which should use
-                       X variable to represent the context entity and may use U
-                       to represent the logged user
-
-    return the sum of the number of items returned by the rql condition as score
-    or 0 at the first entity scoring to zero.
+    See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+    lookup / score rules according to the input context.
     """
     def __init__(self, expression, once_is_enough=False):
         super(rql_condition, self).__init__(once_is_enough)
@@ -932,6 +860,9 @@
             rql = 'Any X WHERE X eid %%(x)s, %s' % expression
         self.rql = rql
 
+    def __repr__(self):
+        return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+
     def score(self, req, rset, row, col):
         try:
             return len(req.execute(self.rql, {'x': rset[row][col],
@@ -939,11 +870,222 @@
         except Unauthorized:
             return 0
 
-    def __repr__(self):
-        return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+# logged user selectors ########################################################
+
+@objectify_selector
+@lltrace
+def authenticated_user(cls, req, **kwargs):
+    """Return 1 if the user is authenticated (e.g. not the anonymous user).
+
+    May only be used on the web side, not on the data repository side.
+    """
+    if req.cnx.anonymous_connection:
+        return 0
+    return 1
+
+
+# XXX == ~ authenticated_user()
+def anonymous_user():
+    """Return 1 if the user is not authenticated (e.g. is the anonymous user).
+
+    May only be used on the web side, not on the data repository side.
+    """
+    return ~ authenticated_user()
+
+
+class match_user_groups(ExpectedValueSelector):
+    """Return a non-zero score if request's user is in at least one of the
+    groups given as initializer argument. Returned score is the number of groups
+    in which the user is.
+
+    If the special 'owners' group is given and `rset` is specified in the input
+    context:
+
+    * if `row` is specified check the entity at the given `row`/`col` (default
+      to 0) is owned by the user
+
+    * else check all entities in `col` (default to 0) are owned by the user
+    """
+
+    @lltrace
+    def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+        user = req.user
+        if user is None:
+            return int('guests' in self.expected)
+        score = user.matching_groups(self.expected)
+        if not score and 'owners' in self.expected and rset:
+            if row is not None:
+                if not user.owns(rset[row][col]):
+                    return 0
+                score = 1
+            else:
+                score = all(user.owns(r[col]) for r in rset)
+        return score
+
+
+# Web request selectors ########################################################
+
+@objectify_selector
+@lltrace
+def primary_view(cls, req, view=None, **kwargs):
+    """Return 1 if:
+
+    * *no view is specified* in the input context
+
+    * a view is specified and its `.is_primary()` method return True
+
+    This selector is usually used by contextual components that only want to
+    appears for the primary view of an entity.
+    """
+    if view is not None and not view.is_primary():
+        return 0
+    return 1
+
+
+class match_view(ExpectedValueSelector):
+    """Return 1 if a view is specified an as its registry id is in one of the
+    expected view id given to the initializer.
+    """
+    @lltrace
+    def __call__(self, cls, req, view=None, **kwargs):
+        if view is None or not view.__regid__ in self.expected:
+            return 0
+        return 1
 
 
-class but_etype(EntitySelector):
+@objectify_selector
+@lltrace
+def match_context_prop(cls, req, context=None, **kwargs):
+    """Return 1 if:
+
+    * no `context` is specified in input context (take care to confusion, here
+      `context` refers to a string given as an argument to the input context...)
+
+    * specified `context` is matching the context property value for the
+      appobject using this selector
+
+    * the appobject's context property value is None
+
+    This selector is usually used by contextual components that want to appears
+    in a configurable place.
+    """
+    if context is None:
+        return 1
+    propval = req.property_value('%s.%s.context' % (cls.__registry__,
+                                                    cls.__regid__))
+    if not propval:
+        propval = cls.context
+    if propval and context != propval:
+        return 0
+    return 1
+
+
+class match_search_state(ExpectedValueSelector):
+    """Return 1 if the current request search state is in one of the expected
+    states given to the initializer.
+
+    Known search states are either 'normal' or 'linksearch' (eg searching for an
+    object to create a relation with another).
+
+    This selector is usually used by action that want to appears or not according
+    to the ui search state.
+    """
+
+    @lltrace
+    def __call__(self, cls, req, **kwargs):
+        try:
+            if not req.search_state[0] in self.expected:
+                return 0
+        except AttributeError:
+            return 1 # class doesn't care about search state, accept it
+        return 1
+
+
+class match_form_params(ExpectedValueSelector):
+    """Return non-zero score if parameter names specified as initializer
+    arguments are specified in request's form parameters. When multiple
+    parameters are specified, all of them should be found in req.form. Return a
+    score corresponding to the number of expected parameters.
+    """
+
+    @lltrace
+    def __call__(self, cls, req, **kwargs):
+        for param in self.expected:
+            if not param in req.form:
+                return 0
+        return len(self.expected)
+
+
+class specified_etype_implements(implements):
+    """Return non-zero score if the entity type specified by an 'etype' key
+    searched in (by priority) input context kwargs and request form parameters
+    match a known entity type (case insensitivly), and it's associated entity
+    class is of one of the type(s) given to the initializer or implements at
+    least one of the given interfaces. If multiple arguments are given, matching
+    one of them is enough.
+
+    Entity types should be given as string, the corresponding class will be
+    fetched from the entity types registry at selection time.
+
+    .. note:: when interface is an entity class, the score will reflect class
+              proximity so the most specific object will be selected.
+
+    This selector is usually used by views holding entity creation forms (since
+    we've no result set to work on).
+    """
+
+    @lltrace
+    def __call__(self, cls, req, **kwargs):
+        try:
+            etype = kwargs['etype']
+        except KeyError:
+            try:
+                etype = req.form['etype']
+            except KeyError:
+                return 0
+            else:
+                # only check this is a known type if etype comes from req.form,
+                # else we want the error to propagate
+                try:
+                    etype = req.vreg.case_insensitive_etypes[etype.lower()]
+                    req.form['etype'] = etype
+                except KeyError:
+                    return 0
+        score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
+        if score:
+            eschema = req.vreg.schema.eschema(etype)
+            if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
+                return score
+        return 0
+
+
+# Other selectors ##############################################################
+
+
+class match_transition(ExpectedValueSelector):
+    """Return 1 if:
+
+    * a `transition` argument is found in the input context which
+      has a `.name` attribute matching one of the expected names given to the
+      initializer
+
+    * no transition specified.
+    """
+    @lltrace
+    def __call__(self, cls, req, transition=None, **kwargs):
+        # XXX check this is a transition that apply to the object?
+        if transition is None:
+            return 1
+        if transition is not None and getattr(transition, 'name', None) in self.expected:
+            return 1
+        return 0
+
+
+## deprecated stuff ############################################################
+
+entity_implements = class_renamed('entity_implements', implements)
+
+class _but_etype(EntitySelector):
     """accept if the given entity types are not found in the result set.
 
     See `EntitySelector` documentation for behaviour when row is not specified.
@@ -951,7 +1093,7 @@
     :param *etypes: entity types (`basestring`) which should be refused
     """
     def __init__(self, *etypes):
-        super(but_etype, self).__init__()
+        super(_but_etype, self).__init__()
         self.but_etypes = etypes
 
     def score(self, req, rset, row, col):
@@ -959,225 +1101,12 @@
             return 0
         return 1
 
-
-class score_entity(EntitySelector):
-    """accept if some arbitrary function return a positive score for an entity
-    found in the result set.
-
-    See `EntitySelector` documentation for behaviour when row is not specified.
-
-    :param scorefunc: callable expected to take an entity as argument and to
-                      return a score >= 0
-    """
-    def __init__(self, scorefunc, once_is_enough=False):
-        super(score_entity, self).__init__(once_is_enough)
-        def intscore(*args, **kwargs):
-            score = scorefunc(*args, **kwargs)
-            if not score:
-                return 0
-            if isinstance(score, (int, long)):
-                return score
-            return 1
-        self.score_entity = intscore
-
-
-# XXX DEPRECATED ##############################################################
-# XXX remove when deprecated functions are removed
-filterwarnings('ignore',
-               category=DeprecationWarning,
-               module='cubicweb.selectors')
-from cubicweb.vregistry import chainall
-
-yes_selector = deprecated()(yes)
-norset_selector = deprecated()(none_rset)
-rset_selector = deprecated()(any_rset)
-anyrset_selector = deprecated()(nonempty_rset)
-emptyrset_selector = deprecated()(empty_rset)
-onelinerset_selector = deprecated()(one_line_rset)
-twolinerset_selector = deprecated()(two_lines_rset)
-twocolrset_selector = deprecated()(two_cols_rset)
-largerset_selector = deprecated()(paginated_rset)
-sortedrset_selector = deprecated()(sorted_rset)
-oneetyperset_selector = deprecated()(one_etype_rset)
-multitype_selector = deprecated()(two_etypes_rset)
-anonymous_selector = deprecated()(anonymous_user)
-not_anonymous_selector = deprecated()(authenticated_user)
-primaryview_selector = deprecated()(primary_view)
-contextprop_selector = deprecated()(match_context_prop)
-
-@deprecated('use non_final_entity instead of %s')
-def nfentity_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    return non_final_entity()(cls, req, rset, row, col)
-
-@deprecated('use implements instead of %s')
-def implement_interface(cls, req, rset=None, row=None, col=0, **kwargs):
-    return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
-_interface_selector = deprecated()(implement_interface)
-interface_selector = deprecated()(implement_interface)
-
-@deprecated('use specified_etype_implements instead of %s')
-def accept_etype(cls, req, *args, **kwargs):
-    """check etype presence in request form *and* accepts conformance"""
-    return specified_etype_implements(*cls.accepts)(cls, req, *args)
-etype_form_selector = accept_etype
-
-@deprecated('use match_search_state instead of %s')
-def searchstate_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    return match_search_state(cls.search_states)(cls, req, rset, row, col)
-
-@deprecated('use match_user_groups instead of %s')
-def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
-    return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs)
-in_group_selector = match_user_group
-
-@deprecated('use relation_possible instead of %s')
-def has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
-    return relation_possible(cls.rtype, role(cls), cls.etype,
-                             getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use relation_possible instead of %s')
-def one_has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
-    return relation_possible(cls.rtype, role(cls), cls.etype,
-                             getattr(cls, 'require_permission', 'read',
-                                     once_is_enough=True))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use implements instead of %s')
-def accept_rset(cls, req, rset=None, row=None, col=0, **kwargs):
-    """simply delegate to cls.accept_rset method"""
-    return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
-accept_rset_selector = accept_rset
-
-accept = chainall(non_final_entity(), accept_rset, name='accept')
-accept = deprecated('use implements selector')(accept)
-accept_selector = deprecated()(accept)
-
-accept_one = deprecated()(chainall(one_line_rset, accept,
-                                   name='accept_one'))
-accept_one_selector = deprecated()(accept_one)
+but_etype = class_renamed('but_etype', _but_etype, 'use ~implements(*etypes) instead')
 
 
-def _rql_condition(cls, req, rset=None, row=None, col=0, **kwargs):
-    if cls.condition:
-        return rql_condition(cls.condition)(cls, req, rset, row, col)
-    return 1
-_rqlcondition_selector = deprecated()(_rql_condition)
-
-rqlcondition_selector = deprecated()(chainall(non_final_entity(), one_line_rset, _rql_condition,
-                         name='rql_condition'))
-
-@deprecated('use but_etype instead of %s')
-def but_etype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    return but_etype(cls.etype)(cls, req, rset, row, col)
-
-@lltrace
-def etype_rtype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
-    schema = cls.schema
-    perm = getattr(cls, 'require_permission', 'read')
-    if hasattr(cls, 'etype'):
-        eschema = schema.eschema(cls.etype)
-        if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
-            return 0
-    if hasattr(cls, 'rtype'):
-        rschema = schema.rschema(cls.rtype)
-        if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
-            return 0
-    return 1
-etype_rtype_selector = deprecated()(etype_rtype_selector)
-
-#req_form_params_selector = deprecated()(match_form_params) # form_params
-#kwargs_selector = deprecated()(match_kwargs) # expected_kwargs
-
-# compound selectors ##########################################################
-
-searchstate_accept = chainall(nonempty_rset(), accept,
-                              name='searchstate_accept')
-searchstate_accept_selector = deprecated()(searchstate_accept)
-
-searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
-                                  name='searchstate_accept_one')
-searchstate_accept_one_selector = deprecated()(searchstate_accept_one)
-
-searchstate_accept = deprecated()(searchstate_accept)
-searchstate_accept_one = deprecated()(searchstate_accept_one)
-
-# end of deprecation section ##################################################
-
-def unbind_method(selector):
-    def new_selector(registered):
-        # get the unbound method
-        if hasattr(registered, 'im_func'):
-            registered = registered.im_func
-        # don't rebind since it will be done automatically during
-        # the assignment, inside the destination class body
-        return selector(registered)
-    new_selector.__name__ = selector.__name__
-    return new_selector
-
-
-def deprecate(registered, msg):
-    # get the unbound method
-    if hasattr(registered, 'im_func'):
-        registered = registered.im_func
-    def _deprecate(cls, vreg):
-        warn(msg, DeprecationWarning)
-        return registered(cls, vreg)
-    return _deprecate
-
-@unbind_method
-def require_group_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'require_groups', None):
-            warn('use "match_user_groups(group1, group2)" instead of using require_groups',
-                 DeprecationWarning)
-            cls.__select__ &= match_user_groups(cls.require_groups)
-        return cls
-    return plug_selector
-
-@unbind_method
-def accepts_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'accepts', None):
-            warn('use "implements("EntityType", IFace)" instead of using accepts on %s'
-                 % cls,
-                 DeprecationWarning)
-            cls.__select__ &= implements(*cls.accepts)
-        return cls
-    return plug_selector
-
-@unbind_method
-def accepts_etype_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'accepts', None):
-            warn('use "specified_etype_implements("EntityType", IFace)" instead of using accepts',
-                 DeprecationWarning)
-            cls.__select__ &= specified_etype_implements(*cls.accepts)
-        return cls
-    return plug_selector
-
-@unbind_method
-def condition_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'condition', None):
-            warn('use "use rql_condition(expression)" instead of using condition',
-                 DeprecationWarning)
-            cls.__select__ &= rql_condition(cls.condition)
-        return cls
-    return plug_selector
-
-@unbind_method
-def has_relation_compat(registered):
-    def plug_selector(cls, vreg):
-        cls = registered(cls, vreg)
-        if getattr(cls, 'etype', None):
-            warn('use relation_possible selector instead of using etype_rtype',
-                 DeprecationWarning)
-            cls.__select__ &= relation_possible(cls.rtype, role(cls),
-                                                getattr(cls, 'etype', None),
-                                                action=getattr(cls, 'require_permission', 'read'))
-        return cls
-    return plug_selector
-
+# XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)?
+#     take care at the implementation though (looking for the 'row' argument's
+#     value)
+two_lines_rset = class_renamed('two_lines_rset', multi_lines_rset)
+two_cols_rset = class_renamed('two_cols_rset', multi_columns_rset)
+two_etypes_rset = class_renamed('two_etypes_rset', multi_etypes_rset)