proper documentation, some bug fixes, upgrade some selectors tls-sprint
authorsylvain.thenault@logilab.fr
Tue, 17 Feb 2009 12:33:57 +0100
branchtls-sprint
changeset 650 75b4c661f71b
parent 649 e5956e9ebef1
child 651 ab6e15854f8c
proper documentation, some bug fixes, upgrade some selectors
selectors.py
--- a/selectors.py	Tue Feb 17 09:59:15 2009 +0100
+++ b/selectors.py	Tue Feb 17 12:33:57 2009 +0100
@@ -105,183 +105,24 @@
         TRACED_OIDS = ()
         return traceback is None
 
-# very basic selectors ########################################################
-
-def yes(cls, *args, **kwargs):
-    """accept everything"""
-    return 1
-yes_selector = deprecated_function(yes)
-
-@lltrace
-def none_rset(cls, req, rset, *args, **kwargs):
-    """accept no result set"""
-    if rset is None:
-        return 1
-    return 0
-norset_selector = deprecated_function(none_rset)
-
-@lltrace
-def any_rset(cls, req, rset, *args, **kwargs):
-    """accept result set, whatever the number of result"""
-    if rset is not None:
-        return 1
-    return 0
-rset_selector = deprecated_function(any_rset)
-
-@lltrace
-def nonempty_rset(cls, req, rset, *args, **kwargs):
-    """accept any non empty result set"""
-    if rset is not None and rset.rowcount:
-        return 1
-    return 0
-anyrset_selector = deprecated_function(nonempty_rset)
-    
-@lltrace
-def empty_rset(cls, req, rset, *args, **kwargs):
-    """accept empty result set"""
-    if rset is not None and rset.rowcount == 0:
-        return 1
-    return 0
-emptyrset_selector = deprecated_function(empty_rset)
-
-@lltrace
-def one_line_rset(cls, req, rset, row=None, *args, **kwargs):
-    """accept result set with a single line of result"""
-    if rset is not None and (row is not None or rset.rowcount == 1):
-        return 1
-    return 0
-onelinerset_selector = deprecated_function(one_line_rset)
-
-@lltrace
-def two_lines_rset(cls, req, rset, *args, **kwargs):
-    """accept result set with *at least* two lines of result"""
-    if rset is not None and rset.rowcount > 1:
-        return 1
-    return 0
-twolinerset_selector = deprecated_function(two_lines_rset)
-
-@lltrace
-def two_cols_rset(cls, req, rset, *args, **kwargs):
-    """accept result set with at least one line and two columns of result"""
-    if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1:
-        return 1
-    return 0
-twocolrset_selector = deprecated_function(two_cols_rset)
-
-@lltrace
-def paginated_rset(cls, req, rset, *args, **kwargs):
-    """accept result sets with more rows than the page size
-    """
-    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 len(rset) <= page_size:
-        return 0
-    return 1
-largerset_selector = deprecated_function(paginated_rset)
-
-@lltrace
-def sorted_rset(cls, req, rset, row=None, col=0, **kwargs):
-    """accept sorted result set"""
-    rqlst = rset.syntax_tree()
-    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
-        return 0
-    return 2
-sortedrset_selector = deprecated_function(sorted_rset)
-
-@lltrace
-def one_etype_rset(cls, req, rset, *args, **kwargs):
-    """accept result set where entities in the first columns are all of the
-    same type
-    """
-    if len(rset.column_types(0)) != 1:
-        return 0
-    return 1
-oneetyperset_selector = deprecated_function(one_etype_rset)
-
-@lltrace
-def two_etypes_rset(cls, req, rset, **kwargs):
-    """accepts resultsets containing several entity types"""
-    if rset:
-        etypes = rset.column_types(0)
-        if len(etypes) > 1:
-            return 1
-    return 0
-multitype_selector = deprecated_function(two_etypes_rset)
-
-
-class match_search_state(Selector):
-    def __init__(self, *expected):
-        self.expected = expected
-        
-    @lltrace
-    def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
-        """checks if the current request search state is in one of the expected states
-        the wrapped class
-
-        search state should be either 'normal' or 'linksearch' (eg searching for an
-        object to create a relation with another)
-        """
-        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(match_search_state):
-    """check if parameters specified as initializer arguments are specified
-    in request form parameters
-    """
-    @lltrace
-    def __call__(self, cls, req, *args, **kwargs):
-        score = 0
-        for param in self.expected:
-            val = req.form.get(param)
-            if not val:
-                return 0
-            score += 1
-        return len(self.expected)
-
-
-class match_kwargs(match_search_state):
-    """check if parameters specified as initializer arguments are specified
-    in named parameters
-    """
-    @lltrace
-    def __call__(self, cls, req, *args, **kwargs):
-        for arg in self.expected:
-            if not arg in kwargs:
-                return 0
-        return len(self.expected)
-
-
-@lltrace
-def anonymous_user(cls, req, *args, **kwargs):
-    """accept if user is anonymous"""
-    if req.cnx.anonymous_connection:
-        return 1
-    return 0
-anonymous_selector = deprecated_function(anonymous_user)
-
-@lltrace
-def authenticated_user(cls, req, *args, **kwargs):
-    """accept if user is authenticated"""
-    return not anonymous_user(cls, req, *args, **kwargs)
-not_anonymous_selector = deprecated_function(authenticated_user)
 
 # abstract selectors ##########################################################
 
 class EClassSelector(Selector):
     """abstract class for selectors working on the entity classes of the result
-    set
+    set. Its __call__ method has the following behaviour:
+
+    * 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:
+      - `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
     """
