common/selectors.py
branchtls-sprint
changeset 631 99f5852f8604
parent 593 6f6549780096
child 633 087e3f1e87c8
equal deleted inserted replaced
630:66ff0b2f7d03 631:99f5852f8604
    41 
    41 
    42 import logging
    42 import logging
    43 
    43 
    44 from logilab.common.compat import all
    44 from logilab.common.compat import all
    45 from logilab.common.deprecation import deprecated_function
    45 from logilab.common.deprecation import deprecated_function
       
    46 from logilab.common.interface import implements as implements_iface
       
    47 
       
    48 from yams import BASE_TYPES
    46 
    49 
    47 from cubicweb import Unauthorized, NoSelectableObject, role
    50 from cubicweb import Unauthorized, NoSelectableObject, role
       
    51 from cubicweb.vregistry import NoSelectableObject, Selector, chainall, chainfirst
    48 from cubicweb.cwvreg import DummyCursorError
    52 from cubicweb.cwvreg import DummyCursorError
    49 from cubicweb.vregistry import chainall, chainfirst, NoSelectableObject
       
    50 from cubicweb.cwconfig import CubicWebConfiguration
    53 from cubicweb.cwconfig import CubicWebConfiguration
    51 from cubicweb.schema import split_expression
    54 from cubicweb.schema import split_expression
    52 
    55 
    53 # helpers for debugging selectors
    56 # helpers for debugging selectors
    54 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
    57 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
    57 def lltrace(selector):
    60 def lltrace(selector):
    58     # don't wrap selectors if not in development mode
    61     # don't wrap selectors if not in development mode
    59     if CubicWebConfiguration.mode == 'installed':
    62     if CubicWebConfiguration.mode == 'installed':
    60         return selector
    63         return selector
    61     def traced(cls, *args, **kwargs):
    64     def traced(cls, *args, **kwargs):
       
    65         if isinstance(cls, Selector):
       
    66             selname = cls.__class__.__name__
       
    67             oid = args[0].id
       
    68         else:
       
    69             selname = selector.__name__
       
    70             oid = cls.id
    62         ret = selector(cls, *args, **kwargs)
    71         ret = selector(cls, *args, **kwargs)
    63         if TRACED_OIDS == 'all' or cls.id in TRACED_OIDS:
    72         if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
    64             SELECTOR_LOGGER.warning('selector %s returned %s for %s', selector.__name__, ret, cls)
    73             SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
    65         return ret
    74         return ret
    66     traced.__name__ = selector.__name__
    75     traced.__name__ = selector.__name__
    67     return traced
    76     return traced
    68 
    77 
    69 class traced_selection(object):
    78 class traced_selection(object):
   172         return 0
   181         return 0
   173     return 1
   182     return 1
   174 largerset_selector = deprecated_function(paginated_rset)
   183 largerset_selector = deprecated_function(paginated_rset)
   175 
   184 
   176 @lltrace
   185 @lltrace
   177 def sorted_rset(cls, req, rset, row=None, col=None, **kwargs):
   186 def sorted_rset(cls, req, rset, row=None, col=0, **kwargs):
   178     """accept sorted result set"""
   187     """accept sorted result set"""
   179     rqlst = rset.syntax_tree()
   188     rqlst = rset.syntax_tree()
   180     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
   189     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
   181         return 0
   190         return 0
   182     return 2
   191     return 2
   200         if len(etypes) > 1:
   209         if len(etypes) > 1:
   201             return 1
   210             return 1
   202     return 0
   211     return 0
   203 multitype_selector = deprecated_function(two_etypes_rset)
   212 multitype_selector = deprecated_function(two_etypes_rset)
   204 
   213 
   205 @lltrace
   214 
   206 def match_search_state(cls, req, rset, row=None, col=None, **kwargs):
   215 class match_search_state(Selector):
   207     """checks if the current search state is in a .search_states attribute of
   216     def __init__(self, *expected_states):
   208     the wrapped class
   217         self.expected_states = expected_states
   209 
   218         
   210     search state should be either 'normal' or 'linksearch' (eg searching for an
   219     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   211     object to create a relation with another)
   220         """checks if the current request search state is in one of the expected states
   212     """
   221         the wrapped class
   213     try:
   222 
   214         if not req.search_state[0] in cls.search_states:
   223         search state should be either 'normal' or 'linksearch' (eg searching for an
   215             return 0
   224         object to create a relation with another)
   216     except AttributeError:
   225         """
   217         return 1 # class doesn't care about search state, accept it
   226         try:
   218     return 1
   227             if not req.search_state[0] in cls.search_states:
   219 searchstate_selector = deprecated_function(match_search_state)
   228                 return 0
       
   229         except AttributeError:
       
   230             return 1 # class doesn't care about search state, accept it
       
   231         return 1
   220 
   232 
   221 @lltrace
   233 @lltrace
   222 def anonymous_user(cls, req, *args, **kwargs):
   234 def anonymous_user(cls, req, *args, **kwargs):
   223     """accept if user is anonymous"""
   235     """accept if user is anonymous"""
   224     if req.cnx.anonymous_connection:
   236     if req.cnx.anonymous_connection:
   256         if not arg in kwargs:
   268         if not arg in kwargs:
   257             return 0
   269             return 0
   258     return 1
   270     return 1
   259 kwargs_selector = deprecated_function(match_kwargs)
   271 kwargs_selector = deprecated_function(match_kwargs)
   260 
   272 
       
   273 # abstract selectors ##########################################################
       
   274 
       
   275 class EClassSelector(Selector):
       
   276     """abstract class for selectors working on the entity classes of the result
       
   277     set
       
   278     """
       
   279     once_is_enough = False
       
   280     
       
   281     @lltrace
       
   282     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
       
   283         if not rset:
       
   284             return 0
       
   285         score = 0
       
   286         if row is None:
       
   287             for etype in rset.column_types(col):
       
   288                 if etype is None: # outer join
       
   289                     continue
       
   290                 if etype in BASE_TYPES:
       
   291                     return 0
       
   292                 escore = self.score_class(cls.vreg.etype_class(etype), req)
       
   293                 if not escore:
       
   294                     return 0
       
   295                 elif self.once_is_enough:
       
   296                     return escore
       
   297                 score += escore
       
   298         else:
       
   299             etype = rset.description[row][col]
       
   300             if etype is not None and not etype in BASE_TYPES:
       
   301                 score = self.score_class(cls.vreg.etype_class(etype), req)
       
   302         return score and (score + 1)
       
   303 
       
   304     def score_class(self, eclass, req):
       
   305         raise NotImplementedError()
       
   306 
       
   307 
       
   308 class EntitySelector(Selector):
       
   309     """abstract class for selectors working on the entity instances of the
       
   310     result set
       
   311     """
       
   312     @lltrace
       
   313     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
       
   314         if not rset:
       
   315             return 0
       
   316         score = 0
       
   317         if row is None:
       
   318             for row, rowvalue in enumerate(rset.rows):
       
   319                 if rowvalue[col] is None: # outer join
       
   320                     continue
       
   321                 try:
       
   322                     escore = self.score_entity(rset.get_entity(row, col))
       
   323                 except NotAnEntity:
       
   324                     return 0
       
   325                 if not escore:
       
   326                     return 0
       
   327                 score += escore
       
   328         else:
       
   329             etype = rset.description[row][col]
       
   330             if etype is not None: # outer join
       
   331                 try:
       
   332                     score = self.score_entity(rset.get_entity(row, col))
       
   333                 except NotAnEntity:
       
   334                     return 0
       
   335         return score and (score + 1)
       
   336 
       
   337     def score_entity(self, entity):
       
   338         raise NotImplementedError()
   261 
   339 
   262 # not so basic selectors ######################################################
   340 # not so basic selectors ######################################################
       
   341 
       
   342 class implements(EClassSelector):
       
   343     """initializer takes a list of interfaces or entity types as argument
       
   344     
       
   345     * if row is None, return the number of implemented interfaces for each
       
   346       entity's class in the result set at the specified column (or column 0).
       
   347       If any class has no matching interface, return 0.
       
   348     * if row is specified, return number of implemented interfaces by the
       
   349       entity's class at this row (and column)
       
   350 
       
   351     if some interface is an entity class, the score will reflect class
       
   352     proximity so the most specific object'll be selected
       
   353     """
       
   354 
       
   355     def __init__(self, *expected_ifaces):
       
   356         self.expected_ifaces = expected_ifaces
       
   357 
       
   358     def score_class(self, eclass, req):
       
   359         score = 0
       
   360         for iface in self.expected_ifaces:
       
   361             if isinstance(iface, basestring):
       
   362                 # entity type
       
   363                 iface = eclass.vreg.etype_class(iface)
       
   364             if implements_iface(eclass, iface):
       
   365                 score += 1
       
   366                 if getattr(iface, '__registry__', None) == 'etypes':
       
   367                     # adjust score if the interface is an entity class
       
   368                     if iface is eclass:
       
   369                         score += len(eclass.e_schema.ancestors()) + 1
       
   370                     else:
       
   371                         parents = [e.type for e in eclass.e_schema.ancestors()]
       
   372                         for index, etype in enumerate(reversed(parents)):
       
   373                             basecls = eclass.vreg.etype_class(etype)
       
   374                             if iface is basecls:
       
   375                                 score += index + 1
       
   376                                 break
       
   377         return score
       
   378 
       
   379 
       
   380 class relation_possible(EClassSelector):
       
   381     """initializer takes relation name as argument and an optional role (default
       
   382       as subject) and target type (default to unspecified)
       
   383       
       
   384     * if row is None, return 1 if every entity's class in the result set at the
       
   385       specified column (or column 0) may have this relation (as role). If target
       
   386       type is specified, check the relation's end may be of this target type.
       
   387       
       
   388     * if row is specified, check relation is supported by the entity's class at
       
   389       this row (and column)
       
   390     """
       
   391     def __init__(self, rtype, role='subject', target_etype=None,
       
   392                  permission='read', once_is_enough=False):
       
   393         self.rtype = rtype
       
   394         self.role = role
       
   395         self.target_etype = target_etype
       
   396         self.permission = permission
       
   397         self.once_is_enough = once_is_enough
       
   398 
       
   399     @lltrace
       
   400     def __call__(self, cls, *args, **kwargs):
       
   401         rschema = cls.schema.rschema(self.rtype)
       
   402         if not (rschema.has_perm(req, self.permission)
       
   403                 or rschema.has_local_role(self.permission)):
       
   404             return 0
       
   405         return super(relation_possible, self)(cls, *args, **kwargs)
       
   406         
       
   407     def score_class(self, eclass, req):
       
   408         eschema = eclass.e_schema
       
   409         try:
       
   410             if self.role == 'object':
       
   411                 rschema = eschema.object_relation(self.rtype)
       
   412             else:
       
   413                 rschema = eschema.subject_relation(self.rtype)
       
   414         except KeyError:
       
   415             return 0
       
   416         if self.target_etype is not None:
       
   417             try:
       
   418                 if self.role == 'object':
       
   419                     return self.target_etype in rschema.objects(eschema)
       
   420                 else:
       
   421                     return self.target_etype in rschema.subjects(eschema)
       
   422             except KeyError, ex:
       
   423                 return 0
       
   424         return 1
       
   425 
       
   426 
       
   427 class non_final_entity(EClassSelector):
       
   428     """initializer takes no argument
       
   429 
       
   430     * if row is None, return 1 if there are only non final entity's class in the
       
   431       result set at the specified column (or column 0)
       
   432     * if row is specified, return 1 if entity's class at this row (and column)
       
   433       isn't final
       
   434     """
       
   435     def score_class(self, eclass, req):
       
   436         return int(not eclass.e_schema.is_final())
       
   437 
       
   438 
       
   439 class match_user_groups(Selector):
       
   440     """initializer takes users group as argument
       
   441 
       
   442     * check logged user is in one of the given groups. If special 'owners' group
       
   443       given:
       
   444       - if row is specified check the entity at the given row/col is owned by
       
   445         the logged user
       
   446       - if row is not specified check all entities in col are owned by the
       
   447         logged user
       
   448     """
       
   449     
       
   450     def __init__(self, *required_groups):
       
   451         self.required_groups = required_groups
       
   452     
       
   453     @lltrace
       
   454     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   455         user = req.user
       
   456         if user is None:
       
   457             return int('guests' in self.require_groups)
       
   458         score = user.matching_groups(self.require_groups)
       
   459         if not score and 'owners' in self.require_groups and rset:
       
   460             nbowned = 0
       
   461             if row is not None:
       
   462                 if not user.owns(rset[row][col]):
       
   463                     return 0
       
   464                 score = 1
       
   465             else:
       
   466                 score = all(user.owns(r[col or 0]) for r in rset)
       
   467         return 0
       
   468 
       
   469 
       
   470 class has_editable_relation(EntitySelector):
       
   471     """initializer takes no argument
       
   472 
       
   473     * if row is specified check the entity at the given row/col has some
       
   474       relation editable by the logged user
       
   475     * if row is not specified check all entities in col are owned have some
       
   476       relation editable by the logged userlogged user
       
   477     """
       
   478         
       
   479     def score_entity(self, entity):
       
   480         # if user has no update right but it can modify some relation,
       
   481         # display action anyway
       
   482         for dummy in entity.srelations_by_category(('generic', 'metadata'),
       
   483                                                    'add'):
       
   484             return 1
       
   485         for rschema, targetschemas, role in entity.relations_by_category(
       
   486             ('primary', 'secondary'), 'add'):
       
   487             if not rschema.is_final():
       
   488                 return 1
       
   489         return 0
       
   490 
       
   491 
       
   492 class may_add_relation(EntitySelector):
       
   493     """initializer a relation type and optional role (default to 'subject') as
       
   494     argument
       
   495 
       
   496     if row is specified check the relation may be added to the entity at the
       
   497     given row/col (if row specified) or to every entities in the given col (if
       
   498     row is not specified)
       
   499     """
       
   500     
       
   501     def __init__(self, rtype, role='subject'):
       
   502         self.rtype = rtype
       
   503         self.role = role
       
   504         
       
   505     def score_entity(self, entity):
       
   506         rschema = entity.schema.rschema(self.rtype)
       
   507         if self.role == 'subject':
       
   508             if not rschema.has_perm(req, 'add', fromeid=entity.eid):
       
   509                 return False
       
   510         elif not rschema.has_perm(req, 'add', toeid=entity.eid):
       
   511             return False
       
   512         return True
       
   513 
       
   514         
       
   515 class has_permission(EntitySelector):
       
   516     """initializer takes a schema action (eg 'read'/'add'/'delete'/'update') as
       
   517     argument
       
   518 
       
   519     * if row is specified check user has permission to do the requested action
       
   520       on the entity at the given row/col
       
   521     * if row is specified check user has permission to do the requested action
       
   522       on all entities in the given col
       
   523     """
       
   524     def __init__(self, schema_action):
       
   525         self.schema_action = schema_action
       
   526         
       
   527     @lltrace
       
   528     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
       
   529         user = req.user
       
   530         action = self.schema_action
       
   531         if row is None:
       
   532             score = 0
       
   533             need_local_check = [] 
       
   534             geteschema = cls.schema.eschema
       
   535             for etype in rset.column_types(0):
       
   536                 if etype in BASE_TYPES:
       
   537                     return 0
       
   538                 eschema = geteschema(etype)
       
   539                 if not user.matching_groups(eschema.get_groups(action)):
       
   540                     if eschema.has_local_role(action):
       
   541                         # have to ckeck local roles
       
   542                         need_local_check.append(eschema)
       
   543                         continue
       
   544                     else:
       
   545                         # even a local role won't be enough
       
   546                         return 0
       
   547                 score += accepted
       
   548             if need_local_check:
       
   549                 # check local role for entities of necessary types
       
   550                 for i, row in enumerate(rset):
       
   551                     if not rset.description[i][0] in need_local_check:
       
   552                         continue
       
   553                     if not self.score_entity(rset.get_entity(i, col)):
       
   554                         return 0
       
   555                     score += 1
       
   556             return score
       
   557         if rset.description[row][col] in BASE_TYPES:
       
   558             return 0
       
   559         return self.score_entity(rset.get_entity(row, col))
       
   560     
       
   561     def score_entity(self, entity):
       
   562         if entity.has_perm(self.schema_action):
       
   563             return 1
       
   564         return 0
       
   565 
       
   566 
       
   567 class has_add_permission(EClassSelector):
       
   568     
       
   569     def score_class(self, eclass, req):
       
   570         eschema = eclass.e_schema
       
   571         if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
       
   572                and eschema.has_perm(req, 'add'):
       
   573             return 1
       
   574         return 0
       
   575 
       
   576         
       
   577 class score_entity(EntitySelector):
       
   578     def __init__(self, scorefunc):
       
   579         self.score_entity = scorefunc
       
   580 
       
   581 # XXX not so basic selectors ######################################################
   263 
   582 
   264 @lltrace
   583 @lltrace
   265 def accept_etype(cls, req, *args, **kwargs):
   584 def accept_etype(cls, req, *args, **kwargs):
   266     """check etype presence in request form *and* accepts conformance"""
   585     """check etype presence in request form *and* accepts conformance"""
   267     if 'etype' not in req.form and 'etype' not in kwargs:
       
   268         return 0
       
   269     try:
   586     try:
   270         etype = req.form['etype']
   587         etype = req.form['etype']
   271     except KeyError:
   588     except KeyError:
   272         etype = kwargs['etype']
   589         try:
   273     # value is a list or a tuple if web request form received several
   590             etype = kwargs['etype']
   274     # values for etype parameter
   591         except KeyError:
   275     assert isinstance(etype, basestring), "got multiple etype parameters in req.form"
   592             return 0
   276     if 'Any' in cls.accepts:
   593     return implements(*cls.accepts).score_class(cls.vreg.etype_class(etype), req)
   277         return 1
       
   278     # no Any found, we *need* exact match
       
   279     if etype not in cls.accepts:
       
   280         return 0
       
   281     # exact match must return a greater value than 'Any'-match
       
   282     return 2
       
   283 etype_form_selector = deprecated_function(accept_etype)
   594 etype_form_selector = deprecated_function(accept_etype)
   284 
   595 
   285 @lltrace
   596 @lltrace
   286 def _non_final_entity(cls, req, rset, row=None, col=None, **kwargs):
   597 def _rql_condition(cls, req, rset, row=None, col=0, **kwargs):
   287     """accept non final entities
       
   288     if row is not specified, use the first one
       
   289     if col is not specified, use the first one
       
   290     """
       
   291     etype = rset.description[row or 0][col or 0]
       
   292     if etype is None: # outer join
       
   293         return 0
       
   294     if cls.schema.eschema(etype).is_final():
       
   295         return 0
       
   296     return 1
       
   297 _nfentity_selector = deprecated_function(_non_final_entity)
       
   298 
       
   299 @lltrace
       
   300 def _rql_condition(cls, req, rset, row=None, col=None, **kwargs):
       
   301     """accept single entity result set if the entity match an rql condition
   598     """accept single entity result set if the entity match an rql condition
   302     """
   599     """
   303     if cls.condition:
   600     if cls.condition:
   304         eid = rset[row or 0][col or 0]
   601         eid = rset[row or 0][col or 0]
   305         if 'U' in frozenset(split_expression(cls.condition)):
   602         if 'U' in frozenset(split_expression(cls.condition)):
   311         except Unauthorized:
   608         except Unauthorized:
   312             return 0
   609             return 0
   313         
   610         
   314     return 1
   611     return 1
   315 _rqlcondition_selector = deprecated_function(_rql_condition)
   612 _rqlcondition_selector = deprecated_function(_rql_condition)
   316 
   613         
   317 @lltrace
   614 @lltrace
   318 def _implement_interface(cls, req, rset, row=None, col=None, **kwargs):
   615 def but_etype(cls, req, rset, row=None, col=0, **kwargs):
   319     """accept uniform result sets, and apply the following rules:
       
   320 
       
   321     * wrapped class must have a accepts_interfaces attribute listing the
       
   322       accepted ORed interfaces
       
   323     * if row is None, return the sum of values returned by the method
       
   324       for each entity's class in the result set. If any score is 0,
       
   325       return 0.
       
   326     * if row is specified, return the value returned by the method with
       
   327       the entity's class of this row
       
   328     """
       
   329     # XXX this selector can be refactored : extract the code testing
       
   330     #     for entity schema / interface compliance
       
   331     score = 0
       
   332     # check 'accepts' to give priority to more specific classes
       
   333     if row is None:
       
   334         for etype in rset.column_types(col or 0):
       
   335             eclass = cls.vreg.etype_class(etype)
       
   336             escore = 0
       
   337             for iface in cls.accepts_interfaces:
       
   338                 escore += iface.is_implemented_by(eclass)
       
   339             if not escore:
       
   340                 return 0
       
   341             score += escore
       
   342             accepts = set(getattr(cls, 'accepts', ()))
       
   343             # if accepts is defined on the vobject, eclass must match
       
   344             if accepts:
       
   345                 eschema = eclass.e_schema
       
   346                 etypes = set([eschema] + eschema.ancestors())
       
   347                 if accepts & etypes:
       
   348                     score += 2
       
   349                 elif 'Any' not in accepts:
       
   350                     return 0
       
   351         return score + 1
       
   352     etype = rset.description[row][col or 0]
       
   353     if etype is None: # outer join
       
   354         return 0
       
   355     eclass = cls.vreg.etype_class(etype)
       
   356     for iface in cls.accepts_interfaces:
       
   357         score += iface.is_implemented_by(eclass)
       
   358     if score:
       
   359         accepts = set(getattr(cls, 'accepts', ()))
       
   360         # if accepts is defined on the vobject, eclass must match
       
   361         if accepts:
       
   362             eschema = eclass.e_schema
       
   363             etypes = set([eschema] + eschema.ancestors())
       
   364             if accepts & etypes:
       
   365                 score += 1
       
   366             elif 'Any' not in accepts:
       
   367                 return 0
       
   368         score += 1
       
   369     return score
       
   370 _interface_selector = deprecated_function(_implement_interface)
       
   371 
       
   372 @lltrace
       
   373 def score_entity_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   374     if row is None:
       
   375         rows = xrange(rset.rowcount)
       
   376     else:
       
   377         rows = (row,)
       
   378     for row in rows:
       
   379         try:
       
   380             score = cls.score_entity(rset.get_entity(row, col or 0))
       
   381         except DummyCursorError:
       
   382             # get a dummy cursor error, that means we are currently
       
   383             # using a dummy rset to list possible views for an entity
       
   384             # type, not for an actual result set. In that case, we
       
   385             # don't care of the value, consider the object as selectable
       
   386             return 1
       
   387         if not score:
       
   388             return 0
       
   389     return 1
       
   390 
       
   391 @lltrace
       
   392 def accept_rset(cls, req, rset, row=None, col=None, **kwargs):
       
   393     """simply delegate to cls.accept_rset method"""
       
   394     return cls.accept_rset(req, rset, row=row, col=col)
       
   395 accept_rset_selector = deprecated_function(accept_rset)
       
   396 
       
   397 @lltrace
       
   398 def but_etype(cls, req, rset, row=None, col=None, **kwargs):
       
   399     """restrict the searchstate_accept_one_selector to exclude entity's type
   616     """restrict the searchstate_accept_one_selector to exclude entity's type
   400     refered by the .etype attribute
   617     refered by the .etype attribute
   401     """
   618     """
   402     if rset.description[row or 0][col or 0] == cls.etype:
   619     if rset.description[row or 0][col or 0] == cls.etype:
   403         return 0
   620         return 0
   404     return 1
   621     return 1
   405 but_etype_selector = deprecated_function(but_etype)
   622 but_etype_selector = deprecated_function(but_etype)
   406 
   623 
   407 @lltrace
   624 @lltrace
   408 def etype_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
   625 def etype_rtype_selector(cls, req, rset, row=None, col=0, **kwargs):
   409     """only check if the user has read access on the entity's type refered
   626     """only check if the user has read access on the entity's type refered
   410     by the .etype attribute and on the relations's type refered by the
   627     by the .etype attribute and on the relations's type refered by the
   411     .rtype attribute if set.
   628     .rtype attribute if set.
   412     """
   629     """
   413     schema = cls.schema
   630     schema = cls.schema
   421         if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
   638         if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
   422             return 0
   639             return 0
   423     return 1
   640     return 1
   424 
   641 
   425 @lltrace
   642 @lltrace
   426 def has_relation(cls, req, rset, row=None, col=None, **kwargs):
   643 def has_related_entities(cls, req, rset, row=None, col=0, **kwargs):
   427     """check if the user has read access on the relations's type refered by the
       
   428     .rtype attribute of the class, and if all entities types in the
       
   429     result set has this relation.
       
   430     """
       
   431     if hasattr(cls, 'rtype'):
       
   432         rschema = cls.schema.rschema(cls.rtype)
       
   433         perm = getattr(cls, 'require_permission', 'read')
       
   434         if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
       
   435             return 0
       
   436         if row is None:
       
   437             for etype in rset.column_types(col or 0):
       
   438                 if not cls.relation_possible(etype):
       
   439                     return 0
       
   440         elif not cls.relation_possible(rset.description[row][col or 0]):
       
   441             return 0
       
   442     return 1
       
   443 accept_rtype_selector = deprecated_function(has_relation)
       
   444 
       
   445 @lltrace
       
   446 def one_has_relation(cls, req, rset, row=None, col=None, **kwargs):
       
   447     """check if the user has read access on the relations's type refered by the
       
   448     .rtype attribute of the class, and if at least one entity type in the
       
   449     result set has this relation.
       
   450     """
       
   451     rschema = cls.schema.rschema(cls.rtype)
       
   452     perm = getattr(cls, 'require_permission', 'read')
       
   453     if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
       
   454         return 0
       
   455     if row is None:
       
   456         for etype in rset.column_types(col or 0):
       
   457             if cls.relation_possible(etype):
       
   458                 return 1
       
   459     elif cls.relation_possible(rset.description[row][col or 0]):
       
   460         return 1
       
   461     return 0
       
   462 one_has_relation_selector = deprecated_function(one_has_relation)
       
   463 
       
   464 @lltrace
       
   465 def has_related_entities(cls, req, rset, row=None, col=None, **kwargs):
       
   466     return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
   644     return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
   467 
   645 
   468 
   646 @lltrace
   469 @lltrace
   647 def user_can_add_etype(cls, req, rset, row=None, col=0, **kwargs):
   470 def match_user_group(cls, req, rset=None, row=None, col=None, **kwargs):
       
   471     """select according to user's groups"""
       
   472     if not cls.require_groups:
       
   473         return 1
       
   474     user = req.user
       
   475     if user is None:
       
   476         return int('guests' in cls.require_groups)
       
   477     score = 0
       
   478     if 'owners' in cls.require_groups and rset:
       
   479         if row is not None:
       
   480             eid = rset[row][col or 0]
       
   481             if user.owns(eid):
       
   482                 score = 1
       
   483         else:
       
   484             score = all(user.owns(r[col or 0]) for r in rset)
       
   485     score += user.matching_groups(cls.require_groups)
       
   486     if score:
       
   487         # add 1 so that an object with one matching group take priority
       
   488         # on an object without require_groups
       
   489         return score + 1 
       
   490     return 0
       
   491 in_group_selector = deprecated_function(match_user_group)
       
   492 
       
   493 @lltrace
       
   494 def user_can_add_etype(cls, req, rset, row=None, col=None, **kwargs):
       
   495     """only check if the user has add access on the entity's type refered
   648     """only check if the user has add access on the entity's type refered
   496     by the .etype attribute.
   649     by the .etype attribute.
   497     """
   650     """
   498     if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
   651     if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
   499         return 0
   652         return 0
   500     return 1
   653     return 1
   501 add_etype_selector = deprecated_function(user_can_add_etype)
   654 add_etype_selector = deprecated_function(user_can_add_etype)
   502 
   655 
   503 @lltrace
   656 @lltrace
   504 def match_context_prop(cls, req, rset, row=None, col=None, context=None,
   657 def match_context_prop(cls, req, rset, row=None, col=0, context=None,
   505                        **kwargs):
   658                        **kwargs):
   506     propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
   659     propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
   507     if not propval:
   660     if not propval:
   508         propval = cls.context
   661         propval = cls.context
   509     if context is not None and propval and context != propval:
   662     if context is not None and propval and context != propval:
   510         return 0
   663         return 0
   511     return 1
   664     return 1
   512 contextprop_selector = deprecated_function(match_context_prop)
   665 contextprop_selector = deprecated_function(match_context_prop)
   513 
   666 
   514 @lltrace
   667 @lltrace
   515 def primary_view(cls, req, rset, row=None, col=None, view=None,
   668 def primary_view(cls, req, rset, row=None, col=0, view=None,
   516                           **kwargs):
   669                           **kwargs):
   517     if view is not None and not view.is_primary():
   670     if view is not None and not view.is_primary():
   518         return 0
   671         return 0
   519     return 1
   672     return 1
   520 primaryview_selector = deprecated_function(primary_view)
   673 primaryview_selector = deprecated_function(primary_view)
   531         except NoSelectableObject:
   684         except NoSelectableObject:
   532             return 0
   685             return 0
   533     return selector
   686     return selector
   534 
   687 
   535 
   688 
       
   689 
       
   690 # XXX DEPRECATED ##############################################################
       
   691 
       
   692 def nfentity_selector(cls, req, rset, row=None, col=0, **kwargs):
       
   693     return non_final_entity()(cls, req, rset, row, col)
       
   694 nfentity_selector = deprecated_function(nfentity_selector)
       
   695 
       
   696 def implement_interface(cls, req, rset, row=None, col=0, **kwargs):
       
   697     return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
       
   698 _interface_selector = deprecated_function(implement_interface)
       
   699 interface_selector = deprecated_function(implement_interface)
       
   700 implement_interface = deprecated_function(implement_interface)
       
   701 
       
   702 def searchstate_selector(cls, req, rset, row=None, col=0, **kwargs):
       
   703     return match_search_state(cls.search_states)(cls, req, rset, row, col)
       
   704 searchstate_selector = deprecated_function(searchstate_selector)
       
   705 
       
   706 def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
       
   707     return match_user_groups(cls.require_groups)(cls, req, rset, row, col, **kwargs)
       
   708 in_group_selector = deprecated_function(match_user_group)
       
   709 match_user_group = deprecated_function(match_user_group)
       
   710 
       
   711 def has_relation(cls, req, rset, row=None, col=0, **kwargs):
       
   712     return relation_possible(cls.rtype, role(cls), cls.etype,
       
   713                              getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
       
   714 has_relation = deprecated_function(has_relation)
       
   715 
       
   716 def one_has_relation(cls, req, rset, row=None, col=0, **kwargs):
       
   717     return relation_possible(cls.rtype, role(cls), cls.etype,
       
   718                              getattr(cls, 'require_permission', 'read',
       
   719                                      once_is_enough=True))(cls, req, rset, row, col, **kwargs)
       
   720 one_has_relation = deprecated_function(one_has_relation, 'use relation_possible selector')
       
   721 
       
   722 def accept_rset(cls, req, rset, row=None, col=0, **kwargs):
       
   723     """simply delegate to cls.accept_rset method"""
       
   724     return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
       
   725 accept_rset_selector = deprecated_function(accept_rset)
       
   726 accept_rset = deprecated_function(accept_rset, 'use implements selector')
       
   727 
       
   728 accept = chainall(non_final_entity(), accept_rset, name='accept')
       
   729 accept_selector = deprecated_function(accept)
       
   730 accept = deprecated_function(accept, 'use implements selector')
       
   731 
   536 # compound selectors ##########################################################
   732 # compound selectors ##########################################################
   537 
   733 
   538 non_final_entity = chainall(nonempty_rset, _non_final_entity)
   734 accept_one = deprecated_function(chainall(one_line_rset, accept,
   539 non_final_entity.__name__ = 'non_final_entity'
   735                                           name='accept_one'))
   540 nfentity_selector = deprecated_function(non_final_entity)
       
   541 
       
   542 implement_interface = chainall(non_final_entity, _implement_interface)
       
   543 implement_interface.__name__ = 'implement_interface'
       
   544 interface_selector = deprecated_function(implement_interface)
       
   545 
       
   546 accept = chainall(non_final_entity, accept_rset)
       
   547 accept.__name__ = 'accept'
       
   548 accept_selector = deprecated_function(accept)
       
   549 
       
   550 accept_one = chainall(one_line_rset, accept)
       
   551 accept_one.__name__ = 'accept_one'
       
   552 accept_one_selector = deprecated_function(accept_one)
   736 accept_one_selector = deprecated_function(accept_one)
   553 
   737 
   554 rql_condition = chainall(non_final_entity, one_line_rset, _rql_condition)
   738 rql_condition = chainall(non_final_entity(), one_line_rset, _rql_condition,
   555 rql_condition.__name__ = 'rql_condition'
   739                          name='rql_condition')
   556 rqlcondition_selector = deprecated_function(rql_condition)
   740 rqlcondition_selector = deprecated_function(rql_condition)
   557 
   741 
   558 
   742 
   559 searchstate_accept = chainall(nonempty_rset, match_search_state, accept)
   743 searchstate_accept = chainall(nonempty_rset, match_search_state, accept,
   560 searchstate_accept.__name__ = 'searchstate_accept'
   744                               name='searchstate_accept')
   561 searchstate_accept_selector = deprecated_function(searchstate_accept)
   745 searchstate_accept_selector = deprecated_function(searchstate_accept)
   562 
   746 
   563 searchstate_accept_one = chainall(one_line_rset, match_search_state,
   747 searchstate_accept_one = chainall(one_line_rset, match_search_state,
   564                                   accept, _rql_condition)
   748                                   accept, _rql_condition,
   565 searchstate_accept_one.__name__ = 'searchstate_accept_one'
   749                                   name='searchstate_accept_one')
   566 searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
   750 searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
   567 
   751 
   568 searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype)
   752 searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype,
   569 searchstate_accept_one_but_etype.__name__ = 'searchstate_accept_one_but_etype'
   753                                             name='searchstate_accept_one_but_etype')
   570 searchstate_accept_one_but_etype_selector = deprecated_function(
   754 searchstate_accept_one_but_etype_selector = deprecated_function(
   571     searchstate_accept_one_but_etype)
   755     searchstate_accept_one_but_etype)