selectors.py
branchtls-sprint
changeset 1474 716f0742ee7f
parent 1472 96e06e623494
child 1492 53d28ac02868
equal deleted inserted replaced
1473:717dea3362c0 1474:716f0742ee7f
    31     # in Python2.4
    31     # in Python2.4
    32     from cubicweb import selectors
    32     from cubicweb import selectors
    33     selectors.TRACED_OIDS = ('calendar',)
    33     selectors.TRACED_OIDS = ('calendar',)
    34     self.view('calendar', myrset)
    34     self.view('calendar', myrset)
    35     selectors.TRACED_OIDS = ()
    35     selectors.TRACED_OIDS = ()
    36  
    36 
    37 
    37 
    38 :organization: Logilab
    38 :organization: Logilab
    39 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
    39 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
    40 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    40 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    41 """
    41 """
    95     or
    95     or
    96 
    96 
    97     >>> with traced_selection( ('oid1', 'oid2') ):
    97     >>> with traced_selection( ('oid1', 'oid2') ):
    98     ...     # some code in which you want to debug selectors
    98     ...     # some code in which you want to debug selectors
    99     ...     # for objects with id 'oid1' and 'oid2'
    99     ...     # for objects with id 'oid1' and 'oid2'
   100     
   100 
   101     """
   101     """
   102     def __init__(self, traced='all'):
   102     def __init__(self, traced='all'):
   103         self.traced = traced
   103         self.traced = traced
   104         
   104 
   105     def __enter__(self):
   105     def __enter__(self):
   106         global TRACED_OIDS
   106         global TRACED_OIDS
   107         TRACED_OIDS = self.traced
   107         TRACED_OIDS = self.traced
   108 
   108 
   109     def __exit__(self, exctype, exc, traceback):
   109     def __exit__(self, exctype, exc, traceback):
   137 # abstract selectors ##########################################################
   137 # abstract selectors ##########################################################
   138 
   138 
   139 class PartialSelectorMixIn(object):
   139 class PartialSelectorMixIn(object):
   140     """convenience mix-in for selectors that will look into the containing
   140     """convenience mix-in for selectors that will look into the containing
   141     class to find missing information.
   141     class to find missing information.
   142     
   142 
   143     cf. `cubicweb.web.action.LinkToEntityAction` for instance
   143     cf. `cubicweb.web.action.LinkToEntityAction` for instance
   144     """
   144     """
   145     def __call__(self, cls, *args, **kwargs):
   145     def __call__(self, cls, *args, **kwargs):
   146         self.complete(cls)
   146         self.complete(cls)
   147         return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs)
   147         return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs)
   155         self.expected_ifaces = expected_ifaces
   155         self.expected_ifaces = expected_ifaces
   156 
   156 
   157     def __str__(self):
   157     def __str__(self):
   158         return '%s(%s)' % (self.__class__.__name__,
   158         return '%s(%s)' % (self.__class__.__name__,
   159                            ','.join(str(s) for s in self.expected_ifaces))
   159                            ','.join(str(s) for s in self.expected_ifaces))
   160     
   160 
   161     def score_interfaces(self, cls_or_inst, cls):
   161     def score_interfaces(self, cls_or_inst, cls):
   162         score = 0
   162         score = 0
   163         vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
   163         vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
   164         for iface in self.expected_ifaces:
   164         for iface in self.expected_ifaces:
   165             if isinstance(iface, basestring):
   165             if isinstance(iface, basestring):
   185       - `once_is_enough` is False, in which case if score_class return 0, 0 is
   185       - `once_is_enough` is False, in which case if score_class return 0, 0 is
   186         returned
   186         returned
   187     """
   187     """
   188     def __init__(self, once_is_enough=False):
   188     def __init__(self, once_is_enough=False):
   189         self.once_is_enough = once_is_enough
   189         self.once_is_enough = once_is_enough
   190     
   190 
   191     @lltrace
   191     @lltrace
   192     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   192     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   193         if not rset:
   193         if not rset:
   194             return 0
   194             return 0
   195         score = 0
   195         score = 0
   211 
   211 
   212     def score(self, cls, req, etype):
   212     def score(self, cls, req, etype):
   213         if etype in BASE_TYPES:
   213         if etype in BASE_TYPES:
   214             return 0
   214             return 0
   215         return self.score_class(cls.vreg.etype_class(etype), req)
   215         return self.score_class(cls.vreg.etype_class(etype), req)
   216         
   216 
   217     def score_class(self, eclass, req):
   217     def score_class(self, eclass, req):
   218         raise NotImplementedError()
   218         raise NotImplementedError()
   219 
   219 
   220 
   220 
   221 class EntitySelector(EClassSelector):
   221 class EntitySelector(EClassSelector):
   234         returned
   234         returned
   235 
   235 
   236     note: None values (resulting from some outer join in the query) are not
   236     note: None values (resulting from some outer join in the query) are not
   237           considered.
   237           considered.
   238     """
   238     """
   239     
   239 
   240     @lltrace
   240     @lltrace
   241     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   241     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   242         if not rset and not kwargs.get('entity'):
   242         if not rset and not kwargs.get('entity'):
   243             return 0
   243             return 0
   244         score = 0
   244         score = 0
   263     def score(self, req, rset, row, col):
   263     def score(self, req, rset, row, col):
   264         try:
   264         try:
   265             return self.score_entity(rset.get_entity(row, col))
   265             return self.score_entity(rset.get_entity(row, col))
   266         except NotAnEntity:
   266         except NotAnEntity:
   267             return 0
   267             return 0
   268                                  
   268 
   269     def score_entity(self, entity):
   269     def score_entity(self, entity):
   270         raise NotImplementedError()
   270         raise NotImplementedError()
   271 
   271 
   272 
   272 
   273 # very basic selectors ########################################################
   273 # very basic selectors ########################################################
   300 def nonempty_rset(cls, req, rset, *args, **kwargs):
   300 def nonempty_rset(cls, req, rset, *args, **kwargs):
   301     """accept any non empty result set"""
   301     """accept any non empty result set"""
   302     if rset is not None and rset.rowcount:
   302     if rset is not None and rset.rowcount:
   303         return 1
   303         return 1
   304     return 0
   304     return 0
   305     
   305 
   306 @objectify_selector
   306 @objectify_selector
   307 @lltrace
   307 @lltrace
   308 def empty_rset(cls, req, rset, *args, **kwargs):
   308 def empty_rset(cls, req, rset, *args, **kwargs):
   309     """accept empty result set"""
   309     """accept empty result set"""
   310     if rset is not None and rset.rowcount == 0:
   310     if rset is not None and rset.rowcount == 0:
   339 
   339 
   340 @objectify_selector
   340 @objectify_selector
   341 @lltrace
   341 @lltrace
   342 def paginated_rset(cls, req, rset, *args, **kwargs):
   342 def paginated_rset(cls, req, rset, *args, **kwargs):
   343     """accept result set with more lines than the page size.
   343     """accept result set with more lines than the page size.
   344     
   344 
   345     Page size is searched in (respecting order):
   345     Page size is searched in (respecting order):
   346     * a page_size argument
   346     * a page_size argument
   347     * a page_size form parameters
   347     * a page_size form parameters
   348     * the navigation.page-size property
   348     * the navigation.page-size property
   349     """
   349     """
   440 
   440 
   441 
   441 
   442 class match_search_state(Selector):
   442 class match_search_state(Selector):
   443     """accept if the current request search state is in one of the expected
   443     """accept if the current request search state is in one of the expected
   444     states given to the initializer
   444     states given to the initializer
   445     
   445 
   446     :param expected: either 'normal' or 'linksearch' (eg searching for an
   446     :param expected: either 'normal' or 'linksearch' (eg searching for an
   447                      object to create a relation with another)
   447                      object to create a relation with another)
   448     """
   448     """
   449     def __init__(self, *expected):
   449     def __init__(self, *expected):
   450         assert expected, self
   450         assert expected, self
   451         self.expected = frozenset(expected)
   451         self.expected = frozenset(expected)
   452 
   452 
   453     def __str__(self):
   453     def __str__(self):
   454         return '%s(%s)' % (self.__class__.__name__,
   454         return '%s(%s)' % (self.__class__.__name__,
   455                            ','.join(sorted(str(s) for s in self.expected)))
   455                            ','.join(sorted(str(s) for s in self.expected)))
   456         
   456 
   457     @lltrace
   457     @lltrace
   458     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   458     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   459         try:
   459         try:
   460             if not req.search_state[0] in self.expected:
   460             if not req.search_state[0] in self.expected:
   461                 return 0
   461                 return 0
   465 
   465 
   466 
   466 
   467 class match_form_params(match_search_state):
   467 class match_form_params(match_search_state):
   468     """accept if parameters specified as initializer arguments are specified
   468     """accept if parameters specified as initializer arguments are specified
   469     in request's form parameters
   469     in request's form parameters
   470     
   470 
   471     :param *expected: parameters (eg `basestring`) which are expected to be
   471     :param *expected: parameters (eg `basestring`) which are expected to be
   472                       found in request's form parameters
   472                       found in request's form parameters
   473     """
   473     """
   474 
   474 
   475     @lltrace
   475     @lltrace
   484 
   484 
   485 
   485 
   486 class match_kwargs(match_search_state):
   486 class match_kwargs(match_search_state):
   487     """accept if parameters specified as initializer arguments are specified
   487     """accept if parameters specified as initializer arguments are specified
   488     in named arguments given to the selector
   488     in named arguments given to the selector
   489     
   489 
   490     :param *expected: parameters (eg `basestring`) which are expected to be
   490     :param *expected: parameters (eg `basestring`) which are expected to be
   491                       found in named arguments (kwargs)
   491                       found in named arguments (kwargs)
   492     """
   492     """
   493 
   493 
   494     @lltrace
   494     @lltrace
   500 
   500 
   501 
   501 
   502 class match_user_groups(match_search_state):
   502 class match_user_groups(match_search_state):
   503     """accept if logged users is in at least one of the given groups. Returned
   503     """accept if logged users is in at least one of the given groups. Returned
   504     score is the number of groups in which the user is.
   504     score is the number of groups in which the user is.
   505     
   505 
   506     If the special 'owners' group is given:
   506     If the special 'owners' group is given:
   507     * if row is specified check the entity at the given row/col is owned by the
   507     * if row is specified check the entity at the given row/col is owned by the
   508       logged user
   508       logged user
   509     * if row is not specified check all entities in col are owned by the logged
   509     * if row is not specified check all entities in col are owned by the logged
   510       user
   510       user
   511 
   511 
   512     :param *required_groups: name of groups (`basestring`) in which the logged
   512     :param *required_groups: name of groups (`basestring`) in which the logged
   513                              user should be
   513                              user should be
   514     """
   514     """
   515     
   515 
   516     @lltrace
   516     @lltrace
   517     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   517     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   518         user = req.user
   518         user = req.user
   519         if user is None:
   519         if user is None:
   520             return int('guests' in self.expected)
   520             return int('guests' in self.expected)
   548     :param oid: an object identifier (`basestring`)
   548     :param oid: an object identifier (`basestring`)
   549     """
   549     """
   550     def __init__(self, registry, oid):
   550     def __init__(self, registry, oid):
   551         self.registry = registry
   551         self.registry = registry
   552         self.oid = oid
   552         self.oid = oid
   553         
   553 
   554     def __call__(self, cls, req, rset, *args, **kwargs):
   554     def __call__(self, cls, req, rset, *args, **kwargs):
   555         try:
   555         try:
   556             cls.vreg.select_object(self.registry, self.oid, req, rset, *args, **kwargs)
   556             cls.vreg.select_object(self.registry, self.oid, req, rset, *args, **kwargs)
   557             return 1
   557             return 1
   558         except NoSelectableObject:
   558         except NoSelectableObject:
   570 
   570 
   571     :param *expected_ifaces: expected interfaces. An interface may be a class
   571     :param *expected_ifaces: expected interfaces. An interface may be a class
   572                              or an entity type (e.g. `basestring`) in which case
   572                              or an entity type (e.g. `basestring`) in which case
   573                              the associated class will be searched in the
   573                              the associated class will be searched in the
   574                              registry (at selection time)
   574                              registry (at selection time)
   575                              
   575 
   576     note: when interface is an entity class, the score will reflect class
   576     note: when interface is an entity class, the score will reflect class
   577           proximity so the most specific object'll be selected
   577           proximity so the most specific object'll be selected
   578     """
   578     """
   579     def score_class(self, eclass, req):
   579     def score_class(self, eclass, req):
   580         return self.score_interfaces(eclass, eclass)
   580         return self.score_interfaces(eclass, eclass)
   587 
   587 
   588     :param *expected_ifaces: expected interfaces. An interface may be a class
   588     :param *expected_ifaces: expected interfaces. An interface may be a class
   589                              or an entity type (e.g. `basestring`) in which case
   589                              or an entity type (e.g. `basestring`) in which case
   590                              the associated class will be searched in the
   590                              the associated class will be searched in the
   591                              registry (at selection time)
   591                              registry (at selection time)
   592                              
   592 
   593     note: when interface is an entity class, the score will reflect class
   593     note: when interface is an entity class, the score will reflect class
   594           proximity so the most specific object'll be selected
   594           proximity so the most specific object'll be selected
   595     """
   595     """
   596     
   596 
   597     @lltrace
   597     @lltrace
   598     def __call__(self, cls, req, *args, **kwargs):
   598     def __call__(self, cls, req, *args, **kwargs):
   599         try:
   599         try:
   600             etype = req.form['etype']
   600             etype = req.form['etype']
   601         except KeyError:
   601         except KeyError:
   615 
   615 
   616     :param *expected_ifaces: expected interfaces. An interface may be a class
   616     :param *expected_ifaces: expected interfaces. An interface may be a class
   617                              or an entity type (e.g. `basestring`) in which case
   617                              or an entity type (e.g. `basestring`) in which case
   618                              the associated class will be searched in the
   618                              the associated class will be searched in the
   619                              registry (at selection time)
   619                              registry (at selection time)
   620                              
   620 
   621     note: when interface is an entity class, the score will reflect class
   621     note: when interface is an entity class, the score will reflect class
   622           proximity so the most specific object'll be selected
   622           proximity so the most specific object'll be selected
   623     """    
   623     """
   624     def score_entity(self, entity):
   624     def score_entity(self, entity):
   625         return self.score_interfaces(entity, entity.__class__)
   625         return self.score_interfaces(entity, entity.__class__)
   626 
   626 
   627 
   627 
   628 class relation_possible(EClassSelector):
   628 class relation_possible(EClassSelector):
   653         if not (rschema.has_perm(req, self.action)
   653         if not (rschema.has_perm(req, self.action)
   654                 or rschema.has_local_role(self.action)):
   654                 or rschema.has_local_role(self.action)):
   655             return 0
   655             return 0
   656         score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
   656         score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
   657         return score
   657         return score
   658         
   658 
   659     def score_class(self, eclass, req):
   659     def score_class(self, eclass, req):
   660         eschema = eclass.e_schema
   660         eschema = eclass.e_schema
   661         try:
   661         try:
   662             if self.role == 'object':
   662             if self.role == 'object':
   663                 rschema = eschema.object_relation(self.rtype)
   663                 rschema = eschema.object_relation(self.rtype)
   682     The selector will look for class attributes to find its missing
   682     The selector will look for class attributes to find its missing
   683     information. The list of attributes required on the class
   683     information. The list of attributes required on the class
   684     for this selector are:
   684     for this selector are:
   685 
   685 
   686     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   686     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   687     
   687 
   688     - `role`: this attribute will be passed to the `cubicweb.role` function
   688     - `role`: this attribute will be passed to the `cubicweb.role` function
   689       to determine the role of class in the relation
   689       to determine the role of class in the relation
   690 
   690 
   691     - `etype` (optional): the entity type on the other side of the relation
   691     - `etype` (optional): the entity type on the other side of the relation
   692     
   692 
   693     :param action: a relation schema action (one of 'read', 'add', 'delete')
   693     :param action: a relation schema action (one of 'read', 'add', 'delete')
   694                    which must be granted to the logged user, else a 0 score will
   694                    which must be granted to the logged user, else a 0 score will
   695                    be returned
   695                    be returned
   696     """
   696     """
   697     def __init__(self, action='read', once_is_enough=False):
   697     def __init__(self, action='read', once_is_enough=False):
   705 
   705 
   706 
   706 
   707 class may_add_relation(EntitySelector):
   707 class may_add_relation(EntitySelector):
   708     """accept if the relation can be added to an entity found in the result set
   708     """accept if the relation can be added to an entity found in the result set
   709     by the logged user.
   709     by the logged user.
   710     
   710 
   711     See `EntitySelector` documentation for behaviour when row is not specified.
   711     See `EntitySelector` documentation for behaviour when row is not specified.
   712 
   712 
   713     :param rtype: a relation type (`basestring`)
   713     :param rtype: a relation type (`basestring`)
   714     :param role: the role of the result set entity in the relation. 'subject' or
   714     :param role: the role of the result set entity in the relation. 'subject' or
   715                  'object', default to 'subject'.
   715                  'object', default to 'subject'.
   716     """
   716     """
   717     
   717 
   718     def __init__(self, rtype, role='subject', once_is_enough=False):
   718     def __init__(self, rtype, role='subject', once_is_enough=False):
   719         super(may_add_relation, self).__init__(once_is_enough)
   719         super(may_add_relation, self).__init__(once_is_enough)
   720         self.rtype = rtype
   720         self.rtype = rtype
   721         self.role = role
   721         self.role = role
   722         
   722 
   723     def score_entity(self, entity):
   723     def score_entity(self, entity):
   724         rschema = entity.schema.rschema(self.rtype)
   724         rschema = entity.schema.rschema(self.rtype)
   725         if self.role == 'subject':
   725         if self.role == 'subject':
   726             if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid):
   726             if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid):
   727                 return 0
   727                 return 0
   736     The selector will look for class attributes to find its missing
   736     The selector will look for class attributes to find its missing
   737     information. The list of attributes required on the class
   737     information. The list of attributes required on the class
   738     for this selector are:
   738     for this selector are:
   739 
   739 
   740     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   740     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   741     
   741 
   742     - `role`: this attribute will be passed to the `cubicweb.role` function
   742     - `role`: this attribute will be passed to the `cubicweb.role` function
   743       to determine the role of class in the relation.
   743       to determine the role of class in the relation.
   744     
   744 
   745     :param action: a relation schema action (one of 'read', 'add', 'delete')
   745     :param action: a relation schema action (one of 'read', 'add', 'delete')
   746                    which must be granted to the logged user, else a 0 score will
   746                    which must be granted to the logged user, else a 0 score will
   747                    be returned
   747                    be returned
   748     """
   748     """
   749     def __init__(self, once_is_enough=False):
   749     def __init__(self, once_is_enough=False):
   751 
   751 
   752     def complete(self, cls):
   752     def complete(self, cls):
   753         self.rtype = cls.rtype
   753         self.rtype = cls.rtype
   754         self.role = role(cls)
   754         self.role = role(cls)
   755 
   755 
   756     
   756 
   757 class has_related_entities(EntitySelector):
   757 class has_related_entities(EntitySelector):
   758     """accept if entity found in the result set has some linked entities using
   758     """accept if entity found in the result set has some linked entities using
   759     the specified relation (optionaly filtered according to the specified target
   759     the specified relation (optionaly filtered according to the specified target
   760     type). Checks first if the relation is possible.
   760     type). Checks first if the relation is possible.
   761     
   761 
   762     See `EntitySelector` documentation for behaviour when row is not specified.
   762     See `EntitySelector` documentation for behaviour when row is not specified.
   763 
   763 
   764     :param rtype: a relation type (`basestring`)
   764     :param rtype: a relation type (`basestring`)
   765     :param role: the role of the result set entity in the relation. 'subject' or
   765     :param role: the role of the result set entity in the relation. 'subject' or
   766                  'object', default to 'subject'.
   766                  'object', default to 'subject'.
   771                  once_is_enough=False):
   771                  once_is_enough=False):
   772         super(has_related_entities, self).__init__(once_is_enough)
   772         super(has_related_entities, self).__init__(once_is_enough)
   773         self.rtype = rtype
   773         self.rtype = rtype
   774         self.role = role
   774         self.role = role
   775         self.target_etype = target_etype
   775         self.target_etype = target_etype
   776     
   776 
   777     def score_entity(self, entity):
   777     def score_entity(self, entity):
   778         relpossel = relation_possible(self.rtype, self.role, self.target_etype)
   778         relpossel = relation_possible(self.rtype, self.role, self.target_etype)
   779         if not relpossel.score_class(entity.__class__, entity.req):
   779         if not relpossel.score_class(entity.__class__, entity.req):
   780             return 0
   780             return 0
   781         rset = entity.related(self.rtype, self.role)
   781         rset = entity.related(self.rtype, self.role)
   790     The selector will look for class attributes to find its missing
   790     The selector will look for class attributes to find its missing
   791     information. The list of attributes required on the class
   791     information. The list of attributes required on the class
   792     for this selector are:
   792     for this selector are:
   793 
   793 
   794     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   794     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   795     
   795 
   796     - `role`: this attribute will be passed to the `cubicweb.role` function
   796     - `role`: this attribute will be passed to the `cubicweb.role` function
   797       to determine the role of class in the relation.
   797       to determine the role of class in the relation.
   798 
   798 
   799     - `etype` (optional): the entity type on the other side of the relation
   799     - `etype` (optional): the entity type on the other side of the relation
   800     
   800 
   801     :param action: a relation schema action (one of 'read', 'add', 'delete')
   801     :param action: a relation schema action (one of 'read', 'add', 'delete')
   802                    which must be granted to the logged user, else a 0 score will
   802                    which must be granted to the logged user, else a 0 score will
   803                    be returned
   803                    be returned
   804     """
   804     """
   805     def __init__(self, once_is_enough=False):
   805     def __init__(self, once_is_enough=False):
   820     * else return a positive score if user has the permission for every entity
   820     * else return a positive score if user has the permission for every entity
   821       in the found in the specified column
   821       in the found in the specified column
   822 
   822 
   823     note: None values (resulting from some outer join in the query) are not
   823     note: None values (resulting from some outer join in the query) are not
   824           considered.
   824           considered.
   825     
   825 
   826     :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
   826     :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
   827     """
   827     """
   828     def __init__(self, action, once_is_enough=False):
   828     def __init__(self, action, once_is_enough=False):
   829         super(has_permission, self).__init__(once_is_enough)
   829         super(has_permission, self).__init__(once_is_enough)
   830         self.action = action
   830         self.action = action
   831         
   831 
   832     @lltrace
   832     @lltrace
   833     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   833     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   834         if rset is None:
   834         if rset is None:
   835             return 0
   835             return 0
   836         user = req.user
   836         user = req.user
   837         action = self.action
   837         action = self.action
   838         if row is None:
   838         if row is None:
   839             score = 0
   839             score = 0
   840             need_local_check = [] 
   840             need_local_check = []
   841             geteschema = cls.schema.eschema
   841             geteschema = cls.schema.eschema
   842             for etype in rset.column_types(0):
   842             for etype in rset.column_types(0):
   843                 if etype in BASE_TYPES:
   843                 if etype in BASE_TYPES:
   844                     return 0
   844                     return 0
   845                 eschema = geteschema(etype)
   845                 eschema = geteschema(etype)
   860                     if not self.score(req, rset, i, col):
   860                     if not self.score(req, rset, i, col):
   861                         return 0
   861                         return 0
   862                 score += 1
   862                 score += 1
   863             return score
   863             return score
   864         return self.score(req, rset, row, col)
   864         return self.score(req, rset, row, col)
   865     
   865 
   866     def score_entity(self, entity):
   866     def score_entity(self, entity):
   867         if entity.has_perm(self.action):
   867         if entity.has_perm(self.action):
   868             return 1
   868             return 1
   869         return 0
   869         return 0
   870 
   870 
   885 
   885 
   886 class rql_condition(EntitySelector):
   886 class rql_condition(EntitySelector):
   887     """accept if an arbitrary rql return some results for an eid found in the
   887     """accept if an arbitrary rql return some results for an eid found in the
   888     result set. Returned score is the number of items returned by the rql
   888     result set. Returned score is the number of items returned by the rql
   889     condition.
   889     condition.
   890     
   890 
   891     See `EntitySelector` documentation for behaviour when row is not specified.
   891     See `EntitySelector` documentation for behaviour when row is not specified.
   892 
   892 
   893     :param expression: basestring containing an rql expression, which should use
   893     :param expression: basestring containing an rql expression, which should use
   894                        X variable to represent the context entity and may use U
   894                        X variable to represent the context entity and may use U
   895                        to represent the logged user
   895                        to represent the logged user
   902         if 'U' in frozenset(split_expression(expression)):
   902         if 'U' in frozenset(split_expression(expression)):
   903             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
   903             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
   904         else:
   904         else:
   905             rql = 'Any X WHERE X eid %%(x)s, %s' % expression
   905             rql = 'Any X WHERE X eid %%(x)s, %s' % expression
   906         self.rql = rql
   906         self.rql = rql
   907         
   907 
   908     def score(self, req, rset, row, col):
   908     def score(self, req, rset, row, col):
   909         try:
   909         try:
   910             return len(req.execute(self.rql, {'x': rset[row][col],
   910             return len(req.execute(self.rql, {'x': rset[row][col],
   911                                               'u': req.user.eid}, 'x'))
   911                                               'u': req.user.eid}, 'x'))
   912         except Unauthorized:
   912         except Unauthorized:
   913             return 0
   913             return 0
   914 
   914 
   915         
   915 
   916 class but_etype(EntitySelector):
   916 class but_etype(EntitySelector):
   917     """accept if the given entity types are not found in the result set.
   917     """accept if the given entity types are not found in the result set.
   918 
   918 
   919     See `EntitySelector` documentation for behaviour when row is not specified.
   919     See `EntitySelector` documentation for behaviour when row is not specified.
   920     
   920 
   921     :param *etypes: entity types (`basestring`) which should be refused
   921     :param *etypes: entity types (`basestring`) which should be refused
   922     """
   922     """
   923     def __init__(self, *etypes):
   923     def __init__(self, *etypes):
   924         super(but_etype, self).__init__()
   924         super(but_etype, self).__init__()
   925         self.but_etypes = etypes
   925         self.but_etypes = etypes
   926         
   926 
   927     def score(self, req, rset, row, col):
   927     def score(self, req, rset, row, col):
   928         if rset.description[row][col] in self.but_etypes:
   928         if rset.description[row][col] in self.but_etypes:
   929             return 0
   929             return 0
   930         return 1
   930         return 1
   931 
   931 
   932                 
   932 
   933 class score_entity(EntitySelector):
   933 class score_entity(EntitySelector):
   934     """accept if some arbitrary function return a positive score for an entity
   934     """accept if some arbitrary function return a positive score for an entity
   935     found in the result set.
   935     found in the result set.
   936     
   936 
   937     See `EntitySelector` documentation for behaviour when row is not specified.
   937     See `EntitySelector` documentation for behaviour when row is not specified.
   938 
   938 
   939     :param scorefunc: callable expected to take an entity as argument and to
   939     :param scorefunc: callable expected to take an entity as argument and to
   940                       return a score >= 0 
   940                       return a score >= 0
   941     """
   941     """
   942     def __init__(self, scorefunc, once_is_enough=False):
   942     def __init__(self, scorefunc, once_is_enough=False):
   943         super(score_entity, self).__init__(once_is_enough)
   943         super(score_entity, self).__init__(once_is_enough)
   944         self.score_entity = scorefunc
   944         self.score_entity = scorefunc
   945 
   945 
  1020     return 1
  1020     return 1
  1021 _rqlcondition_selector = deprecated_function(_rql_condition)
  1021 _rqlcondition_selector = deprecated_function(_rql_condition)
  1022 
  1022 
  1023 rqlcondition_selector = deprecated_function(chainall(non_final_entity(), one_line_rset, _rql_condition,
  1023 rqlcondition_selector = deprecated_function(chainall(non_final_entity(), one_line_rset, _rql_condition,
  1024                          name='rql_condition'))
  1024                          name='rql_condition'))
  1025     
  1025 
  1026 def but_etype_selector(cls, req, rset, row=None, col=0, **kwargs):
  1026 def but_etype_selector(cls, req, rset, row=None, col=0, **kwargs):
  1027     return but_etype(cls.etype)(cls, req, rset, row, col)
  1027     return but_etype(cls.etype)(cls, req, rset, row, col)
  1028 but_etype_selector = deprecated_function(but_etype_selector)
  1028 but_etype_selector = deprecated_function(but_etype_selector)
  1029 
  1029 
  1030 @lltrace
  1030 @lltrace
  1077         registered = registered.im_func
  1077         registered = registered.im_func
  1078     def _deprecate(cls, vreg):
  1078     def _deprecate(cls, vreg):
  1079         warn(msg, DeprecationWarning)
  1079         warn(msg, DeprecationWarning)
  1080         return registered(cls, vreg)
  1080         return registered(cls, vreg)
  1081     return _deprecate
  1081     return _deprecate
  1082     
  1082 
  1083 @unbind_method
  1083 @unbind_method
  1084 def require_group_compat(registered):
  1084 def require_group_compat(registered):
  1085     def plug_selector(cls, vreg):
  1085     def plug_selector(cls, vreg):
  1086         cls = registered(cls, vreg)
  1086         cls = registered(cls, vreg)
  1087         if getattr(cls, 'require_groups', None):
  1087         if getattr(cls, 'require_groups', None):
  1121             warn('use "use rql_condition(expression)" instead of using condition',
  1121             warn('use "use rql_condition(expression)" instead of using condition',
  1122                  DeprecationWarning)
  1122                  DeprecationWarning)
  1123             cls.__select__ &= rql_condition(cls.condition)
  1123             cls.__select__ &= rql_condition(cls.condition)
  1124         return cls
  1124         return cls
  1125     return plug_selector
  1125     return plug_selector
  1126      
  1126 
  1127 @unbind_method
  1127 @unbind_method
  1128 def has_relation_compat(registered):
  1128 def has_relation_compat(registered):
  1129     def plug_selector(cls, vreg):
  1129     def plug_selector(cls, vreg):
  1130         cls = registered(cls, vreg)
  1130         cls = registered(cls, vreg)
  1131         if getattr(cls, 'etype', None):
  1131         if getattr(cls, 'etype', None):