selectors.py
branchtls-sprint
changeset 634 0badd061ce0f
child 640 8e64f12be69c
equal deleted inserted replaced
633:087e3f1e87c8 634:0badd061ce0f
       
     1 """This file contains some basic selectors required by application objects.
       
     2 
       
     3 A selector is responsible to score how well an object may be used with a
       
     4 given result set (publishing time selection)
       
     5 
       
     6 If you have trouble with selectors, especially if the objet (typically
       
     7 a view or a component) you want to use is not selected and you want to
       
     8 know which one(s) of its selectors fail (e.g. returns 0), you can use
       
     9 `traced_selection` or even direclty `TRACED_OIDS`.
       
    10 
       
    11 `TRACED_OIDS` is a tuple of traced object ids. The special value
       
    12 'all' may be used to log selectors for all objects.
       
    13 
       
    14 For instance, say that the following code yields a `NoSelectableObject`
       
    15 exception::
       
    16 
       
    17     self.view('calendar', myrset)
       
    18 
       
    19 You can log the selectors involved for *calendar* by replacing the line
       
    20 above by::
       
    21 
       
    22     # in Python2.5
       
    23     from cubicweb.common.selectors import traced_selection
       
    24     with traced_selection():
       
    25         self.view('calendar', myrset)
       
    26 
       
    27     # in Python2.4
       
    28     from cubicweb.common import selectors
       
    29     selectors.TRACED_OIDS = ('calendar',)
       
    30     self.view('calendar', myrset)
       
    31     selectors.TRACED_OIDS = ()
       
    32  
       
    33 
       
    34 
       
    35 :organization: Logilab
       
    36 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
    37 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
    38 """
       
    39 
       
    40 __docformat__ = "restructuredtext en"
       
    41 
       
    42 import logging
       
    43 from warnings import warn
       
    44 
       
    45 from logilab.common.compat import all
       
    46 from logilab.common.deprecation import deprecated_function
       
    47 from logilab.common.interface import implements as implements_iface
       
    48 
       
    49 from yams import BASE_TYPES
       
    50 
       
    51 from cubicweb import Unauthorized, NoSelectableObject, role
       
    52 from cubicweb.vregistry import NoSelectableObject, Selector, chainall, chainfirst
       
    53 from cubicweb.cwvreg import DummyCursorError
       
    54 from cubicweb.cwconfig import CubicWebConfiguration
       
    55 from cubicweb.schema import split_expression
       
    56 
       
    57 # helpers for debugging selectors
       
    58 SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
       
    59 TRACED_OIDS = ()
       
    60 
       
    61 def lltrace(selector):
       
    62     # don't wrap selectors if not in development mode
       
    63     if CubicWebConfiguration.mode == 'installed':
       
    64         return selector
       
    65     def traced(cls, *args, **kwargs):
       
    66         if isinstance(cls, Selector):
       
    67             selname = cls.__class__.__name__
       
    68             oid = args[0].id
       
    69         else:
       
    70             selname = selector.__name__
       
    71             oid = cls.id
       
    72         ret = selector(cls, *args, **kwargs)
       
    73         if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
       
    74             #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
       
    75             print 'selector %s returned %s for %s' % (selname, ret, cls)
       
    76         return ret
       
    77     traced.__name__ = selector.__name__
       
    78     return traced
       
    79 
       
    80 class traced_selection(object):
       
    81     """selector debugging helper.
       
    82 
       
    83     Typical usage is :
       
    84 
       
    85     >>> with traced_selection():
       
    86     ...     # some code in which you want to debug selectors
       
    87     ...     # for all objects
       
    88 
       
    89     or
       
    90 
       
    91     >>> with traced_selection( ('oid1', 'oid2') ):
       
    92     ...     # some code in which you want to debug selectors
       
    93     ...     # for objects with id 'oid1' and 'oid2'
       
    94     
       
    95     """
       
    96     def __init__(self, traced='all'):
       
    97         self.traced = traced
       
    98         
       
    99     def __enter__(self):
       
   100         global TRACED_OIDS
       
   101         TRACED_OIDS = self.traced
       
   102 
       
   103     def __exit__(self, exctype, exc, traceback):
       
   104         global TRACED_OIDS
       
   105         TRACED_OIDS = ()
       
   106         return traceback is None
       
   107 
       
   108 # very basic selectors ########################################################
       
   109 
       
   110 def yes(cls, *args, **kwargs):
       
   111     """accept everything"""
       
   112     return 1
       
   113 yes_selector = deprecated_function(yes)
       
   114 
       
   115 @lltrace
       
   116 def none_rset(cls, req, rset, *args, **kwargs):
       
   117     """accept no result set"""
       
   118     if rset is None:
       
   119         return 1
       
   120     return 0
       
   121 norset_selector = deprecated_function(none_rset)
       
   122 
       
   123 @lltrace
       
   124 def any_rset(cls, req, rset, *args, **kwargs):
       
   125     """accept result set, whatever the number of result"""
       
   126     if rset is not None:
       
   127         return 1
       
   128     return 0
       
   129 rset_selector = deprecated_function(any_rset)
       
   130 
       
   131 @lltrace
       
   132 def nonempty_rset(cls, req, rset, *args, **kwargs):
       
   133     """accept any non empty result set"""
       
   134     if rset is not None and rset.rowcount:
       
   135         return 1
       
   136     return 0
       
   137 anyrset_selector = deprecated_function(nonempty_rset)
       
   138     
       
   139 @lltrace
       
   140 def empty_rset(cls, req, rset, *args, **kwargs):
       
   141     """accept empty result set"""
       
   142     if rset is not None and rset.rowcount == 0:
       
   143         return 1
       
   144     return 0
       
   145 emptyrset_selector = deprecated_function(empty_rset)
       
   146 
       
   147 @lltrace
       
   148 def one_line_rset(cls, req, rset, row=None, *args, **kwargs):
       
   149     """accept result set with a single line of result"""
       
   150     if rset is not None and (row is not None or rset.rowcount == 1):
       
   151         return 1
       
   152     return 0
       
   153 onelinerset_selector = deprecated_function(one_line_rset)
       
   154 
       
   155 @lltrace
       
   156 def two_lines_rset(cls, req, rset, *args, **kwargs):
       
   157     """accept result set with *at least* two lines of result"""
       
   158     if rset is not None and rset.rowcount > 1:
       
   159         return 1
       
   160     return 0
       
   161 twolinerset_selector = deprecated_function(two_lines_rset)
       
   162 
       
   163 @lltrace
       
   164 def two_cols_rset(cls, req, rset, *args, **kwargs):
       
   165     """accept result set with at least one line and two columns of result"""
       
   166     if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1:
       
   167         return 1
       
   168     return 0
       
   169 twocolrset_selector = deprecated_function(two_cols_rset)
       
   170 
       
   171 @lltrace
       
   172 def paginated_rset(cls, req, rset, *args, **kwargs):
       
   173     """accept result sets with more rows than the page size
       
   174     """
       
   175     page_size = kwargs.get('page_size')
       
   176     if page_size is None:
       
   177         page_size = req.form.get('page_size')
       
   178         if page_size is None:
       
   179             page_size = req.property_value('navigation.page-size')
       
   180         else:
       
   181             page_size = int(page_size)
       
   182     if rset is None or len(rset) <= page_size:
       
   183         return 0
       
   184     return 1
       
   185 largerset_selector = deprecated_function(paginated_rset)
       
   186 
       
   187 @lltrace
       
   188 def sorted_rset(cls, req, rset, row=None, col=0, **kwargs):
       
   189     """accept sorted result set"""
       
   190     rqlst = rset.syntax_tree()
       
   191     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
       
   192         return 0
       
   193     return 2
       
   194 sortedrset_selector = deprecated_function(sorted_rset)
       
   195 
       
   196 @lltrace
       
   197 def one_etype_rset(cls, req, rset, *args, **kwargs):
       
   198     """accept result set where entities in the first columns are all of the
       
   199     same type
       
   200     """
       
   201     if len(rset.column_types(0)) != 1:
       
   202         return 0
       
   203     return 1
       
   204 oneetyperset_selector = deprecated_function(one_etype_rset)
       
   205 
       
   206 @lltrace
       
   207 def two_etypes_rset(cls, req, rset, **kwargs):
       
   208     """accepts resultsets containing several entity types"""
       
   209     if rset:
       
   210         etypes = rset.column_types(0)
       
   211         if len(etypes) > 1:
       
   212             return 1
       
   213     return 0
       
   214 multitype_selector = deprecated_function(two_etypes_rset)
       
   215 
       
   216 
       
   217 class match_search_state(Selector):
       
   218     def __init__(self, *expected):
       
   219         self.expected = expected
       
   220         
       
   221     @lltrace
       
   222     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
       
   223         """checks if the current request search state is in one of the expected states
       
   224         the wrapped class
       
   225 
       
   226         search state should be either 'normal' or 'linksearch' (eg searching for an
       
   227         object to create a relation with another)
       
   228         """
       
   229         try:
       
   230             if not req.search_state[0] in self.expected:
       
   231                 return 0
       
   232         except AttributeError:
       
   233             return 1 # class doesn't care about search state, accept it
       
   234         return 1
       
   235 
       
   236 
       
   237 class match_form_params(match_search_state):
       
   238     """check if parameters specified as initializer arguments are specified
       
   239     in request form parameters
       
   240     """
       
   241     @lltrace
       
   242     def __call__(self, cls, req, *args, **kwargs):
       
   243         score = 0
       
   244         for param in self.expected:
       
   245             val = req.form.get(param)
       
   246             if not val:
       
   247                 return 0
       
   248             score += 1
       
   249         return len(self.expected)
       
   250 
       
   251 
       
   252 class match_kwargs(match_search_state):
       
   253     """check if parameters specified as initializer arguments are specified
       
   254     in named parameters
       
   255     """
       
   256     @lltrace
       
   257     def __call__(self, cls, req, *args, **kwargs):
       
   258         for arg in self.expected:
       
   259             if not arg in kwargs:
       
   260                 return 0
       
   261         return len(self.expected)
       
   262 
       
   263 
       
   264 @lltrace
       
   265 def anonymous_user(cls, req, *args, **kwargs):
       
   266     """accept if user is anonymous"""
       
   267     if req.cnx.anonymous_connection:
       
   268         return 1
       
   269     return 0
       
   270 anonymous_selector = deprecated_function(anonymous_user)
       
   271 
       
   272 @lltrace
       
   273 def authenticated_user(cls, req, *args, **kwargs):
       
   274     """accept if user is authenticated"""
       
   275     return not anonymous_user(cls, req, *args, **kwargs)
       
   276 not_anonymous_selector = deprecated_function(authenticated_user)
       
   277 
       
   278 # abstract selectors ##########################################################
       
   279 
       
   280 class EClassSelector(Selector):
       
   281     """abstract class for selectors working on the entity classes of the result
       
   282     set
       
   283     """
       
   284     once_is_enough = False
       
   285     
       
   286     @lltrace
       
   287     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
       
   288         if not rset:
       
   289             return 0
       
   290         score = 0
       
   291         if row is None:
       
   292             for etype in rset.column_types(col):
       
   293                 if etype is None: # outer join
       
   294                     continue
       
   295                 if etype in BASE_TYPES:
       
   296                     return 0
       
   297                 escore = self.score_class(cls.vreg.etype_class(etype), req)
       
   298                 if not escore:
       
   299                     return 0
       
   300                 elif self.once_is_enough:
       
   301                     return escore
       
   302                 score += escore
       
   303         else:
       
   304             etype = rset.description[row][col]
       
   305             if etype is not None and not etype in BASE_TYPES:
       
   306                 score = self.score_class(cls.vreg.etype_class(etype), req)
       
   307         return score and (score + 1)
       
   308 
       
   309     def score_class(self, eclass, req):
       
   310         raise NotImplementedError()
       
   311 
       
   312 
       
   313 class EntitySelector(Selector):
       
   314     """abstract class for selectors working on the entity instances of the
       
   315     result set
       
   316     """
       
   317     @lltrace
       
   318     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
       
   319         if not rset:
       
   320             return 0
       
   321         score = 0
       
   322         if row is None:
       
   323             for row, rowvalue in enumerate(rset.rows):
       
   324                 if rowvalue[col] is None: # outer join
       
   325                     continue
       
   326                 try:
       
   327                     escore = self.score_entity(rset.get_entity(row, col))
       
   328                 except NotAnEntity:
       
   329                     return 0
       
   330                 if not escore:
       
   331                     return 0
       
   332                 score += escore
       
   333         else:
       
   334             etype = rset.description[row][col]
       
   335             if etype is not None: # outer join
       
   336                 try:
       
   337                     score = self.score_entity(rset.get_entity(row, col))
       
   338                 except NotAnEntity:
       
   339                     return 0
       
   340         return score and (score + 1)
       
   341 
       
   342     def score_entity(self, entity):
       
   343         raise NotImplementedError()
       
   344 
       
   345 # not so basic selectors ######################################################
       
   346 
       
   347 class implements(EClassSelector):
       
   348     """initializer takes a list of interfaces or entity types as argument
       
   349     
       
   350     * if row is None, return the number of implemented interfaces for each
       
   351       entity's class in the result set at the specified column (or column 0).
       
   352       If any class has no matching interface, return 0.
       
   353     * if row is specified, return number of implemented interfaces by the
       
   354       entity's class at this row (and column)
       
   355 
       
   356     if some interface is an entity class, the score will reflect class
       
   357     proximity so the most specific object'll be selected
       
   358     """
       
   359 
       
   360     def __init__(self, *expected_ifaces):
       
   361         self.expected_ifaces = expected_ifaces
       
   362 
       
   363     def score_class(self, eclass, req):
       
   364         score = 0
       
   365         for iface in self.expected_ifaces:
       
   366             if isinstance(iface, basestring):
       
   367                 # entity type
       
   368                 iface = eclass.vreg.etype_class(iface)
       
   369             if implements_iface(eclass, iface):
       
   370                 score += 1
       
   371                 if getattr(iface, '__registry__', None) == 'etypes':
       
   372                     score += 1
       
   373                     # adjust score if the interface is an entity class
       
   374                     if iface is eclass:
       
   375                         score += len(eclass.e_schema.ancestors())
       
   376                         print 'is majoration', len(eclass.e_schema.ancestors()) 
       
   377                     else:
       
   378                         parents = [e.type for e in eclass.e_schema.ancestors()]
       
   379                         for index, etype in enumerate(reversed(parents)):
       
   380                             basecls = eclass.vreg.etype_class(etype)
       
   381                             if iface is basecls:
       
   382                                 score += index
       
   383                                 print 'etype majoration', index
       
   384                                 break
       
   385         return score
       
   386 
       
   387 
       
   388 class specified_etype_implements(implements):
       
   389     """return the "interface score" for class associated to 'etype' (expected in
       
   390     request form or arguments)
       
   391     """
       
   392     
       
   393     @lltrace
       
   394     def __call__(cls, req, *args, **kwargs):
       
   395         try:
       
   396             etype = req.form['etype']
       
   397         except KeyError:
       
   398             try:
       
   399                 etype = kwargs['etype']
       
   400             except KeyError:
       
   401                 return 0
       
   402         return self.score_class(cls.vreg.etype_class(etype), req)
       
   403 
       
   404 
       
   405 class relation_possible(EClassSelector):
       
   406     """initializer takes relation name as argument and an optional role (default
       
   407       as subject) and target type (default to unspecified)
       
   408       
       
   409     * if row is None, return 1 if every entity's class in the result set at the
       
   410       specified column (or column 0) may have this relation (as role). If target
       
   411       type is specified, check the relation's end may be of this target type.
       
   412       
       
   413     * if row is specified, check relation is supported by the entity's class at
       
   414       this row (and column)
       
   415     """
       
   416     def __init__(self, rtype, role='subject', target_etype=None,
       
   417                  permission='read', once_is_enough=False):
       
   418         self.rtype = rtype
       
   419         self.role = role
       
   420         self.target_etype = target_etype
       
   421         self.permission = permission
       
   422         self.once_is_enough = once_is_enough
       
   423 
       
   424     @lltrace
       
   425     def __call__(self, cls, *args, **kwargs):
       
   426         rschema = cls.schema.rschema(self.rtype)
       
   427         if not (rschema.has_perm(req, self.permission)
       
   428                 or rschema.has_local_role(self.permission)):
       
   429             return 0
       
   430         return super(relation_possible, self)(cls, *args, **kwargs)
       
   431         
       
   432     def score_class(self, eclass, req):
       
   433         eschema = eclass.e_schema
       
   434         try:
       
   435             if self.role == 'object':
       
   436                 rschema = eschema.object_relation(self.rtype)
       
   437             else:
       
   438                 rschema = eschema.subject_relation(self.rtype)
       
   439         except KeyError:
       
   440             return 0
       
   441         if self.target_etype is not None:
       
   442             try:
       
   443                 if self.role == 'object':
       
   444                     return self.target_etype in rschema.objects(eschema)
       
   445                 else:
       
   446                     return self.target_etype in rschema.subjects(eschema)
       
   447             except KeyError, ex:
       
   448                 return 0
       
   449         return 1
       
   450 
       
   451 
       
   452 class non_final_entity(EClassSelector):
       
   453     """initializer takes no argument
       
   454 
       
   455     * if row is None, return 1 if there are only non final entity's class in the
       
   456       result set at the specified column (or column 0)
       
   457     * if row is specified, return 1 if entity's class at this row (and column)
       
   458       isn't final
       
   459     """
       
   460     def score_class(self, eclass, req):
       
   461         return int(not eclass.e_schema.is_final())
       
   462 
       
   463 
       
   464 class match_user_groups(Selector):
       
   465     """initializer takes users group as argument
       
   466 
       
   467     * check logged user is in one of the given groups. If special 'owners' group
       
   468       given:
       
   469       - if row is specified check the entity at the given row/col is owned by
       
   470         the logged user
       
   471       - if row is not specified check all entities in col are owned by the
       
   472         logged user
       
   473     """
       
   474     
       
   475     def __init__(self, *required_groups):
       
   476         self.required_groups = required_groups
       
   477     
       
   478     @lltrace
       
   479     def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
       
   480         user = req.user
       
   481         if user is None:
       
   482             return int('guests' in self.require_groups)
       
   483         score = user.matching_groups(self.require_groups)
       
   484         if not score and 'owners' in self.require_groups and rset:
       
   485             nbowned = 0
       
   486             if row is not None:
       
   487                 if not user.owns(rset[row][col]):
       
   488                     return 0
       
   489                 score = 1
       
   490             else:
       
   491                 score = all(user.owns(r[col or 0]) for r in rset)
       
   492         return 0
       
   493 
       
   494 
       
   495 class has_editable_relation(EntitySelector):
       
   496     """initializer takes no argument
       
   497 
       
   498     * if row is specified check the entity at the given row/col has some
       
   499       relation editable by the logged user
       
   500     * if row is not specified check all entities in col are owned have some
       
   501       relation editable by the logged userlogged user
       
   502     """
       
   503         
       
   504     def score_entity(self, entity):
       
   505         # if user has no update right but it can modify some relation,
       
   506         # display action anyway
       
   507         for dummy in entity.srelations_by_category(('generic', 'metadata'),
       
   508                                                    'add'):
       
   509             return 1
       
   510         for rschema, targetschemas, role in entity.relations_by_category(
       
   511             ('primary', 'secondary'), 'add'):
       
   512             if not rschema.is_final():
       
   513                 return 1
       
   514         return 0
       
   515 
       
   516 
       
   517 class may_add_relation(EntitySelector):
       
   518     """initializer takes a relation type and optional role (default to
       
   519     'subject') as argument
       
   520 
       
   521     if row is specified check the relation may be added to the entity at the
       
   522     given row/col (if row specified) or to every entities in the given col (if
       
   523     row is not specified)
       
   524     """
       
   525     
       
   526     def __init__(self, rtype, role='subject'):
       
   527         self.rtype = rtype
       
   528         self.role = role
       
   529         
       
   530     def score_entity(self, entity):
       
   531         rschema = entity.schema.rschema(self.rtype)
       
   532         if self.role == 'subject':
       
   533             if not rschema.has_perm(req, 'add', fromeid=entity.eid):
       
   534                 return False
       
   535         elif not rschema.has_perm(req, 'add', toeid=entity.eid):
       
   536             return False
       
   537         return True
       
   538 
       
   539         
       
   540 class has_permission(EntitySelector):
       
   541     """initializer takes a schema action (eg 'read'/'add'/'delete'/'update') as
       
   542     argument
       
   543 
       
   544     * if row is specified check user has permission to do the requested action
       
   545       on the entity at the given row/col
       
   546     * if row is specified check user has permission to do the requested action
       
   547       on all entities in the given col
       
   548     """
       
   549     def __init__(self, schema_action):
       
   550         self.schema_action = schema_action
       
   551         
       
   552     @lltrace
       
   553     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
       
   554         user = req.user
       
   555         action = self.schema_action
       
   556         if row is None:
       
   557             score = 0
       
   558             need_local_check = [] 
       
   559             geteschema = cls.schema.eschema
       
   560             for etype in rset.column_types(0):
       
   561                 if etype in BASE_TYPES:
       
   562                     return 0
       
   563                 eschema = geteschema(etype)
       
   564                 if not user.matching_groups(eschema.get_groups(action)):
       
   565                     if eschema.has_local_role(action):
       
   566                         # have to ckeck local roles
       
   567                         need_local_check.append(eschema)
       
   568                         continue
       
   569                     else:
       
   570                         # even a local role won't be enough
       
   571                         return 0
       
   572                 score += accepted
       
   573             if need_local_check:
       
   574                 # check local role for entities of necessary types
       
   575                 for i, row in enumerate(rset):
       
   576                     if not rset.description[i][0] in need_local_check:
       
   577                         continue
       
   578                     if not self.score_entity(rset.get_entity(i, col)):
       
   579                         return 0
       
   580                     score += 1
       
   581             return score
       
   582         if rset.description[row][col] in BASE_TYPES:
       
   583             return 0
       
   584         return self.score_entity(rset.get_entity(row, col))
       
   585     
       
   586     def score_entity(self, entity):
       
   587         if entity.has_perm(self.schema_action):
       
   588             return 1
       
   589         return 0
       
   590 
       
   591 
       
   592 class has_add_permission(EClassSelector):
       
   593     """return 1 if the user may add some entity of the types found in the
       
   594     result set (0 else)
       
   595     """
       
   596     def score_class(self, eclass, req):
       
   597         eschema = eclass.e_schema
       
   598         if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
       
   599                and eschema.has_perm(req, 'add'):
       
   600             return 1
       
   601         return 0
       
   602 
       
   603         
       
   604 class score_entity(EntitySelector):
       
   605     """initializer takes a function as argument (which is expected to take an
       
   606     entity as argument)
       
   607 
       
   608     return the score returned by the function on the entity at the given row/col
       
   609     (if row specified) or the sum of the score for every entities in the given
       
   610     col (if row is not specified). Return 0 at the first entity scoring to zero.
       
   611     """
       
   612     def __init__(self, scorefunc):
       
   613         self.score_entity = scorefunc
       
   614 
       
   615     
       
   616 # XXX not so basic selectors ######################################################
       
   617 
       
   618 @lltrace
       
   619 def _rql_condition(cls, req, rset, row=None, col=0, **kwargs):
       
   620     """accept single entity result set if the entity match an rql condition
       
   621     """
       
   622     if cls.condition:
       
   623         eid = rset[row or 0][col or 0]
       
   624         if 'U' in frozenset(split_expression(cls.condition)):
       
   625             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % cls.condition
       
   626         else:
       
   627             rql = 'Any X WHERE X eid %%(x)s, %s' % cls.condition
       
   628         try:
       
   629             return len(req.execute(rql, {'x': eid, 'u': req.user.eid}, 'x'))
       
   630         except Unauthorized:
       
   631             return 0
       
   632         
       
   633     return 1
       
   634 _rqlcondition_selector = deprecated_function(_rql_condition)
       
   635         
       
   636 @lltrace
       
   637 def but_etype(cls, req, rset, row=None, col=0, **kwargs):
       
   638     """restrict the searchstate_accept_one_selector to exclude entity's type
       
   639     refered by the .etype attribute
       
   640     """
       
   641     if rset.description[row or 0][col or 0] == cls.etype:
       
   642         return 0
       
   643     return 1
       
   644 but_etype_selector = deprecated_function(but_etype)
       
   645 
       
   646 @lltrace
       
   647 def etype_rtype_selector(cls, req, rset, row=None, col=0, **kwargs):
       
   648     """only check if the user has read access on the entity's type refered
       
   649     by the .etype attribute and on the relations's type refered by the
       
   650     .rtype attribute if set.
       
   651     """
       
   652     schema = cls.schema
       
   653     perm = getattr(cls, 'require_permission', 'read')
       
   654     if hasattr(cls, 'etype'):
       
   655         eschema = schema.eschema(cls.etype)
       
   656         if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
       
   657             return 0
       
   658     if hasattr(cls, 'rtype'):
       
   659         rschema = schema.rschema(cls.rtype)
       
   660         if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
       
   661             return 0
       
   662     return 1
       
   663 
       
   664 @lltrace
       
   665 def has_related_entities(cls, req, rset, row=None, col=0, **kwargs):
       
   666     return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
       
   667 
       
   668 @lltrace
       
   669 def user_can_add_etype(cls, req, rset, row=None, col=0, **kwargs):
       
   670     """only check if the user has add access on the entity's type refered
       
   671     by the .etype attribute.
       
   672     """
       
   673     if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
       
   674         return 0
       
   675     return 1
       
   676 add_etype_selector = deprecated_function(user_can_add_etype)
       
   677 
       
   678 @lltrace
       
   679 def match_context_prop(cls, req, rset, row=None, col=0, context=None,
       
   680                        **kwargs):
       
   681     propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
       
   682     if not propval:
       
   683         propval = cls.context
       
   684     if context is not None and propval and context != propval:
       
   685         return 0
       
   686     return 1
       
   687 contextprop_selector = deprecated_function(match_context_prop)
       
   688 
       
   689 @lltrace
       
   690 def primary_view(cls, req, rset, row=None, col=0, view=None,
       
   691                           **kwargs):
       
   692     if view is not None and not view.is_primary():
       
   693         return 0
       
   694     return 1
       
   695 primaryview_selector = deprecated_function(primary_view)
       
   696 
       
   697 def appobject_selectable(registry, oid):
       
   698     """return a selector that will have a positive score if an object for the
       
   699     given registry and object id is selectable for the input context
       
   700     """
       
   701     @lltrace
       
   702     def selector(cls, req, rset, *args, **kwargs):
       
   703         try:
       
   704             cls.vreg.select_object(registry, oid, req, rset, *args, **kwargs)
       
   705             return 1
       
   706         except NoSelectableObject:
       
   707             return 0
       
   708     return selector
       
   709 
       
   710 
       
   711 
       
   712 # XXX DEPRECATED ##############################################################
       
   713 
       
   714 def nfentity_selector(cls, req, rset, row=None, col=0, **kwargs):
       
   715     return non_final_entity()(cls, req, rset, row, col)
       
   716 nfentity_selector = deprecated_function(nfentity_selector)
       
   717 
       
   718 def implement_interface(cls, req, rset, row=None, col=0, **kwargs):
       
   719     return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
       
   720 _interface_selector = deprecated_function(implement_interface)
       
   721 interface_selector = deprecated_function(implement_interface)
       
   722 implement_interface = deprecated_function(implement_interface, 'use implements')
       
   723 
       
   724 def accept_etype(cls, req, *args, **kwargs):
       
   725     """check etype presence in request form *and* accepts conformance"""
       
   726     return specified_etype_implements(*cls.accepts)(cls, req, *args)
       
   727 etype_form_selector = deprecated_function(accept_etype)
       
   728 accept_etype = deprecated_function(accept_etype, 'use specified_etype_implements')
       
   729 
       
   730 def searchstate_selector(cls, req, rset, row=None, col=0, **kwargs):
       
   731     return match_search_state(cls.search_states)(cls, req, rset, row, col)
       
   732 searchstate_selector = deprecated_function(searchstate_selector)
       
   733 
       
   734 def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
       
   735     return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs)
       
   736 in_group_selector = deprecated_function(match_user_group)
       
   737 match_user_group = deprecated_function(match_user_group)
       
   738 
       
   739 def has_relation(cls, req, rset, row=None, col=0, **kwargs):
       
   740     return relation_possible(cls.rtype, role(cls), cls.etype,
       
   741                              getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
       
   742 has_relation = deprecated_function(has_relation)
       
   743 
       
   744 def one_has_relation(cls, req, rset, row=None, col=0, **kwargs):
       
   745     return relation_possible(cls.rtype, role(cls), cls.etype,
       
   746                              getattr(cls, 'require_permission', 'read',
       
   747                                      once_is_enough=True))(cls, req, rset, row, col, **kwargs)
       
   748 one_has_relation = deprecated_function(one_has_relation, 'use relation_possible selector')
       
   749 
       
   750 def accept_rset(cls, req, rset, row=None, col=0, **kwargs):
       
   751     """simply delegate to cls.accept_rset method"""
       
   752     return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
       
   753 accept_rset_selector = deprecated_function(accept_rset)
       
   754 accept_rset = deprecated_function(accept_rset, 'use implements selector')
       
   755 
       
   756 accept = chainall(non_final_entity(), accept_rset, name='accept')
       
   757 accept_selector = deprecated_function(accept)
       
   758 accept = deprecated_function(accept, 'use implements selector')
       
   759 
       
   760 # compound selectors ##########################################################
       
   761 
       
   762 accept_one = deprecated_function(chainall(one_line_rset, accept,
       
   763                                           name='accept_one'))
       
   764 accept_one_selector = deprecated_function(accept_one)
       
   765 
       
   766 rql_condition = chainall(non_final_entity(), one_line_rset, _rql_condition,
       
   767                          name='rql_condition')
       
   768 rqlcondition_selector = deprecated_function(rql_condition)
       
   769 
       
   770 
       
   771 searchstate_accept = chainall(nonempty_rset, match_search_state, accept,
       
   772                               name='searchstate_accept')
       
   773 searchstate_accept_selector = deprecated_function(searchstate_accept)
       
   774 
       
   775 searchstate_accept_one = chainall(one_line_rset, match_search_state,
       
   776                                   accept, _rql_condition,
       
   777                                   name='searchstate_accept_one')
       
   778 searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
       
   779 
       
   780 searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype,
       
   781                                             name='searchstate_accept_one_but_etype')
       
   782 searchstate_accept_one_but_etype_selector = deprecated_function(
       
   783     searchstate_accept_one_but_etype)
       
   784 
       
   785 #req_form_params_selector = deprecated_function(match_form_params) # form_params
       
   786 #kwargs_selector = deprecated_function(match_kwargs) # expected_kwargs
       
   787 
       
   788 
       
   789 def require_group_compat(registered):
       
   790     def plug_selector(cls, vreg):
       
   791         cls = registered(cls, vreg)
       
   792         if getattr(cls, 'require_groups', None):
       
   793             warn('use "use match_user_groups(group1, group2)" instead of using require_groups',
       
   794                  DeprecationWarning)
       
   795             cls.__selectors__ += (match_user_groups(cls.require_groups),)
       
   796         return cls
       
   797     return classmethod(plug_selector)
       
   798 
       
   799 def accepts_compat(registered):
       
   800     def plug_selector(cls, vreg):
       
   801         cls = registered(cls, vreg)
       
   802         if getattr(cls, 'accepts', None):
       
   803             warn('use "use match_user_groups(group1, group2)" instead of using require_groups',
       
   804                  DeprecationWarning)
       
   805             cls.__selectors__ += (implements(*cls.accepts),)
       
   806         return cls
       
   807     return classmethod(plug_selector)