common/selectors.py
changeset 0 b97547f5f1fa
child 142 0425ee84cfa6
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     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 :organization: Logilab
       
     7 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     8 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     9 """
       
    10 
       
    11 __docformat__ = "restructuredtext en"
       
    12 
       
    13 from logilab.common.compat import all
       
    14 
       
    15 from cubicweb import Unauthorized
       
    16 from cubicweb.cwvreg import DummyCursorError
       
    17 from cubicweb.vregistry import chainall, chainfirst
       
    18 from cubicweb.cwconfig import CubicWebConfiguration
       
    19 from cubicweb.schema import split_expression
       
    20 
       
    21 
       
    22 def lltrace(selector):
       
    23     # don't wrap selectors if not in development mode
       
    24     if CubicWebConfiguration.mode == 'installed':
       
    25         return selector
       
    26     def traced(cls, *args, **kwargs):
       
    27         ret = selector(cls, *args, **kwargs)
       
    28         cls.lldebug('selector %s returned %s for %s', selector.__name__, ret, cls)
       
    29         return ret
       
    30     return traced
       
    31     
       
    32 # very basic selectors ########################################################
       
    33 
       
    34 def yes_selector(cls, *args, **kwargs):
       
    35     """accept everything"""
       
    36     return 1
       
    37 
       
    38 @lltrace
       
    39 def norset_selector(cls, req, rset, *args, **kwargs):
       
    40     """accept no result set"""
       
    41     if rset is None:
       
    42         return 1
       
    43     return 0
       
    44 
       
    45 @lltrace
       
    46 def rset_selector(cls, req, rset, *args, **kwargs):
       
    47     """accept result set, whatever the number of result"""
       
    48     if rset is not None:
       
    49         return 1
       
    50     return 0
       
    51 
       
    52 @lltrace
       
    53 def anyrset_selector(cls, req, rset, *args, **kwargs):
       
    54     """accept any non empty result set"""
       
    55     if rset and rset.rowcount: # XXX if rset is not None and rset.rowcount > 0:
       
    56         return 1
       
    57     return 0
       
    58     
       
    59 @lltrace
       
    60 def emptyrset_selector(cls, req, rset, *args, **kwargs):
       
    61     """accept empty result set"""
       
    62     if rset is not None and rset.rowcount == 0:
       
    63         return 1
       
    64     return 0
       
    65 
       
    66 @lltrace
       
    67 def onelinerset_selector(cls, req, rset, row=None, *args, **kwargs):
       
    68     """accept result set with a single line of result"""
       
    69     if rset is not None and (row is not None or rset.rowcount == 1):
       
    70         return 1
       
    71     return 0
       
    72 
       
    73 @lltrace
       
    74 def twolinerset_selector(cls, req, rset, *args, **kwargs):
       
    75     """accept result set with at least two lines of result"""
       
    76     if rset is not None and rset.rowcount > 1:
       
    77         return 1
       
    78     return 0
       
    79 
       
    80 @lltrace
       
    81 def twocolrset_selector(cls, req, rset, *args, **kwargs):
       
    82     """accept result set with at least one line and two columns of result"""
       
    83     if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1:
       
    84         return 1
       
    85     return 0
       
    86 
       
    87 @lltrace
       
    88 def largerset_selector(cls, req, rset, *args, **kwargs):
       
    89     """accept result sets with more rows than the page size
       
    90     """
       
    91     if rset is None or len(rset) <= req.property_value('navigation.page-size'):
       
    92         return 0
       
    93     return 1
       
    94 
       
    95 @lltrace
       
    96 def sortedrset_selector(cls, req, rset, row=None, col=None):
       
    97     """accept sorted result set"""
       
    98     rqlst = rset.syntax_tree()
       
    99     if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
       
   100         return 0
       
   101     return 2
       
   102 
       
   103 @lltrace
       
   104 def oneetyperset_selector(cls, req, rset, *args, **kwargs):
       
   105     """accept result set where entities in the first columns are all of the
       
   106     same type
       
   107     """
       
   108     if len(rset.column_types(0)) != 1:
       
   109         return 0
       
   110     return 1
       
   111 
       
   112 @lltrace
       
   113 def multitype_selector(cls, req, rset, **kwargs):
       
   114     """accepts resultsets containing several entity types"""
       
   115     if rset:
       
   116         etypes = rset.column_types(0)
       
   117         if len(etypes) > 1:
       
   118             return 1
       
   119     return 0
       
   120 
       
   121 @lltrace
       
   122 def searchstate_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   123     """extend the anyrset_selector by checking if the current search state
       
   124     is in a .search_states attribute of the wrapped class
       
   125 
       
   126     search state should be either 'normal' or 'linksearch' (eg searching for an
       
   127     object to create a relation with another)
       
   128     """
       
   129     try:
       
   130         if not req.search_state[0] in cls.search_states:
       
   131             return 0
       
   132     except AttributeError:
       
   133         return 1 # class don't care about search state, accept it
       
   134     return 1
       
   135 
       
   136 @lltrace
       
   137 def anonymous_selector(cls, req, *args, **kwargs):
       
   138     """accept if user is anonymous"""
       
   139     if req.cnx.anonymous_connection:
       
   140         return 1
       
   141     return 0
       
   142 
       
   143 @lltrace
       
   144 def not_anonymous_selector(cls, req, *args, **kwargs):
       
   145     """accept if user is anonymous"""
       
   146     return not anonymous_selector(cls, req, *args, **kwargs)
       
   147 
       
   148 
       
   149 # not so basic selectors ######################################################
       
   150 
       
   151 @lltrace
       
   152 def req_form_params_selector(cls, req, *args, **kwargs):
       
   153     """check if parameters specified by the form_params attribute on
       
   154     the wrapped class are specified in request form parameters
       
   155     """
       
   156     score = 0
       
   157     for param in cls.form_params:
       
   158         val = req.form.get(param)
       
   159         if not val:
       
   160             return 0
       
   161         score += 1
       
   162     return score + 1
       
   163 
       
   164 @lltrace
       
   165 def kwargs_selector(cls, req, *args, **kwargs):
       
   166     """check if arguments specified by the expected_kwargs attribute on
       
   167     the wrapped class are specified in given named parameters
       
   168     """
       
   169     values = []
       
   170     for arg in cls.expected_kwargs:
       
   171         if not arg in kwargs:
       
   172             return 0
       
   173     return 1
       
   174 
       
   175 @lltrace
       
   176 def etype_form_selector(cls, req, *args, **kwargs):
       
   177     """check etype presence in request form *and* accepts conformance"""
       
   178     if 'etype' not in req.form and 'etype' not in kwargs:
       
   179         return 0
       
   180     try:
       
   181         etype = req.form['etype']
       
   182     except KeyError:
       
   183         etype = kwargs['etype']
       
   184     # value is a list or a tuple if web request form received several
       
   185     # values for etype parameter
       
   186     assert isinstance(etype, basestring), "got multiple etype parameters in req.form"
       
   187     if 'Any' in cls.accepts:
       
   188         return 1
       
   189     # no Any found, we *need* exact match
       
   190     if etype not in cls.accepts:
       
   191         return 0
       
   192     # exact match must return a greater value than 'Any'-match
       
   193     return 2
       
   194 
       
   195 @lltrace
       
   196 def _nfentity_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   197     """accept non final entities
       
   198     if row is not specified, use the first one
       
   199     if col is not specified, use the first one
       
   200     """
       
   201     etype = rset.description[row or 0][col or 0]
       
   202     if etype is None: # outer join
       
   203         return 0
       
   204     if cls.schema.eschema(etype).is_final():
       
   205         return 0
       
   206     return 1
       
   207 
       
   208 @lltrace
       
   209 def _rqlcondition_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   210     """accept single entity result set if the entity match an rql condition
       
   211     """
       
   212     if cls.condition:
       
   213         eid = rset[row or 0][col or 0]
       
   214         if 'U' in frozenset(split_expression(cls.condition)):
       
   215             rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % cls.condition
       
   216         else:
       
   217             rql = 'Any X WHERE X eid %%(x)s, %s' % cls.condition
       
   218         try:
       
   219             return len(req.execute(rql, {'x': eid, 'u': req.user.eid}, 'x'))
       
   220         except Unauthorized:
       
   221             return 0
       
   222         
       
   223     return 1
       
   224 
       
   225 @lltrace
       
   226 def _interface_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   227     """accept uniform result sets, and apply the following rules:
       
   228 
       
   229     * wrapped class must have a accepts_interfaces attribute listing the
       
   230       accepted ORed interfaces
       
   231     * if row is None, return the sum of values returned by the method
       
   232       for each entity's class in the result set. If any score is 0,
       
   233       return 0.
       
   234     * if row is specified, return the value returned by the method with
       
   235       the entity's class of this row
       
   236     """
       
   237     score = 0
       
   238     # check 'accepts' to give priority to more specific classes
       
   239     if row is None:
       
   240         for etype in rset.column_types(col or 0):
       
   241             eclass = cls.vreg.etype_class(etype)
       
   242             escore = 0
       
   243             for iface in cls.accepts_interfaces:
       
   244                 escore += iface.is_implemented_by(eclass)
       
   245             if not escore:
       
   246                 return 0
       
   247             score += escore
       
   248             if eclass.id in getattr(cls, 'accepts', ()):
       
   249                 score += 2
       
   250         return score + 1
       
   251     etype = rset.description[row][col or 0]
       
   252     if etype is None: # outer join
       
   253         return 0
       
   254     eclass = cls.vreg.etype_class(etype)
       
   255     for iface in cls.accepts_interfaces:
       
   256         score += iface.is_implemented_by(eclass)
       
   257     if score:
       
   258         if eclass.id in getattr(cls, 'accepts', ()):
       
   259             score += 2
       
   260         else:
       
   261             score += 1
       
   262     return score
       
   263 
       
   264 @lltrace
       
   265 def score_entity_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   266     if row is None:
       
   267         rows = xrange(rset.rowcount)
       
   268     else:
       
   269         rows = (row,)
       
   270     for row in rows:
       
   271         try:
       
   272             score = cls.score_entity(rset.get_entity(row, col or 0))
       
   273         except DummyCursorError:
       
   274             # get a dummy cursor error, that means we are currently
       
   275             # using a dummy rset to list possible views for an entity
       
   276             # type, not for an actual result set. In that case, we
       
   277             # don't care of the value, consider the object as selectable
       
   278             return 1
       
   279         if not score:
       
   280             return 0
       
   281     return 1
       
   282 
       
   283 @lltrace
       
   284 def accept_rset_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   285     """simply delegate to cls.accept_rset method"""
       
   286     return cls.accept_rset(req, rset, row=row, col=col)
       
   287 
       
   288 @lltrace
       
   289 def but_etype_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   290     """restrict the searchstate_accept_one_selector to exclude entity's type
       
   291     refered by the .etype attribute
       
   292     """
       
   293     if rset.description[row or 0][col or 0] == cls.etype:
       
   294         return 0
       
   295     return 1
       
   296 
       
   297 @lltrace
       
   298 def etype_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   299     """only check if the user has read access on the entity's type refered
       
   300     by the .etype attribute and on the relations's type refered by the
       
   301     .rtype attribute if set.
       
   302     """
       
   303     schema = cls.schema
       
   304     perm = getattr(cls, 'require_permission', 'read')
       
   305     if hasattr(cls, 'etype'):
       
   306         eschema = schema.eschema(cls.etype)
       
   307         if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
       
   308             return 0
       
   309     if hasattr(cls, 'rtype'):
       
   310         if not schema.rschema(cls.rtype).has_perm(req, perm):
       
   311             return 0
       
   312     return 1
       
   313 
       
   314 @lltrace
       
   315 def accept_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   316     if hasattr(cls, 'rtype'):
       
   317         if row is None:
       
   318             for etype in rset.column_types(col or 0):
       
   319                 if not cls.relation_possible(etype):
       
   320                     return 0
       
   321         elif not cls.relation_possible(rset.description[row][col or 0]):
       
   322             return 0
       
   323     return 1
       
   324 
       
   325 @lltrace
       
   326 def one_has_relation_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   327     """check if the user has read access on the relations's type refered by the
       
   328     .rtype attribute of the class, and if at least one entity type in the
       
   329     result set has this relation.
       
   330     """
       
   331     schema = cls.schema
       
   332     perm = getattr(cls, 'require_permission', 'read')
       
   333     if not schema.rschema(cls.rtype).has_perm(req, perm):
       
   334         return 0
       
   335     if row is None:
       
   336         for etype in rset.column_types(col or 0):
       
   337             if cls.relation_possible(etype):
       
   338                 return 1
       
   339     elif cls.relation_possible(rset.description[row][col or 0]):
       
   340         return 1
       
   341     return 0
       
   342 
       
   343 @lltrace
       
   344 def in_group_selector(cls, req, rset=None, row=None, col=None, **kwargs):
       
   345     """select according to user's groups"""
       
   346     if not cls.require_groups:
       
   347         return 1
       
   348     user = req.user
       
   349     if user is None:
       
   350         return int('guests' in cls.require_groups)
       
   351     score = 0
       
   352     if 'owners' in cls.require_groups and rset:
       
   353         if row is not None:
       
   354             eid = rset[row][col or 0]
       
   355             if user.owns(eid):
       
   356                 score = 1
       
   357         else:
       
   358             score = all(user.owns(r[col or 0]) for r in rset)
       
   359     score += user.matching_groups(cls.require_groups)
       
   360     if score:
       
   361         # add 1 so that an object with one matching group take priority
       
   362         # on an object without require_groups
       
   363         return score + 1 
       
   364     return 0
       
   365 
       
   366 @lltrace
       
   367 def add_etype_selector(cls, req, rset, row=None, col=None, **kwargs):
       
   368     """only check if the user has add access on the entity's type refered
       
   369     by the .etype attribute.
       
   370     """
       
   371     if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
       
   372         return 0
       
   373     return 1
       
   374 
       
   375 @lltrace
       
   376 def contextprop_selector(cls, req, rset, row=None, col=None, context=None,
       
   377                           **kwargs):
       
   378     propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
       
   379     if not propval:
       
   380         propval = cls.context
       
   381     if context is not None and propval is not None and context != propval:
       
   382         return 0
       
   383     return 1
       
   384 
       
   385 @lltrace
       
   386 def primaryview_selector(cls, req, rset, row=None, col=None, view=None,
       
   387                           **kwargs):
       
   388     if view is not None and not view.is_primary():
       
   389         return 0
       
   390     return 1
       
   391 
       
   392 
       
   393 # compound selectors ##########################################################
       
   394 
       
   395 nfentity_selector = chainall(anyrset_selector, _nfentity_selector)
       
   396 interface_selector = chainall(nfentity_selector, _interface_selector)
       
   397 
       
   398 accept_selector = chainall(nfentity_selector, accept_rset_selector)
       
   399 accept_one_selector = chainall(onelinerset_selector, accept_selector)
       
   400 
       
   401 rqlcondition_selector = chainall(nfentity_selector,
       
   402                                  onelinerset_selector,
       
   403                                  _rqlcondition_selector)
       
   404 
       
   405 searchstate_accept_selector = chainall(anyrset_selector, searchstate_selector,
       
   406                                        accept_selector)
       
   407 searchstate_accept_one_selector = chainall(anyrset_selector, searchstate_selector,
       
   408                                            accept_selector, rqlcondition_selector)
       
   409 searchstate_accept_one_but_etype_selector = chainall(searchstate_accept_one_selector,
       
   410                                                      but_etype_selector)
       
   411 
       
   412 __all__ = [name for name in globals().keys() if name.endswith('selector')]
       
   413 __all__ += ['chainall', 'chainfirst']