-    once_is_enough = False
+    def __init__(self, once_is_enough=False):
+        self.once_is_enough = once_is_enough
     
     @lltrace
     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
@@ -292,28 +133,44 @@
             for etype in rset.column_types(col):
                 if etype is None: # outer join
                     continue
-                if etype in BASE_TYPES:
-                    return 0
-                escore = self.score_class(cls.vreg.etype_class(etype), req)
-                if not escore:
+                escore = self.score(cls, req, etype)
+                if not escore and not self.once_is_enough:
                     return 0
                 elif self.once_is_enough:
                     return escore
                 score += escore
         else:
             etype = rset.description[row][col]
-            if etype is not None and not etype in BASE_TYPES:
-                score = self.score_class(cls.vreg.etype_class(etype), req)
+            if etype is not None:
+                score = self.score(cls, req, etype)
         return score and (score + 1)
 
+    def score(self, cls, req, etype):
+        if etype in BASE_TYPES:
+            return 0
+        return self.score_class(cls.vreg.etype_class(etype), req)
+        
     def score_class(self, eclass, req):
         raise NotImplementedError()
 
 
-class EntitySelector(Selector):
+class EntitySelector(EClassSelector):
     """abstract class for selectors working on the entity instances of the
-    result set
+    result set. Its __call__ method has the following behaviour:
+
+    * 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:
+      - `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
+
+    note: None values (resulting from some outer join in the query) are not
+          considered.
     """
+    
     @lltrace
     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
         if not rset:
@@ -323,12 +180,11 @@
             for row, rowvalue in enumerate(rset.rows):
                 if rowvalue[col] is None: # outer join
                     continue
-                try:
-                    escore = self.score(req, rset, row, col))
-                except NotAnEntity:
+                escore = self.score(req, rset, row, col))
+                if not escore and not self.once_is_enough:
                     return 0
-                if not escore:
-                    return 0
+                elif self.once_is_enough:
+                    return escore
                 score += escore
         else:
             etype = rset.description[row][col]
@@ -345,21 +201,284 @@
     def score_entity(self, entity):
         raise NotImplementedError()
 
