selectors.py
changeset 4462 c57c8176b8c2
parent 4458 6151849f41e0
child 4509 a17033cfc892
equal deleted inserted replaced
4461:a35c76ffed92 4462:c57c8176b8c2
   134         # implenting an interface takes precedence other special Any interface
   134         # implenting an interface takes precedence other special Any interface
   135         return 2
   135         return 2
   136     return 0
   136     return 0
   137 
   137 
   138 
   138 
   139 # abstract selectors ##########################################################
   139 # abstract selectors / mixin helpers ###########################################
   140 
   140 
   141 class PartialSelectorMixIn(object):
   141 class PartialSelectorMixIn(object):
   142     """convenience mix-in for selectors that will look into the containing
   142     """convenience mix-in for selectors that will look into the containing
   143     class to find missing information.
   143     class to find missing information.
   144 
   144 
   174             score += score_interface(etypesreg, cls_or_inst, cls, iface)
   174             score += score_interface(etypesreg, cls_or_inst, cls, iface)
   175         return score
   175         return score
   176 
   176 
   177 
   177 
   178 class EClassSelector(Selector):
   178 class EClassSelector(Selector):
   179     """abstract class for selectors working on the entity classes of the result
   179     """abstract class for selectors working on *entity class(es)* specified
   180     set. Its __call__ method has the following behaviour:
   180     explicitly or found of the result set.
   181 
   181 
   182     * if 'entity' find in kwargs, return the score returned by the score_class
   182     Here are entity lookup / scoring rules:
   183       method for this entity's class
   183 
   184     * elif row is specified, return the score returned by the score_class method
   184     * if `entity` is specified, return score for this entity's class
   185       called with the entity class found in the specified cell
   185 
   186     * else return the sum of score returned by the score_class method for each
   186     * elif `row` is specified, return score for the class of the entity
   187       entity type found in the specified column, unless:
   187       found in the specified cell, using column specified by `col` or 0
       
   188 
       
   189     * else return the sum of scores for each entity class found in the column
       
   190       specified specified by the `col` argument or in column 0 if not specified,
       
   191       unless:
       
   192 
       
   193       - `once_is_enough` is False (the default) and some entity class is scored
       
   194         to 0, in which case 0 is returned
       
   195 
   188       - `once_is_enough` is True, in which case the first non-zero score is
   196       - `once_is_enough` is True, in which case the first non-zero score is
   189         returned
   197         returned
   190       - `once_is_enough` is False, in which case if score_class return 0, 0 is
   198 
   191         returned
   199       - `accept_none` is False and some cell in the column has a None value
       
   200         (this may occurs with outer join)
   192     """
   201     """
   193     def __init__(self, once_is_enough=False, accept_none=True):
   202     def __init__(self, once_is_enough=False, accept_none=True):
   194         self.once_is_enough = once_is_enough
   203         self.once_is_enough = once_is_enough
   195         self.accept_none = accept_none
   204         self.accept_none = accept_none
   196 
   205 
   228     def score_class(self, eclass, req):
   237     def score_class(self, eclass, req):
   229         raise NotImplementedError()
   238         raise NotImplementedError()
   230 
   239 
   231 
   240 
   232 class EntitySelector(EClassSelector):
   241 class EntitySelector(EClassSelector):
   233     """abstract class for selectors working on the entity instances of the
   242     """abstract class for selectors working on *entity instance(s)* specified
   234     result set. Its __call__ method has the following behaviour:
   243     explicitly or found of the result set.
   235 
   244 
   236     * if 'entity' find in kwargs, return the score returned by the score_entity
   245     Here are entity lookup / scoring rules:
   237       method for this entity
   246 
   238     * if row is specified, return the score returned by the score_entity method
   247     * if `entity` is specified, return score for this entity
   239       called with the entity instance found in the specified cell
   248 
   240     * else return the sum of score returned by the score_entity method for each
   249     * elif `row` is specified, return score for the entity found in the
   241       entity found in the specified column, unless:
   250       specified cell, using column specified by `col` or 0
       
   251 
       
   252     * else return the sum of scores for each entity found in the column
       
   253       specified specified by the `col` argument or in column 0 if not specified,
       
   254       unless:
       
   255 
       
   256       - `once_is_enough` is False (the default) and some entity is scored
       
   257         to 0, in which case 0 is returned
       
   258 
   242       - `once_is_enough` is True, in which case the first non-zero score is
   259       - `once_is_enough` is True, in which case the first non-zero score is
   243         returned
   260         returned
   244       - `once_is_enough` is False, in which case if score_class return 0, 0 is
   261 
   245         returned
   262       - `accept_none` is False and some cell in the column has a None value
   246 
   263         (this may occurs with outer join)
   247     note: None values (resulting from some outer join in the query) are not
   264 
   248           considered.
   265     .. note::
       
   266        using EntitySelector or EClassSelector as base selector class impacts
       
   267        performance, since when no entity or row is specified the later works on
       
   268        every different *entity class* found in the result set, while the former
       
   269        works on each *entity* (eg each row of the result set), which may be much
       
   270        more costly.
   249     """
   271     """
   250 
   272 
   251     @lltrace
   273     @lltrace
   252     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   274     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   253         if not rset and not kwargs.get('entity'):
   275         if not rset and not kwargs.get('entity'):
   257             score = self.score_entity(kwargs['entity'])
   279             score = self.score_entity(kwargs['entity'])
   258         elif row is None:
   280         elif row is None:
   259             col = col or 0
   281             col = col or 0
   260             for row, rowvalue in enumerate(rset.rows):
   282             for row, rowvalue in enumerate(rset.rows):
   261                 if rowvalue[col] is None: # outer join
   283                 if rowvalue[col] is None: # outer join
       
   284                     if not self.accept_none:
       
   285                         return 0
   262                     continue
   286                     continue
   263                 escore = self.score(req, rset, row, col)
   287                 escore = self.score(req, rset, row, col)
   264                 if not escore and not self.once_is_enough:
   288                 if not escore and not self.once_is_enough:
   265                     return 0
   289                     return 0
   266                 elif self.once_is_enough:
   290                 elif self.once_is_enough:
   281 
   305 
   282     def score_entity(self, entity):
   306     def score_entity(self, entity):
   283         raise NotImplementedError()
   307         raise NotImplementedError()
   284 
   308 
   285 
   309 
   286 # very basic selectors ########################################################
   310 class ExpectedValueSelector(Selector):
       
   311     """Take a list of expected values as initializer argument, check
       
   312     _get_value method return one of these expected values.
       
   313     """
       
   314     def __init__(self, *expected):
       
   315         assert expected, self
       
   316         self.expected = frozenset(expected)
       
   317 
       
   318     def __str__(self):
       
   319         return '%s(%s)' % (self.__class__.__name__,
       
   320                            ','.join(sorted(str(s) for s in self.expected)))
       
   321 
       
   322     @lltrace
       
   323     def __call__(self, cls, req, **kwargs):
       
   324         if self._get_value(cls, req, **kwargs) in self.expected:
       
   325             return 1
       
   326         return 0
       
   327 
       
   328     def _get_value(self, cls, req, **kwargs):
       
   329         raise NotImplementedError()
       
   330 
       
   331 
       
   332 # bare selectors ##############################################################
       
   333 
       
   334 class match_kwargs(ExpectedValueSelector):
       
   335     """Return non-zero score if parameter names specified as initializer
       
   336     arguments are specified in the input context. When multiple parameters are
       
   337     specified, all of them should be specified in the input context. Return a
       
   338     score corresponding to the number of expected parameters.
       
   339     """
       
   340 
       
   341     @lltrace
       
   342     def __call__(self, cls, req, **kwargs):
       
   343         for arg in self.expected:
       
   344             if not arg in kwargs:
       
   345                 return 0
       
   346         return len(self.expected)
       
   347 
       
   348 
       
   349 class appobject_selectable(Selector):
       
   350     """return 1 if another appobject is selectable using the same input context.
       
   351 
       
   352     Initializer arguments:
       
   353     * `registry`, a registry name
       
   354     * `regid`, an object identifier in this registry
       
   355     """
       
   356     def __init__(self, registry, regid):
       
   357         self.registry = registry
       
   358         self.regid = regid
       
   359 
       
   360     def __call__(self, cls, req, **kwargs):
       
   361         try:
       
   362             req.vreg[self.registry].select(self.regid, req, **kwargs)
       
   363             return 1
       
   364         except NoSelectableObject:
       
   365             return 0
       
   366 
       
   367 
       
   368 # rset selectors ##############################################################
   287 
   369 
   288 @objectify_selector
   370 @objectify_selector
   289 @lltrace
   371 @lltrace
   290 def none_rset(cls, req, rset=None, **kwargs):
   372 def none_rset(cls, req, rset=None, **kwargs):
   291     """accept no result set (e.g. given rset is None)"""
   373     """Return 1 if the result set is None (eg usually not specified)."""
   292     if rset is None:
   374     if rset is None:
   293         return 1
   375         return 1
   294     return 0
   376     return 0
   295 
   377 
       
   378 
       
   379 # XXX == ~ none_rset
   296 @objectify_selector
   380 @objectify_selector
   297 @lltrace
   381 @lltrace
   298 def any_rset(cls, req, rset=None, **kwargs):
   382 def any_rset(cls, req, rset=None, **kwargs):
   299     """accept result set, whatever the number of result it contains"""
   383     """Return 1 for any result set, whatever the number of rows in it, even 0."""
   300     if rset is not None:
   384     if rset is not None:
   301         return 1
   385         return 1
   302     return 0
   386     return 0
   303 
   387 
       
   388 
   304 @objectify_selector
   389 @objectify_selector
   305 @lltrace
   390 @lltrace
   306 def nonempty_rset(cls, req, rset=None, **kwargs):
   391 def nonempty_rset(cls, req, rset=None, **kwargs):
   307     """accept any non empty result set"""
   392     """Return 1 for result set containing one ore more rows."""
   308     if rset is not None and rset.rowcount:
   393     if rset is not None and rset.rowcount:
   309         return 1
   394         return 1
   310     return 0
   395     return 0
   311 
   396 
       
   397 
       
   398 # XXX == ~ nonempty_rset
   312 @objectify_selector
   399 @objectify_selector
   313 @lltrace
   400 @lltrace
   314 def empty_rset(cls, req, rset=None, **kwargs):
   401 def empty_rset(cls, req, rset=None, **kwargs):
   315     """accept empty result set"""
   402     """Return 1 for result set which doesn't contain any row."""
   316     if rset is not None and rset.rowcount == 0:
   403     if rset is not None and rset.rowcount == 0:
   317         return 1
   404         return 1
   318     return 0
   405     return 0
   319 
   406 
       
   407 
       
   408 # XXX == multi_lines_rset(1)
   320 @objectify_selector
   409 @objectify_selector
   321 @lltrace
   410 @lltrace
   322 def one_line_rset(cls, req, rset=None, row=None, **kwargs):
   411 def one_line_rset(cls, req, rset=None, row=None, **kwargs):
   323     """if row is specified, accept result set with a single line of result,
   412     """Return 1 if the result set is of size 1 or if a specific row in the
   324     else accepts anyway
   413     result set is specified ('row' argument).
   325     """
   414     """
   326     if rset is not None and (row is not None or rset.rowcount == 1):
   415     if rset is not None and (row is not None or rset.rowcount == 1):
   327         return 1
   416         return 1
   328     return 0
   417     return 0
   329 
   418 
   330 
   419 
   331 class multi_lines_rset(Selector):
   420 class multi_lines_rset(Selector):
       
   421     """If `nb`is specified, return 1 if the result set has exactly `nb` row of
       
   422     result. Else (`nb` is None), return 1 if the result set contains *at least*
       
   423     two rows.
       
   424     """
   332     def __init__(self, nb=None):
   425     def __init__(self, nb=None):
   333         self.expected = nb
   426         self.expected = nb
   334 
   427 
   335     def match_expected(self, num):
   428     def match_expected(self, num):
   336         if self.expected is None:
   429         if self.expected is None:
   337             return num > 1
   430             return num > 1
   338         return num == self.expected
   431         return num == self.expected
   339 
   432 
   340     @lltrace
   433     @lltrace
   341     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   434     def __call__(self, cls, req, rset=None, **kwargs):
   342         return rset is not None and self.match_expected(rset.rowcount)
   435         return rset is not None and self.match_expected(rset.rowcount)
   343 
   436 
   344 
   437 
   345 class multi_columns_rset(multi_lines_rset):
   438 class multi_columns_rset(multi_lines_rset):
   346 
   439     """If `nb`is specified, return 1 if the result set has exactly `nb` column
   347     @lltrace
   440     per row. Else (`nb` is None), return 1 if the result set contains *at least*
   348     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   441     two columns per row. Return 0 for empty result set.
   349         return rset and self.match_expected(len(rset.rows[0])) or 0 # *must not* return None
   442     """
       
   443 
       
   444     @lltrace
       
   445     def __call__(self, cls, req, rset=None, **kwargs):
       
   446         # 'or 0' since we *must not* return None
       
   447         return rset and self.match_expected(len(rset.rows[0])) or 0
   350 
   448 
   351 
   449 
   352 @objectify_selector
   450 @objectify_selector
   353 @lltrace
   451 @lltrace
   354 def paginated_rset(cls, req, rset=None, **kwargs):
   452 def paginated_rset(cls, req, rset=None, **kwargs):
   355     """accept result set with more lines than the page size.
   453     """Return 1 for result set with more rows than a page size.
   356 
   454 
   357     Page size is searched in (respecting order):
   455     Page size is searched in (respecting order):
   358     * a page_size argument
   456     * a `page_size` argument
   359     * a page_size form parameters
   457     * a `page_size` form parameters
   360     * the navigation.page-size property
   458     * the :ref:`navigation.page-size` property
   361     """
   459     """
       
   460     if rset is None:
       
   461         return 0
   362     page_size = kwargs.get('page_size')
   462     page_size = kwargs.get('page_size')
   363     if page_size is None:
   463     if page_size is None:
   364         page_size = req.form.get('page_size')
   464         page_size = req.form.get('page_size')
   365         if page_size is None:
   465         if page_size is None:
   366             page_size = req.property_value('navigation.page-size')
   466             page_size = req.property_value('navigation.page-size')
   367         else:
   467         else:
   368             page_size = int(page_size)
   468             page_size = int(page_size)
   369     if rset is None or rset.rowcount <= page_size:
   469     if rset.rowcount <= page_size:
   370         return 0
   470         return 0
   371     return 1
   471     return 1
       
   472 
   372 
   473 
   373 @objectify_selector
   474 @objectify_selector
   374 @lltrace
   475 @lltrace
   375 def sorted_rset(cls, req, rset=None, **kwargs):
   476 def sorted_rset(cls, req, rset=None, **kwargs):
   376     """accept sorted result set"""
   477     """Return 1 for sorted result set (e.g. from an RQL query containing an
       
   478     :ref:ORDERBY clause.
       
   479     """
       
   480     if rset is None:
       
   481         return 0
   377     rqlst = rset.syntax_tree()
   482     rqlst = rset.syntax_tree()
   378     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
   483     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
   379         return 0
   484         return 0
   380     return 2
   485     return 2
   381 
   486 
       
   487 
       
   488 # XXX == multi_etypes_rset(1)
   382 @objectify_selector
   489 @objectify_selector
   383 @lltrace
   490 @lltrace
   384 def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
   491 def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
   385     """accept result set where entities in the specified column (or 0) are all
   492     """Return 1 if the result set contains entities which are all of the same
   386     of the same type
   493     type in the column specified by the `col` argument of the input context, or
       
   494     in column 0.
   387     """
   495     """
   388     if rset is None:
   496     if rset is None:
   389         return 0
   497         return 0
   390     if len(rset.column_types(col)) != 1:
   498     if len(rset.column_types(col)) != 1:
   391         return 0
   499         return 0
   392     return 1
   500     return 1
   393 
   501 
   394 
   502 
   395 class multi_etypes_rset(multi_lines_rset):
   503 class multi_etypes_rset(multi_lines_rset):
       
   504     """If `nb` is specified, return 1 if the result set contains `nb` different
       
   505     types of entities in the column specified by the `col` argument of the input
       
   506     context, or in column 0. If `nb` is None, return 1 if the result set contains
       
   507     *at least* two different types of entities.
       
   508     """
   396 
   509 
   397     @lltrace
   510     @lltrace
   398     def __call__(self, cls, req, rset=None, col=0, **kwargs):
   511     def __call__(self, cls, req, rset=None, col=0, **kwargs):
   399         return rset and self.match_expected(len(rset.column_types(col)))
   512         # 'or 0' since we *must not* return None
   400 
   513         return rset and self.match_expected(len(rset.column_types(col))) or 0
       
   514 
       
   515 
       
   516 # entity selectors #############################################################
   401 
   517 
   402 class non_final_entity(EClassSelector):
   518 class non_final_entity(EClassSelector):
   403     """accept if entity type found in the result set is non final.
   519     """Return 1 for entity of a non final entity type(s). Remember, "final"
   404 
   520     entity types are String, Int, etc... This is equivalent to
   405     See `EClassSelector` documentation for behaviour when row is not specified.
   521     `implements('Any')` but more optimized.
       
   522 
       
   523     See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
       
   524     class lookup / score rules according to the input context.
   406     """
   525     """
   407     def score(self, cls, req, etype):
   526     def score(self, cls, req, etype):
   408         if etype in BASE_TYPES:
   527         if etype in BASE_TYPES:
   409             return 0
   528             return 0
   410         return 1
   529         return 1
   411 
   530 
   412 
   531 
   413 @objectify_selector
       
   414 @lltrace
       
   415 def authenticated_user(cls, req, *args, **kwargs):
       
   416     """accept if user is authenticated"""
       
   417     if req.cnx.anonymous_connection:
       
   418         return 0
       
   419     return 1
       
   420 
       
   421 def anonymous_user():
       
   422     return ~ authenticated_user()
       
   423 
       
   424 @objectify_selector
       
   425 @lltrace
       
   426 def primary_view(cls, req, rset=None, row=None, col=0, view=None, **kwargs):
       
   427     """accept if view given as named argument is a primary view, or if no view
       
   428     is given
       
   429     """
       
   430     if view is not None and not view.is_primary():
       
   431         return 0
       
   432     return 1
       
   433 
       
   434 @objectify_selector
       
   435 @lltrace
       
   436 def match_context_prop(cls, req, rset=None, row=None, col=0, context=None,
       
   437                        **kwargs):
       
   438     """accept if:
       
   439     * no context given
       
   440     * context (`basestring`) is matching the context property value for the
       
   441       given cls
       
   442     """
       
   443     propval = req.property_value('%s.%s.context' % (cls.__registry__,
       
   444                                                     cls.__regid__))
       
   445     if not propval:
       
   446         propval = cls.context
       
   447     if context is not None and propval and context != propval:
       
   448         return 0
       
   449     return 1
       
   450 
       
   451 
       
   452 class match_search_state(Selector):
       
   453     """accept if the current request search state is in one of the expected
       
   454     states given to the initializer
       
   455 
       
   456     :param expected: either 'normal' or 'linksearch' (eg searching for an
       
   457                      object to create a relation with another)
       
   458     """
       
   459     def __init__(self, *expected):
       
   460         assert expected, self
       
   461         self.expected = frozenset(expected)
       
   462 
       
   463     def __str__(self):
       
   464         return '%s(%s)' % (self.__class__.__name__,
       
   465                            ','.join(sorted(str(s) for s in self.expected)))
       
   466 
       
   467     @lltrace
       
   468     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   469         try:
       
   470             if not req.search_state[0] in self.expected:
       
   471                 return 0
       
   472         except AttributeError:
       
   473             return 1 # class doesn't care about search state, accept it
       
   474         return 1
       
   475 
       
   476 
       
   477 class match_form_params(match_search_state):
       
   478     """accept if parameters specified as initializer arguments are specified
       
   479     in request's form parameters
       
   480 
       
   481     :param *expected: parameters (eg `basestring`) which are expected to be
       
   482                       found in request's form parameters
       
   483     """
       
   484 
       
   485     @lltrace
       
   486     def __call__(self, cls, req, *args, **kwargs):
       
   487         score = 0
       
   488         for param in self.expected:
       
   489             if not param in req.form:
       
   490                 return 0
       
   491             score += 1
       
   492         return len(self.expected)
       
   493 
       
   494 
       
   495 class match_kwargs(match_search_state):
       
   496     """accept if parameters specified as initializer arguments are specified
       
   497     in named arguments given to the selector
       
   498 
       
   499     :param *expected: parameters (eg `basestring`) which are expected to be
       
   500                       found in named arguments (kwargs)
       
   501     """
       
   502 
       
   503     @lltrace
       
   504     def __call__(self, cls, req, *args, **kwargs):
       
   505         for arg in self.expected:
       
   506             if not arg in kwargs:
       
   507                 return 0
       
   508         return len(self.expected)
       
   509 
       
   510 
       
   511 class match_user_groups(match_search_state):
       
   512     """accept if logged users is in at least one of the given groups. Returned
       
   513     score is the number of groups in which the user is.
       
   514 
       
   515     If the special 'owners' group is given:
       
   516     * if row is specified check the entity at the given row/col is owned by the
       
   517       logged user
       
   518     * if row is not specified check all entities in col are owned by the logged
       
   519       user
       
   520 
       
   521     :param *required_groups: name of groups (`basestring`) in which the logged
       
   522                              user should be
       
   523     """
       
   524 
       
   525     @lltrace
       
   526     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   527         user = req.user
       
   528         if user is None:
       
   529             return int('guests' in self.expected)
       
   530         score = user.matching_groups(self.expected)
       
   531         if not score and 'owners' in self.expected and rset:
       
   532             if row is not None:
       
   533                 if not user.owns(rset[row][col]):
       
   534                     return 0
       
   535                 score = 1
       
   536             else:
       
   537                 score = all(user.owns(r[col]) for r in rset)
       
   538         return score
       
   539 
       
   540 
       
   541 class match_transition(match_search_state):
       
   542     @lltrace
       
   543     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   544         try:
       
   545             # XXX check this is a transition that apply to the object?
       
   546             if not kwargs['transition'].name in self.expected:
       
   547                 return 0
       
   548         except KeyError:
       
   549             return 0
       
   550         return 1
       
   551 
       
   552 
       
   553 class match_view(match_search_state):
       
   554     """accept if the current view is in one of the expected vid given to the
       
   555     initializer
       
   556     """
       
   557     @lltrace
       
   558     def __call__(self, cls, req, rset=None, row=None, col=0, view=None, **kwargs):
       
   559         if view is None or not view.__regid__ in self.expected:
       
   560             return 0
       
   561         return 1
       
   562 
       
   563 
       
   564 class appobject_selectable(Selector):
       
   565     """accept with another appobject is selectable using selector's input
       
   566     context.
       
   567 
       
   568     :param registry: a registry name (`basestring`)
       
   569     :param oid: an object identifier (`basestring`)
       
   570     """
       
   571     def __init__(self, registry, oid):
       
   572         self.registry = registry
       
   573         self.oid = oid
       
   574 
       
   575     def __call__(self, cls, req, **kwargs):
       
   576         try:
       
   577             req.vreg[self.registry].select(self.oid, req, **kwargs)
       
   578             return 1
       
   579         except NoSelectableObject:
       
   580             return 0
       
   581 
       
   582 
       
   583 # not so basic selectors ######################################################
       
   584 
       
   585 class implements(ImplementsMixIn, EClassSelector):
   532 class implements(ImplementsMixIn, EClassSelector):
   586     """accept if entity classes found in the result set implements at least one
   533     """Return non-zero score for entity that are of the given type(s) or
   587     of the interfaces given as argument. Returned score is the number of
   534     implements at least one of the given interface(s). If multiple arguments are
   588     implemented interfaces.
   535     given, matching one of them is enough.
   589 
   536 
   590     See `EClassSelector` documentation for behaviour when row is not specified.
   537     Entity types should be given as string, the corresponding class will be
   591 
   538     fetched from the entity types registry at selection time.
   592     :param *expected_ifaces: expected interfaces. An interface may be a class
   539 
   593                              or an entity type (e.g. `basestring`) in which case
   540     See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
   594                              the associated class will be searched in the
   541     class lookup / score rules according to the input context.
   595                              registry (at selection time)
   542 
   596 
   543     .. note:: when interface is an entity class, the score will reflect class
   597     note: when interface is an entity class, the score will reflect class
   544               proximity so the most specific object will be selected.
   598           proximity so the most specific object'll be selected
       
   599     """
   545     """
   600     def score_class(self, eclass, req):
   546     def score_class(self, eclass, req):
   601         return self.score_interfaces(req, eclass, eclass)
   547         return self.score_interfaces(req, eclass, eclass)
   602 
   548 
   603 
   549 
   604 class specified_etype_implements(implements):
   550 class score_entity(EntitySelector):
   605     """accept if entity class specified using an 'etype' parameters in name
   551     """Return score according to an arbitrary function given as argument which
   606     argument or request form implements at least one of the interfaces given as
   552     will be called with input content entity as argument.
   607     argument. Returned score is the number of implemented interfaces.
   553 
   608 
   554     This is a very useful selector that will usually interest you since it
   609     :param *expected_ifaces: expected interfaces. An interface may be a class
   555     allows a lot of things without having to write a specific selector.
   610                              or an entity type (e.g. `basestring`) in which case
   556 
   611                              the associated class will be searched in the
   557     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
   612                              registry (at selection time)
   558     lookup / score rules according to the input context.
   613 
   559     """
   614     note: when interface is an entity class, the score will reflect class
   560     def __init__(self, scorefunc, once_is_enough=False):
   615           proximity so the most specific object'll be selected
   561         super(score_entity, self).__init__(once_is_enough)
   616     """
   562         def intscore(*args, **kwargs):
   617 
   563             score = scorefunc(*args, **kwargs)
   618     @lltrace
   564             if not score:
   619     def __call__(self, cls, req, *args, **kwargs):
       
   620         try:
       
   621             etype = kwargs['etype']
       
   622         except KeyError:
       
   623             try:
       
   624                 etype = req.form['etype']
       
   625             except KeyError:
       
   626                 return 0
   565                 return 0
   627             else:
   566             if isinstance(score, (int, long)):
   628                 # only check this is a known type if etype comes from req.form,
       
   629                 # else we want the error to propagate
       
   630                 try:
       
   631                     etype = req.vreg.case_insensitive_etypes[etype.lower()]
       
   632                     req.form['etype'] = etype
       
   633                 except KeyError:
       
   634                     return 0
       
   635         score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
       
   636         if score:
       
   637             eschema = req.vreg.schema.eschema(etype)
       
   638             if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
       
   639                 return score
   567                 return score
   640         return 0
   568             return 1
   641 
   569         self.score_entity = intscore
   642 
   570 
   643 class relation_possible(EClassSelector):
   571 
   644     """accept if entity class found in the result set support the relation.
   572 class relation_possible(EntitySelector):
   645 
   573     """Return 1 for entity that supports the relation, provided that the
   646     See `EClassSelector` documentation for behaviour when row is not specified.
   574     request's user may do some `action` on it (see below).
   647 
   575 
   648     :param rtype: a relation type (`basestring`)
   576     The relation is specified by the following initializer arguments:
   649     :param role: the role of the result set entity in the relation. 'subject' or
   577 
   650                  'object', default to 'subject'.
   578     * `rtype`, the name of the relation
   651     :param target_type: if specified, check the relation's end may be of this
   579 
   652                         target type (`basestring`)
   580     * `role`, the role of the entity in the relation, either 'subject' or
   653     :param action: a relation schema action (one of 'read', 'add', 'delete')
   581       'object', default to 'subject'
   654                    which must be granted to the logged user, else a 0 score will
   582 
   655                    be returned
   583     * `target_etype`, optional name of an entity type that should be supported
   656     """
   584       at the other end of the relation
       
   585 
       
   586     * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete',
       
   587       default to 'read') which must be granted to the user, else a 0 score will
       
   588       be returned
       
   589 
       
   590     * `strict`, boolean (default to False) telling what to do when the user has
       
   591       not globally the permission for the action (eg the action is not granted
       
   592       to one of the user's groups)
       
   593 
       
   594       - when strict is False, if there are some local role defined for this
       
   595         action (e.g. using rql expressions), then the permission will be
       
   596         considered as granted
       
   597 
       
   598       - when strict is True, then the permission will be actually checked for
       
   599         each entity
       
   600 
       
   601     Setting `strict` to True impacts performance for large result set since
       
   602     you'll then get the :class:`~cubicweb.selectors.EntitySelector` behaviour
       
   603     while otherwise you get the :class:`~cubicweb.selectors.EClassSelector`'s
       
   604     one. See those classes documentation for entity lookup / score rules
       
   605     according to the input context.
       
   606     """
       
   607 
   657     def __init__(self, rtype, role='subject', target_etype=None,
   608     def __init__(self, rtype, role='subject', target_etype=None,
   658                  action='read', once_is_enough=False):
   609                  action='read', strict=False, **kwargs):
   659         super(relation_possible, self).__init__(once_is_enough)
   610         super(relation_possible, self).__init__(**kwargs)
   660         self.rtype = rtype
   611         self.rtype = rtype
   661         self.role = role
   612         self.role = role
   662         self.target_etype = target_etype
   613         self.target_etype = target_etype
   663         self.action = action
   614         self.action = action
   664 
   615         self.strict = strict
   665     def score_class(self, eclass, req):
   616 
       
   617     # hack hack hack
       
   618     def __call__(self, cls, req, **kwargs):
       
   619         if self.strict:
       
   620             return EntitySelector.__call__(self, cls, req, **kwargs)
       
   621         return EClassSelector.__call__(self, cls, req, **kwargs)
       
   622 
       
   623     def score(self, *args):
       
   624         if self.strict:
       
   625             return EntitySelector.score(self, *args)
       
   626         return EClassSelector.score(self, *args)
       
   627 
       
   628     def _get_rschema(self, eclass):
   666         eschema = eclass.e_schema
   629         eschema = eclass.e_schema
   667         try:
   630         try:
   668             if self.role == 'object':
   631             if self.role == 'object':
   669                 rschema = eschema.objrels[self.rtype]
   632                 return eschema.objrels[self.rtype]
   670             else:
   633             else:
   671                 rschema = eschema.subjrels[self.rtype]
   634                 return eschema.subjrels[self.rtype]
   672         except KeyError:
   635         except KeyError:
   673             return 0
   636             return None
       
   637 
       
   638     def score_class(self, eclass, req):
       
   639         rschema = self._get_rschema(eclass)
       
   640         if rschema is None:
       
   641             return 0 # relation not supported
       
   642         eschema = eclass.e_schema
   674         if self.target_etype is not None:
   643         if self.target_etype is not None:
   675             try:
   644             try:
   676                 rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
   645                 rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
   677                 if not rdef.may_have_permission(self.action, req):
   646                 if not rdef.may_have_permission(self.action, req):
   678                     return 0
   647                     return 0
   680                 return 0
   649                 return 0
   681         else:
   650         else:
   682             return rschema.may_have_permission(self.action, req, eschema, self.role)
   651             return rschema.may_have_permission(self.action, req, eschema, self.role)
   683         return 1
   652         return 1
   684 
   653 
   685 
       
   686 class partial_relation_possible(PartialSelectorMixIn, relation_possible):
       
   687     """partial version of the relation_possible selector
       
   688 
       
   689     The selector will look for class attributes to find its missing
       
   690     information. The list of attributes required on the class
       
   691     for this selector are:
       
   692 
       
   693     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
       
   694 
       
   695     - `role`: this attribute will be passed to the `cubicweb.role` function
       
   696       to determine the role of class in the relation
       
   697 
       
   698     - `etype` (optional): the entity type on the other side of the relation
       
   699 
       
   700     :param action: a relation schema action (one of 'read', 'add', 'delete')
       
   701                    which must be granted to the logged user, else a 0 score will
       
   702                    be returned
       
   703     """
       
   704     def __init__(self, action='read', once_is_enough=False):
       
   705         super(partial_relation_possible, self).__init__(None, None, None,
       
   706                                                         action, once_is_enough)
       
   707 
       
   708     def complete(self, cls):
       
   709         self.rtype = cls.rtype
       
   710         self.role = role(cls)
       
   711         self.target_etype = getattr(cls, 'etype', None)
       
   712 
       
   713 
       
   714 class may_add_relation(EntitySelector):
       
   715     """accept if the relation can be added to an entity found in the result set
       
   716     by the logged user.
       
   717 
       
   718     See `EntitySelector` documentation for behaviour when row is not specified.
       
   719 
       
   720     :param rtype: a relation type (`basestring`)
       
   721     :param role: the role of the result set entity in the relation. 'subject' or
       
   722                  'object', default to 'subject'.
       
   723     """
       
   724 
       
   725     def __init__(self, rtype, role='subject', target_etype=None,
       
   726                  once_is_enough=False):
       
   727         super(may_add_relation, self).__init__(once_is_enough)
       
   728         self.rtype = rtype
       
   729         self.role = role
       
   730         self.target_etype = target_etype
       
   731 
       
   732     def score_entity(self, entity):
   654     def score_entity(self, entity):
   733         rschema = entity._cw.vreg.schema.rschema(self.rtype)
   655         rschema = self._get_rschema(entity)
       
   656         if rschema is None:
       
   657             return 0 # relation not supported
   734         if self.target_etype is not None:
   658         if self.target_etype is not None:
   735             rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
   659             rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
   736         if self.role == 'subject':
   660         if self.role == 'subject':
   737             if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid):
   661             if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid):
   738                 return 0
   662                 return 0
   739         elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid):
   663         elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid):
   740             return 0
   664             return 0
   741         return 1
   665         return 1
   742 
   666 
   743 
   667 
   744 class partial_may_add_relation(PartialSelectorMixIn, may_add_relation):
   668 class partial_relation_possible(PartialSelectorMixIn, relation_possible):
   745     """partial version of the may_add_relation selector
   669     """Same as :class:~`cubicweb.selectors.relation_possible`, but will look for
   746 
   670     attributes of the selected class to get information which is otherwise
   747     The selector will look for class attributes to find its missing
   671     expected by the initializer, except for `action` and `strict` which are kept
   748     information. The list of attributes required on the class
   672     as initializer arguments.
   749     for this selector are:
   673 
   750 
   674     This is useful to predefine selector of an abstract class designed to be
   751     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   675     customized.
   752 
   676     """
   753     - `role`: this attribute will be passed to the `cubicweb.role` function
   677     def __init__(self, action='read', **kwargs):
   754       to determine the role of class in the relation.
   678         super(partial_relation_possible, self).__init__(None, None, None,
   755 
   679                                                         action, **kwargs)
   756     :param action: a relation schema action (one of 'read', 'add', 'delete')
       
   757                    which must be granted to the logged user, else a 0 score will
       
   758                    be returned
       
   759     """
       
   760     def __init__(self, once_is_enough=False):
       
   761         super(partial_may_add_relation, self).__init__(None, once_is_enough=once_is_enough)
       
   762 
   680 
   763     def complete(self, cls):
   681     def complete(self, cls):
   764         self.rtype = cls.rtype
   682         self.rtype = cls.rtype
   765         self.role = role(cls)
   683         self.role = role(cls)
   766         self.target_etype = getattr(cls, 'etype', None)
   684         self.target_etype = getattr(cls, 'etype', None)
       
   685         if self.target_etype is not None:
       
   686             warn('[3.6] please rename etype to target_etype on %s' % cls,
       
   687                  DeprecationWarning)
       
   688         else:
       
   689             self.target_etype = getattr(cls, 'target_etype', None)
   767 
   690 
   768 
   691 
   769 class has_related_entities(EntitySelector):
   692 class has_related_entities(EntitySelector):
   770     """accept if entity found in the result set has some linked entities using
   693     """Return 1 if entity support the specified relation and has some linked
   771     the specified relation (optionaly filtered according to the specified target
   694     entities by this relation , optionaly filtered according to the specified
   772     type). Checks first if the relation is possible.
   695     target type.
   773 
   696 
   774     See `EntitySelector` documentation for behaviour when row is not specified.
   697     The relation is specified by the following initializer arguments:
   775 
   698 
   776     :param rtype: a relation type (`basestring`)
   699     * `rtype`, the name of the relation
   777     :param role: the role of the result set entity in the relation. 'subject' or
   700 
   778                  'object', default to 'subject'.
   701     * `role`, the role of the entity in the relation, either 'subject' or
   779     :param target_type: if specified, check the relation's end may be of this
   702       'object', default to 'subject'.
   780                         target type (`basestring`)
   703 
   781     """
   704     * `target_etype`, optional name of an entity type that should be found
   782     def __init__(self, rtype, role='subject', target_etype=None,
   705       at the other end of the relation
   783                  once_is_enough=False):
   706 
   784         super(has_related_entities, self).__init__(once_is_enough)
   707     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
       
   708     lookup / score rules according to the input context.
       
   709     """
       
   710     def __init__(self, rtype, role='subject', target_etype=None, **kwargs):
       
   711         super(has_related_entities, self).__init__(**kwargs)
   785         self.rtype = rtype
   712         self.rtype = rtype
   786         self.role = role
   713         self.role = role
   787         self.target_etype = target_etype
   714         self.target_etype = target_etype
   788 
   715 
   789     def score_entity(self, entity):
   716     def score_entity(self, entity):
   795             return any(r for r in rset.description if r[0] == self.target_etype)
   722             return any(r for r in rset.description if r[0] == self.target_etype)
   796         return rset and 1 or 0
   723         return rset and 1 or 0
   797 
   724 
   798 
   725 
   799 class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
   726 class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
   800     """partial version of the has_related_entities selector
   727     """Same as :class:~`cubicweb.selectors.has_related_entity`, but will look
   801 
   728     for attributes of the selected class to get information which is otherwise
   802     The selector will look for class attributes to find its missing
   729     expected by the initializer.
   803     information. The list of attributes required on the class
   730 
   804     for this selector are:
   731     This is useful to predefine selector of an abstract class designed to be
   805 
   732     customized.
   806     - `rtype`: same as `rtype` parameter of the `relation_possible` selector
   733     """
   807 
   734     def __init__(self, **kwargs):
   808     - `role`: this attribute will be passed to the `cubicweb.role` function
   735         super(partial_has_related_entities, self).__init__(None, None, None,
   809       to determine the role of class in the relation.
   736                                                            **kwargs)
   810 
   737 
   811     - `etype` (optional): the entity type on the other side of the relation
       
   812 
       
   813     :param action: a relation schema action (one of 'read', 'add', 'delete')
       
   814                    which must be granted to the logged user, else a 0 score will
       
   815                    be returned
       
   816     """
       
   817     def __init__(self, once_is_enough=False):
       
   818         super(partial_has_related_entities, self).__init__(None, None,
       
   819                                                            None, once_is_enough)
       
   820     def complete(self, cls):
   738     def complete(self, cls):
   821         self.rtype = cls.rtype
   739         self.rtype = cls.rtype
   822         self.role = role(cls)
   740         self.role = role(cls)
   823         self.target_etype = getattr(cls, 'etype', None)
   741         self.target_etype = getattr(cls, 'etype', None)
       
   742         if self.target_etype is not None:
       
   743             warn('[3.6] please rename etype to target_etype on %s' % cls,
       
   744                  DeprecationWarning)
       
   745         else:
       
   746             self.target_etype = getattr(cls, 'target_etype', None)
   824 
   747 
   825 
   748 
   826 class has_permission(EntitySelector):
   749 class has_permission(EntitySelector):
   827     """accept if user has the permission to do the requested action on a result
   750     """Return non-zero score if request's user has the permission to do the
   828     set entity.
   751     requested action on the entity. `action` is an entity schema action (eg one
   829 
   752     of 'read', 'add', 'delete', 'update').
   830     * if row is specified, return 1 if user has the permission on the entity
   753 
   831       instance found in the specified cell
   754     Here are entity lookup / scoring rules:
   832     * else return a positive score if user has the permission for every entity
   755 
   833       in the found in the specified column
   756     * if `entity` is specified, check permission is granted for this entity
   834 
   757 
   835     note: None values (resulting from some outer join in the query) are not
   758     * elif `row` is specified, check permission is granted for the entity found
   836           considered.
   759       in the specified cell
   837 
   760 
   838     :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
   761     * else check permission is granted for each entity found in the column
   839     """
   762       specified specified by the `col` argument or in column 0
   840     def __init__(self, action, once_is_enough=False):
   763     """
   841         super(has_permission, self).__init__(once_is_enough)
   764     def __init__(self, action):
   842         self.action = action
   765         self.action = action
   843 
   766 
       
   767     # don't use EntitySelector.__call__ but this optimized implementation to
       
   768     # avoid considering each entity when it's not necessary
   844     @lltrace
   769     @lltrace
   845     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
   770     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   771         if kwargs.get('entity'):
       
   772             return self.score_entity(kwargs['entity'])
   846         if rset is None:
   773         if rset is None:
   847             return 0
   774             return 0
   848         user = req.user
   775         user = req.user
   849         action = self.action
   776         action = self.action
   850         if row is None:
   777         if row is None:
   880             return 1
   807             return 1
   881         return 0
   808         return 0
   882 
   809 
   883 
   810 
   884 class has_add_permission(EClassSelector):
   811 class has_add_permission(EClassSelector):
   885     """accept if logged user has the add permission on entity class found in the
   812     """Return 1 if request's user has the add permission on entity type
   886     result set, and class is not a strict subobject.
   813     specified in the `etype` initializer argument, or according to entity found
   887 
   814     in the input content if not specified.
   888     See `EClassSelector` documentation for behaviour when row is not specified.
   815 
   889     """
   816     It also check that then entity type is not a strict subobject (e.g. may only
   890     def score(self, cls, req, etype):
   817     be used as a composed of another entity).
   891         eschema = req.vreg.schema.eschema(etype)
   818 
   892         if not (eschema.final or eschema.is_subobject(strict=True)) \
   819     See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
   893                and eschema.has_perm(req, 'add'):
   820     class lookup / score rules according to the input context when `etype` is
   894             return 1
   821     not specified.
   895         return 0
   822     """
       
   823     def __init__(self, etype=None, **kwargs):
       
   824         super(has_add_permission, self).__init__(**kwargs)
       
   825         self.etype = etype
       
   826 
       
   827     @lltrace
       
   828     def __call__(self, cls, req, **kwargs):
       
   829         if self.etype is None:
       
   830             return super(has_add_permission, self).__call__(cls, req, **kwargs)
       
   831         return self.score(cls, req, self.etype)
       
   832 
       
   833     def score_class(self, eclass, req):
       
   834         eschema = eclass.e_schema
       
   835         if eschema.final or eschema.is_subobject(strict=True) \
       
   836                or not eschema.has_perm(req, 'add'):
       
   837             return 0
       
   838         return 1
   896 
   839 
   897 
   840 
   898 class rql_condition(EntitySelector):
   841 class rql_condition(EntitySelector):
   899     """accept if an arbitrary rql return some results for an eid found in the
   842     """Return non-zero score if arbitrary rql specified in `expression`
   900     result set. Returned score is the number of items returned by the rql
   843     initializer argument return some results for entity found in the input
       
   844     context. Returned score is the number of items returned by the rql
   901     condition.
   845     condition.
   902 
   846 
   903     See `EntitySelector` documentation for behaviour when row is not specified.
   847     `expression` is expected to be a string containing an rql expression, which
   904 
   848     must use 'X' variable to represent the context entity and may use 'U' to
   905     :param expression: basestring containing an rql expression, which should use
   849     represent the request's user.
   906                        X variable to represent the context entity and may use U
   850 
   907                        to represent the logged user
   851     See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
   908 
   852     lookup / score rules according to the input context.
   909     return the sum of the number of items returned by the rql condition as score
       
   910     or 0 at the first entity scoring to zero.
       
   911     """
   853     """
   912     def __init__(self, expression, once_is_enough=False):
   854     def __init__(self, expression, once_is_enough=False):
   913         super(rql_condition, self).__init__(once_is_enough)
   855         super(rql_condition, self).__init__(once_is_enough)
   914         if 'U' in frozenset(split_expression(expression)):
   856         if 'U' in frozenset(split_expression(expression)):
   915             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
   857             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
   916         else:
   858         else:
   917             rql = 'Any X WHERE X eid %%(x)s, %s' % expression
   859             rql = 'Any X WHERE X eid %%(x)s, %s' % expression
   918         self.rql = rql
   860         self.rql = rql
   919 
   861 
       
   862     def __repr__(self):
       
   863         return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
       
   864 
   920     def score(self, req, rset, row, col):
   865     def score(self, req, rset, row, col):
   921         try:
   866         try:
   922             return len(req.execute(self.rql, {'x': rset[row][col],
   867             return len(req.execute(self.rql, {'x': rset[row][col],
   923                                               'u': req.user.eid}, 'x'))
   868                                               'u': req.user.eid}, 'x'))
   924         except Unauthorized:
   869         except Unauthorized:
   925             return 0
   870             return 0
   926 
   871 
   927     def __repr__(self):
   872 # logged user selectors ########################################################
   928         return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
   873 
   929 
   874 @objectify_selector
   930 
   875 @lltrace
   931 class score_entity(EntitySelector):
   876 def authenticated_user(cls, req, **kwargs):
   932     """accept if some arbitrary function return a positive score for an entity
   877     """Return 1 if the user is authenticated (e.g. not the anonymous user).
   933     found in the result set.
   878 
   934 
   879     May only be used on the web side, not on the data repository side.
   935     See `EntitySelector` documentation for behaviour when row is not specified.
   880     """
   936 
   881     if req.cnx.anonymous_connection:
   937     :param scorefunc: callable expected to take an entity as argument and to
   882         return 0
   938                       return a score >= 0
   883     return 1
   939     """
   884 
   940     def __init__(self, scorefunc, once_is_enough=False):
   885 
   941         super(score_entity, self).__init__(once_is_enough)
   886 # XXX == ~ authenticated_user()
   942         def intscore(*args, **kwargs):
   887 def anonymous_user():
   943             score = scorefunc(*args, **kwargs)
   888     """Return 1 if the user is not authenticated (e.g. is the anonymous user).
   944             if not score:
   889 
       
   890     May only be used on the web side, not on the data repository side.
       
   891     """
       
   892     return ~ authenticated_user()
       
   893 
       
   894 
       
   895 class match_user_groups(ExpectedValueSelector):
       
   896     """Return a non-zero score if request's user is in at least one of the
       
   897     groups given as initializer argument. Returned score is the number of groups
       
   898     in which the user is.
       
   899 
       
   900     If the special 'owners' group is given and `rset` is specified in the input
       
   901     context:
       
   902 
       
   903     * if `row` is specified check the entity at the given `row`/`col` (default
       
   904       to 0) is owned by the user
       
   905 
       
   906     * else check all entities in `col` (default to 0) are owned by the user
       
   907     """
       
   908 
       
   909     @lltrace
       
   910     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   911         user = req.user
       
   912         if user is None:
       
   913             return int('guests' in self.expected)
       
   914         score = user.matching_groups(self.expected)
       
   915         if not score and 'owners' in self.expected and rset:
       
   916             if row is not None:
       
   917                 if not user.owns(rset[row][col]):
       
   918                     return 0
       
   919                 score = 1
       
   920             else:
       
   921                 score = all(user.owns(r[col]) for r in rset)
       
   922         return score
       
   923 
       
   924 
       
   925 # Web request selectors ########################################################
       
   926 
       
   927 @objectify_selector
       
   928 @lltrace
       
   929 def primary_view(cls, req, view=None, **kwargs):
       
   930     """Return 1 if:
       
   931 
       
   932     * *no view is specified* in the input context
       
   933 
       
   934     * a view is specified and its `.is_primary()` method return True
       
   935 
       
   936     This selector is usually used by contextual components that only want to
       
   937     appears for the primary view of an entity.
       
   938     """
       
   939     if view is not None and not view.is_primary():
       
   940         return 0
       
   941     return 1
       
   942 
       
   943 
       
   944 class match_view(ExpectedValueSelector):
       
   945     """Return 1 if a view is specified an as its registry id is in one of the
       
   946     expected view id given to the initializer.
       
   947     """
       
   948     @lltrace
       
   949     def __call__(self, cls, req, view=None, **kwargs):
       
   950         if view is None or not view.__regid__ in self.expected:
       
   951             return 0
       
   952         return 1
       
   953 
       
   954 
       
   955 @objectify_selector
       
   956 @lltrace
       
   957 def match_context_prop(cls, req, context=None, **kwargs):
       
   958     """Return 1 if:
       
   959 
       
   960     * no `context` is specified in input context (take care to confusion, here
       
   961       `context` refers to a string given as an argument to the input context...)
       
   962 
       
   963     * specified `context` is matching the context property value for the
       
   964       appobject using this selector
       
   965 
       
   966     * the appobject's context property value is None
       
   967 
       
   968     This selector is usually used by contextual components that want to appears
       
   969     in a configurable place.
       
   970     """
       
   971     if context is None:
       
   972         return 0
       
   973     propval = req.property_value('%s.%s.context' % (cls.__registry__,
       
   974                                                     cls.__regid__))
       
   975     if not propval:
       
   976         propval = cls.context
       
   977     if propval and context != propval:
       
   978         return 0
       
   979     return 1
       
   980 
       
   981 
       
   982 class match_search_state(ExpectedValueSelector):
       
   983     """Return 1 if the current request search state is in one of the expected
       
   984     states given to the initializer.
       
   985 
       
   986     Known search states are either 'normal' or 'linksearch' (eg searching for an
       
   987     object to create a relation with another).
       
   988 
       
   989     This selector is usually used by action that want to appears or not according
       
   990     to the ui search state.
       
   991     """
       
   992 
       
   993     @lltrace
       
   994     def __call__(self, cls, req, **kwargs):
       
   995         try:
       
   996             if not req.search_state[0] in self.expected:
   945                 return 0
   997                 return 0
   946             if isinstance(score, (int, long)):
   998         except AttributeError:
       
   999             return 1 # class doesn't care about search state, accept it
       
  1000         return 1
       
  1001 
       
  1002 
       
  1003 class match_form_params(ExpectedValueSelector):
       
  1004     """Return non-zero score if parameter names specified as initializer
       
  1005     arguments are specified in request's form parameters. When multiple
       
  1006     parameters are specified, all of them should be found in req.form. Return a
       
  1007     score corresponding to the number of expected parameters.
       
  1008     """
       
  1009 
       
  1010     @lltrace
       
  1011     def __call__(self, cls, req, **kwargs):
       
  1012         for param in self.expected:
       
  1013             if not param in req.form:
       
  1014                 return 0
       
  1015         return len(self.expected)
       
  1016 
       
  1017 
       
  1018 class specified_etype_implements(implements):
       
  1019     """Return non-zero score if the entity type specified by an 'etype' key
       
  1020     searched in (by priority) input context kwargs and request form parameters
       
  1021     match a known entity type (case insensitivly), and it's associated entity
       
  1022     class is of one of the type(s) given to the initializer or implements at
       
  1023     least one of the given interfaces. If multiple arguments are given, matching
       
  1024     one of them is enough.
       
  1025 
       
  1026     Entity types should be given as string, the corresponding class will be
       
  1027     fetched from the entity types registry at selection time.
       
  1028 
       
  1029     .. note:: when interface is an entity class, the score will reflect class
       
  1030               proximity so the most specific object will be selected.
       
  1031 
       
  1032     This selector is usually used by views holding entity creation forms (since
       
  1033     we've no result set to work on).
       
  1034     """
       
  1035 
       
  1036     @lltrace
       
  1037     def __call__(self, cls, req, **kwargs):
       
  1038         try:
       
  1039             etype = kwargs['etype']
       
  1040         except KeyError:
       
  1041             try:
       
  1042                 etype = req.form['etype']
       
  1043             except KeyError:
       
  1044                 return 0
       
  1045             else:
       
  1046                 # only check this is a known type if etype comes from req.form,
       
  1047                 # else we want the error to propagate
       
  1048                 try:
       
  1049                     etype = req.vreg.case_insensitive_etypes[etype.lower()]
       
  1050                     req.form['etype'] = etype
       
  1051                 except KeyError:
       
  1052                     return 0
       
  1053         score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
       
  1054         if score:
       
  1055             eschema = req.vreg.schema.eschema(etype)
       
  1056             if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
   947                 return score
  1057                 return score
       
  1058         return 0
       
  1059 
       
  1060 
       
  1061 # Other selectors ##############################################################
       
  1062 
       
  1063 
       
  1064 class match_transition(ExpectedValueSelector):
       
  1065     """Return 1 if a `transition` argument is found in the input context which
       
  1066     has a `.name` attribute matching one of the expected names given to the
       
  1067     initializer.
       
  1068     """
       
  1069     @lltrace
       
  1070     def __call__(self, cls, req, transition=None, **kwargs):
       
  1071         # XXX check this is a transition that apply to the object?
       
  1072         if transition is not None and getattr(transition, 'name', None) in self.expected:
   948             return 1
  1073             return 1
   949         self.score_entity = intscore
  1074         return 0
       
  1075 
   950 
  1076 
   951 ## deprecated stuff ############################################################
  1077 ## deprecated stuff ############################################################
   952 
  1078 
   953 entity_implements = class_renamed('entity_implements', implements)
  1079 entity_implements = class_renamed('entity_implements', implements)
   954 
  1080