+
+# very basic selectors ########################################################
+
+def yes(cls, *args, **kwargs):
+    """accept everything"""
+    return 1
+
+@lltrace
+def none_rset(cls, req, rset, *args, **kwargs):
+    """accept no result set (e.g. given rset is None)"""
+    if rset is None:
+        return 1
+    return 0
+
+@lltrace
+def any_rset(cls, req, rset, *args, **kwargs):
+    """accept result set, whatever the number of result it contains"""
+    if rset is not None:
+        return 1
+    return 0
+
+@lltrace
+def nonempty_rset(cls, req, rset, *args, **kwargs):
+    """accept any non empty result set"""
+    if rset is not None and rset.rowcount:
+        return 1
+    return 0
+    
+@lltrace
+def empty_rset(cls, req, rset, *args, **kwargs):
+    """accept empty result set"""
+    if rset is not None and rset.rowcount == 0:
+        return 1
+    return 0
+
+@lltrace
+def one_line_rset(cls, req, rset, row=None, *args, **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
+
+@lltrace
+def two_lines_rset(cls, req, rset, *args, **kwargs):
+    """accept result set with *at least* two lines of result"""
+    if rset is not None and rset.rowcount > 1:
+        return 1
+    return 0
+
+@lltrace
+def two_cols_rset(cls, req, rset, *args, **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
+
+@lltrace
+def paginated_rset(cls, req, rset, *args, **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
+
+@lltrace
+def sorted_rset(cls, req, rset, row=None, col=0, **kwargs):
+    """accept sorted result set"""
+    rqlst = rset.syntax_tree()
+    if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
+        return 0
+    return 2
+
+@lltrace
+def one_etype_rset(cls, req, rset, row=None, col=0, *args, **kwargs):
+    """accept result set where entities in the specified column (or 0) are all
+    of the same type
+    """
+    if len(rset.column_types(col)) != 1:
+        return 0
+    return 1
+
+@lltrace
+def two_etypes_rset(cls, req, rset, row=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
+
+@lltrace
+def anonymous_user(cls, req, *args, **kwargs):
+    """accept if user is anonymous"""
+    if req.cnx.anonymous_connection:
+        return 1
+    return 0
+
+@lltrace
+def authenticated_user(cls, req, *args, **kwargs):
+    """accept if user is authenticated"""
+    return not anonymous_user(cls, req, *args, **kwargs)
+
+@lltrace
+def primary_view(cls, req, rset, 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
+
+@lltrace
+def match_context_prop(cls, req, rset, 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)
+    """
+    def __init__(self, *expected):
+        self.expected = expected
+        
+    @lltrace
+    def __call__(self, cls, req, rset, 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
+
+
+class match_form_params(match_search_state):
+    """accept if parameters specified as initializer arguments are specified
+    in request's form parameters
+    
+    :param *expected: parameters (eg `basestring`) which are expected to be
+                      found in request's form parameters
+    """
+
+    @lltrace
+    def __call__(self, cls, req, *args, **kwargs):
+        score = 0
+        for param in self.expected:
+            val = req.form.get(param)
+            if not val:
+                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):
+        for arg in self.expected:
+            if not arg in kwargs:
+                return 0
+        return len(self.expected)
+
+
+class match_user_groups(Selector):
+    """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
+    """
+    
+    def __init__(self, *required_groups):
+        self.required_groups = required_groups
+    
+    @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.required_groups)
+        score = user.matching_groups(self.required_groups)
+        if not score and 'owners' in self.required_groups and rset:
+            nbowned = 0
+            if row is not None:
+                if not user.owns(rset[row][col]):
+                    return 0
+                score = 1
+            else:
+                score = all(user.owns(r[col or 0]) for r in rset)
+        return 0
+
+
+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):
+        self.registry = registry
+        self.oid = oid
+        
+    def __call__(self, cls, req, rset, *args, **kwargs):
+        try:
+            cls.vreg.select_object(self.registry, self.oid, req, rset, *args, **kwargs)
+            return 1
+        except NoSelectableObject:
+            return 0
+
+
 # not so basic selectors ######################################################
 
 class implements(EClassSelector):
-    """initializer takes a list of interfaces or entity types as argument
-    
-    * if row is None, return the number of implemented interfaces for each
-      entity's class in the result set at the specified column (or column 0).
-      If any class has no matching interface, return 0.
-    * if row is specified, return number of implemented interfaces by the
-      entity's class at this row (and column)
+    """accept if entity class found in the result set implements at least one
+    of the interfaces given as argument. Returned score is the number of
+    implemented interfaces.
+
+    See `EClassSelector` documentation for behaviour when row is not specified.
 
-    if some interface is an entity class, the score will reflect class
-    proximity so the most specific object'll be selected
+    :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)
+                             
+    note: when interface is an entity class, the score will reflect class
+          proximity so the most specific object'll be selected
     """
-
     def __init__(self, *expected_ifaces):
         self.expected_ifaces = expected_ifaces
 
@@ -376,21 +495,30 @@
                     # adjust score if the interface is an entity class
                     if iface is eclass:
                         score += len(eclass.e_schema.ancestors())
-                        print 'is majoration', len(eclass.e_schema.ancestors()) 
+#                        print 'is majoration', len(eclass.e_schema.ancestors()) 
                     else:
                         parents = [e.type for e in eclass.e_schema.ancestors()]
                         for index, etype in enumerate(reversed(parents)):
                             basecls = eclass.vreg.etype_class(etype)
                             if iface is basecls:
                                 score += index
-                                print 'etype majoration', index
+#                                print 'etype majoration', index
                                 break
         return score
 
 
 class specified_etype_implements(implements):
-    """return the "interface score" for class associated to 'etype' (expected in
-    request form or arguments)
+    """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.
+
+    :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)
+                             
+    note: when interface is an entity class, the score will reflect class
+          proximity so the most specific object'll be selected
     """
     
     @lltrace
@@ -406,29 +534,32 @@
 
 
 class relation_possible(EClassSelector):
-    """initializer takes relation name as argument and an optional role (default
-      as subject) and target type (default to unspecified)
-      
-    * if row is None, return 1 if every entity's class in the result set at the
-      specified column (or column 0) may have this relation (as role). If target
-      type is specified, check the relation's end may be of this target type.
-      
-    * if row is specified, check relation is supported by the entity's class at
-      this row (and column)
+    """accept if entity class found in the result set support the relation.
+
+    See `EClassSelector` 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'.
+    :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
     """
     def __init__(self, rtype, role='subject', target_etype=None,
-                 permission='read', once_is_enough=False):
+                 action='read', once_is_enough=False):
+        super(relation_possible, self).__init__(once_is_enough)
         self.rtype = rtype
         self.role = role
         self.target_etype = target_etype
-        self.permission = permission
-        self.once_is_enough = once_is_enough
+        self.action = action
 
     @lltrace
     def __call__(self, cls, *args, **kwargs):
         rschema = cls.schema.rschema(self.rtype)
-        if not (rschema.has_perm(req, self.permission)
-                or rschema.has_local_role(self.permission)):
+        if not (rschema.has_perm(req, self.action)
+                or rschema.has_local_role(self.action)):
             return 0
         return super(relation_possible, self)(cls, *args, **kwargs)
         
@@ -452,56 +583,11 @@
         return 1
 
 
-class non_final_entity(EClassSelector):
-    """initializer takes no argument
-
-    * if row is None, return 1 if there are only non final entity's class in the
-      result set at the specified column (or column 0)
-    * if row is specified, return 1 if entity's class at this row (and column)
-      isn't final
-    """
-    def score_class(self, eclass, req):
-        return int(not eclass.e_schema.is_final())
-
-
-class match_user_groups(Selector):
-    """initializer takes users group as argument
+class has_editable_relation(EntitySelector):
+    """accept if some relations for an entity found in the result set is
+    editable by the logged user.
 
-    * check logged user is in one of the given groups. If special 'owners' group
-      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
-    """
-    
-    def __init__(self, *required_groups):
-        self.required_groups = required_groups
-    
-    @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.require_groups)
-        score = user.matching_groups(self.require_groups)
-        if not score and 'owners' in self.require_groups and rset:
-            nbowned = 0
-            if row is not None:
-                if not user.owns(rset[row][col]):
-                    return 0
-                score = 1
-            else:
-                score = all(user.owns(r[col or 0]) for r in rset)
-        return 0
-
-
-class has_editable_relation(EntitySelector):
-    """initializer takes no argument
-
-    * if row is specified check the entity at the given row/col has some
-      relation editable by the logged user
-    * if row is not specified check all entities in col are owned have some
-      relation editable by the logged userlogged user
+    See `EntitySelector` documentation for behaviour when row is not specified.
     """
         
     def score_entity(self, entity):
@@ -518,12 +604,14 @@
 
 
 class may_add_relation(EntitySelector):
-    """initializer takes a relation type and optional role (default to
-    'subject') as argument
+    """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.
 
-    if row is specified check the relation may be added to the entity at the
-    given row/col (if row specified) or to every entities in the given col (if
-    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'):
@@ -534,28 +622,60 @@
         rschema = entity.schema.rschema(self.rtype)
         if self.role == 'subject':
             if not rschema.has_perm(req, 'add', fromeid=entity.eid):
-                return False
+                return 0
         elif not rschema.has_perm(req, 'add', toeid=entity.eid):
-            return False
-        return True
+            return 0
+        return 1
+
+
+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).
+    
+    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'.
+    :param target_type: if specified, check the relation's end may be of this
+                        target type (`basestring`)
+    """
+    def __init__(self, rtype, role='subject', target_etype=None,
+                 once_is_enough=False):
+        self.rtype = rtype
+        self.role = role
+        self.target_etype = target_etype
+        self.once_is_enough = once_is_enough
+    
+    def score_entity(self, entity):
+        rset = entity.related(self.rtype, self.role)
+        if self.target_etype:
+            return any(x for x, in rset.description if x == self.target_etype)
+        return bool(rset)
 
         
 class has_permission(EntitySelector):
-    """initializer takes a schema action (eg 'read'/'add'/'delete'/'update') as
-    argument
+    """accept if user has the permission to do the requested action on a result
+    set entity.
+
+    * 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
 
-    * if row is specified check user has permission to do the requested action
-      on the entity at the given row/col
-    * if row is specified check user has permission to do the requested action
-      on all entities in the given col
+    note: None values (resulting from some outer join in the query) are not
+          considered.
+    
+    :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
     """
-    def __init__(self, schema_action):
-        self.schema_action = schema_action
+    def __init__(self, action):
+        self.action = action
         
     @lltrace
     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
         user = req.user
-        action = self.schema_action
+        action = self.action
         if row is None:
             score = 0
             need_local_check = [] 
@@ -572,53 +692,48 @@
                     else:
                         # even a local role won't be enough
                         return 0
-                score += accepted
+                score += 1
             if need_local_check:
                 # check local role for entities of necessary types
                 for i, row in enumerate(rset):
                     if not rset.description[i][0] in need_local_check:
                         continue
-                    if not self.score_entity(rset.get_entity(i, col)):
+                    if not self.score(req, rset, i, col)):
                         return 0
-                    score += 1
+                score += 1
             return score
-        if rset.description[row][col] in BASE_TYPES:
-            return 0
-        return self.score_entity(rset.get_entity(row, col))
+        return self.score(req, rset, i, col)
     
     def score_entity(self, entity):
-        if entity.has_perm(self.schema_action):
+        if entity.has_perm(self.action):
             return 1
         return 0
 
 
 class has_add_permission(EClassSelector):
-    """return 1 if the user may add some entity of the types found in the
-    result set (0 else)
+    """accept if logged user has the add permission on entity class found in the
+    result set, and class is not a strict subobject.
+
+    See `EClassSelector` documentation for behaviour when row is not specified.
     """
-    def score_class(self, eclass, req):
-        eschema = eclass.e_schema
+    def score(self, cls, req, etype):
+        eschema = cls.schema.eschema(etype)
         if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
                and eschema.has_perm(req, 'add'):
             return 1
         return 0
 
-        
-class score_entity(EntitySelector):
-    """initializer takes a function as argument (which is expected to take an
-    entity as argument)
-
-    return the score returned by the function on the entity at the given row/col
-    (if row specified) or the sum of the score for every entities in the given
-    col (if row is not specified). Return 0 at the first entity scoring to zero.
-    """
-    def __init__(self, scorefunc):
-        self.score_entity = scorefunc
-
 
 class rql_condition(EntitySelector):
-    """initializer takes a rql expression as argument (which should use X
-    variable to represent the context entity).
+    """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
+    condition.
+    
+    See `EntitySelector` documentation for behaviour when row is not specified.
+
+    :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.
@@ -638,93 +753,53 @@
 
         
 class but_etype(EntitySelector):
-    """initializer takes an entity type as argument.
+    """accept if the given entity types are not found in the result set.
 
-    return 0 if an entity type is this type, else 1.
+    See `EntitySelector` documentation for behaviour when row is not specified.
+    
+    :param *etypes: entity types (`basestring`) which should be refused
     """
-    def __init__(self, etype):
-        self.etype = etype
+    def __init__(self, *etypes):
+        self.but_etypes = etypes
         
     def score(self, req, rset, row, col):
-        if rset.description[row][col] == self.etype:
+        if rset.description[row][col] in self.but_etypes:
             return 0
         return 1
 
-
-class appobject_selectable(Selector):
-    """initializer takes a registry and oid of another vobject
+                
+class score_entity(EntitySelector):
+    """accept if some arbitrary function return a positive score for an entity
+    found in the result set.
     
-    return 1 if the given registry and object is selectable using selector's
-    input context, else 0
-    """
-    def __init__(self, registry, oid):
-        self.registry = registry
-        self.oid = oid
-        
-    def __call__(self, cls, req, rset, *args, **kwargs):
-        try:
-            cls.vreg.select_object(self.registry, self.oid, req, rset, *args, **kwargs)
-            return 1
-        except NoSelectableObject:
-            return 0
+    See `EntitySelector` documentation for behaviour when row is not specified.
 
-        
-# XXX not so basic selectors ######################################################
-
-@lltrace
-def etype_rtype_selector(cls, req, rset, row=None, col=0, **kwargs):
-    """only check if the user has read access on the entity's type refered
-    by the .etype attribute and on the relations's type refered by the
-    .rtype attribute if set.
+    :param scorefunc: callable expected to take an entity as argument and to
+                      return a score >= 0 
     """
-    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
-
-@lltrace
-def has_related_entities(cls, req, rset, row=None, col=0, **kwargs):
-    return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
-
-@lltrace
-def user_can_add_etype(cls, req, rset, row=None, col=0, **kwargs):
-    """only check if the user has add access on the entity's type refered
-    by the .etype attribute.
-    """
-    if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
-        return 0
-    return 1
-add_etype_selector = deprecated_function(user_can_add_etype)
-
-@lltrace
-def match_context_prop(cls, req, rset, row=None, col=0, context=None,
-                       **kwargs):
-    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
-contextprop_selector = deprecated_function(match_context_prop)
-
-@lltrace
-def primary_view(cls, req, rset, row=None, col=0, view=None, **kwargs):
-    if view is not None and not view.is_primary():
-        return 0
-    return 1
-primaryview_selector = deprecated_function(primary_view)
-
+    def __init__(self, scorefunc):
+        self.score_entity = scorefunc
 
 
 # XXX DEPRECATED ##############################################################
 
+yes_selector = deprecated_function(yes)
+norset_selector = deprecated_function(none_rset)
+rset_selector = deprecated_function(any_rset)
+anyrset_selector = deprecated_function(nonempty_rset)
+emptyrset_selector = deprecated_function(empty_rset)
+onelinerset_selector = deprecated_function(one_line_rset)
+twolinerset_selector = deprecated_function(two_lines_rset)
+twocolrset_selector = deprecated_function(two_cols_rset)
+largerset_selector = deprecated_function(paginated_rset)
+sortedrset_selector = deprecated_function(sorted_rset)
+oneetyperset_selector = deprecated_function(one_etype_rset)
+multitype_selector = deprecated_function(two_etypes_rset)
+anonymous_selector = deprecated_function(anonymous_user)
+not_anonymous_selector = deprecated_function(authenticated_user)
+primaryview_selector = deprecated_function(primary_view)
+contextprop_selector = deprecated_function(match_context_prop)
+
 def nfentity_selector(cls, req, rset, row=None, col=0, **kwargs):
     return non_final_entity()(cls, req, rset, row, col)
 nfentity_selector = deprecated_function(nfentity_selector)
@@ -789,6 +864,24 @@
     return but_etype(cls.etype)(cls, req, rset, row, col)
 but_etype_selector = deprecated_function(but_etype_selector)
 
+@lltrace
+def etype_rtype_selector(cls, req, rset, row=None, col=0, **kwargs):
+    """only check if the user has read access on the entity's type refered
+    by the .etype attribute and on the relations's type refered by the
+    .rtype attribute if set.
+    """
+    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_function(etype_rtype_selector)
 
 #req_form_params_selector = deprecated_function(match_form_params) # form_params
 #kwargs_selector = deprecated_function(match_kwargs) # expected_kwargs
@@ -804,10 +897,8 @@
                                   name='searchstate_accept_one')
 searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
 
-searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype,
-                                            name='searchstate_accept_one_but_etype')
-searchstate_accept_one_but_etype_selector = deprecated_function(
-    searchstate_accept_one_but_etype)
+searchstate_accept = deprecated_function(searchstate_accept)
+searchstate_accept_one = deprecated_function(searchstate_accept_one)
 
 
 def require_group_compat(registered):