selectors.py
branchtls-sprint
changeset 1301 4596ce9bb4dc
parent 1263 01152fffd593
child 1472 96e06e623494
equal deleted inserted replaced
1300:62d2b890a980 1301:4596ce9bb4dc
   111         TRACED_OIDS = ()
   111         TRACED_OIDS = ()
   112         return traceback is None
   112         return traceback is None
   113 
   113 
   114 
   114 
   115 # abstract selectors ##########################################################
   115 # abstract selectors ##########################################################
       
   116 
   116 class PartialSelectorMixIn(object):
   117 class PartialSelectorMixIn(object):
   117     """convenience mix-in for selectors that will look into the containing
   118     """convenience mix-in for selectors that will look into the containing
   118     class to find missing information.
   119     class to find missing information.
   119     
   120     
   120     cf. `cubicweb.web.action.LinkToEntityAction` for instance
   121     cf. `cubicweb.web.action.LinkToEntityAction` for instance
   121     """
   122     """
   122     def __call__(self, cls, *args, **kwargs):
   123     def __call__(self, cls, *args, **kwargs):
   123         self.complete(cls)
   124         self.complete(cls)
   124         return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs)
   125         return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs)
       
   126 
       
   127 
       
   128 class ImplementsMixIn(object):
       
   129     """mix-in class for selectors checking implemented interfaces of something
       
   130     """
       
   131     def __init__(self, *expected_ifaces):
       
   132         super(ImplementsMixIn, self).__init__()
       
   133         self.expected_ifaces = expected_ifaces
       
   134 
       
   135     def __str__(self):
       
   136         return '%s(%s)' % (self.__class__.__name__,
       
   137                            ','.join(str(s) for s in self.expected_ifaces))
       
   138     
       
   139     def score_interfaces(self, cls_or_inst, cls):
       
   140         score = 0
       
   141         vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
       
   142         for iface in self.expected_ifaces:
       
   143             if isinstance(iface, basestring):
       
   144                 # entity type
       
   145                 try:
       
   146                     iface = vreg.etype_class(iface)
       
   147                 except KeyError:
       
   148                     continue # entity type not in the schema
       
   149             if implements_iface(cls_or_inst, iface):
       
   150                 if getattr(iface, '__registry__', None) == 'etypes':
       
   151                     # adjust score if the interface is an entity class
       
   152                     if iface is cls:
       
   153                         score += len(eschema.ancestors()) + 4
       
   154                     else: 
       
   155                         parents = [e.type for e in eschema.ancestors()]
       
   156                         for index, etype in enumerate(reversed(parents)):
       
   157                             basecls = vreg.etype_class(etype)
       
   158                             if iface is basecls:
       
   159                                 score += index + 3
       
   160                                 break
       
   161                         else: # Any
       
   162                             score += 1
       
   163                 else:
       
   164                     # implenting an interface takes precedence other special Any
       
   165                     # interface
       
   166                     score += 2
       
   167         return score
       
   168 
   125 
   169 
   126 class EClassSelector(Selector):
   170 class EClassSelector(Selector):
   127     """abstract class for selectors working on the entity classes of the result
   171     """abstract class for selectors working on the entity classes of the result
   128     set. Its __call__ method has the following behaviour:
   172     set. Its __call__ method has the following behaviour:
   129 
   173 
   171 
   215 
   172 class EntitySelector(EClassSelector):
   216 class EntitySelector(EClassSelector):
   173     """abstract class for selectors working on the entity instances of the
   217     """abstract class for selectors working on the entity instances of the
   174     result set. Its __call__ method has the following behaviour:
   218     result set. Its __call__ method has the following behaviour:
   175 
   219 
       
   220     * if 'entity' find in kwargs, return the score returned by the score_entity
       
   221       method for this entity
   176     * if row is specified, return the score returned by the score_entity method
   222     * if row is specified, return the score returned by the score_entity method
   177       called with the entity instance found in the specified cell
   223       called with the entity instance found in the specified cell
   178     * else return the sum of score returned by the score_entity method for each
   224     * else return the sum of score returned by the score_entity method for each
   179       entity found in the specified column, unless:
   225       entity found in the specified column, unless:
   180       - `once_is_enough` is True, in which case the first non-zero score is
   226       - `once_is_enough` is True, in which case the first non-zero score is
   186           considered.
   232           considered.
   187     """
   233     """
   188     
   234     
   189     @lltrace
   235     @lltrace
   190     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   236     def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
   191         if not rset:
   237         if not rset and not kwargs.get('entity'):
   192             return 0
   238             return 0
   193         score = 0
   239         score = 0
   194         if row is None:
   240         if kwargs.get('entity'):
       
   241             score = self.score_entity(kwargs['entity'])
       
   242         elif row is None:
   195             for row, rowvalue in enumerate(rset.rows):
   243             for row, rowvalue in enumerate(rset.rows):
   196                 if rowvalue[col] is None: # outer join
   244                 if rowvalue[col] is None: # outer join
   197                     continue
   245                     continue
   198                 escore = self.score(req, rset, row, col)
   246                 escore = self.score(req, rset, row, col)
   199                 if not escore and not self.once_is_enough:
   247                 if not escore and not self.once_is_enough:
   506             return 0
   554             return 0
   507 
   555 
   508 
   556 
   509 # not so basic selectors ######################################################
   557 # not so basic selectors ######################################################
   510 
   558 
   511 class implements(EClassSelector):
   559 class implements(ImplementsMixIn, EClassSelector):
   512     """accept if entity class found in the result set implements at least one
   560     """accept if entity classes found in the result set implements at least one
   513     of the interfaces given as argument. Returned score is the number of
   561     of the interfaces given as argument. Returned score is the number of
   514     implemented interfaces.
   562     implemented interfaces.
   515 
   563 
   516     See `EClassSelector` documentation for behaviour when row is not specified.
   564     See `EClassSelector` documentation for behaviour when row is not specified.
   517 
   565 
   521                              registry (at selection time)
   569                              registry (at selection time)
   522                              
   570                              
   523     note: when interface is an entity class, the score will reflect class
   571     note: when interface is an entity class, the score will reflect class
   524           proximity so the most specific object'll be selected
   572           proximity so the most specific object'll be selected
   525     """
   573     """
   526     def __init__(self, *expected_ifaces):
       
   527         super(implements, self).__init__()
       
   528         self.expected_ifaces = expected_ifaces
       
   529 
       
   530     def __str__(self):
       
   531         return '%s(%s)' % (self.__class__.__name__,
       
   532                            ','.join(str(s) for s in self.expected_ifaces))
       
   533     
       
   534     def score_class(self, eclass, req):
   574     def score_class(self, eclass, req):
   535         score = 0
   575         return self.score_interfaces(eclass, eclass)
   536         for iface in self.expected_ifaces:
       
   537             if isinstance(iface, basestring):
       
   538                 # entity type
       
   539                 try:
       
   540                     iface = eclass.vreg.etype_class(iface)
       
   541                 except KeyError:
       
   542                     continue # entity type not in the schema
       
   543             if implements_iface(eclass, iface):
       
   544                 if getattr(iface, '__registry__', None) == 'etypes':
       
   545                     # adjust score if the interface is an entity class
       
   546                     if iface is eclass:
       
   547                         score += len(eclass.e_schema.ancestors()) + 4
       
   548                     else: 
       
   549                         parents = [e.type for e in eclass.e_schema.ancestors()]
       
   550                         for index, etype in enumerate(reversed(parents)):
       
   551                             basecls = eclass.vreg.etype_class(etype)
       
   552                             if iface is basecls:
       
   553                                 score += index + 3
       
   554                                 break
       
   555                         else: # Any
       
   556                             score += 1
       
   557                 else:
       
   558                     # implenting an interface takes precedence other special Any
       
   559                     # interface
       
   560                     score += 2
       
   561         return score
       
   562 
   576 
   563 
   577 
   564 class specified_etype_implements(implements):
   578 class specified_etype_implements(implements):
   565     """accept if entity class specified using an 'etype' parameters in name
   579     """accept if entity class specified using an 'etype' parameters in name
   566     argument or request form implements at least one of the interfaces given as
   580     argument or request form implements at least one of the interfaces given as
   583             try:
   597             try:
   584                 etype = kwargs['etype']
   598                 etype = kwargs['etype']
   585             except KeyError:
   599             except KeyError:
   586                 return 0
   600                 return 0
   587         return self.score_class(cls.vreg.etype_class(etype), req)
   601         return self.score_class(cls.vreg.etype_class(etype), req)
       
   602 
       
   603 
       
   604 class entity_implements(ImplementsMixIn, EntitySelector):
       
   605     """accept if entity instances found in the result set implements at least one
       
   606     of the interfaces given as argument. Returned score is the number of
       
   607     implemented interfaces.
       
   608 
       
   609     See `EntitySelector` documentation for behaviour when row is not specified.
       
   610 
       
   611     :param *expected_ifaces: expected interfaces. An interface may be a class
       
   612                              or an entity type (e.g. `basestring`) in which case
       
   613                              the associated class will be searched in the
       
   614                              registry (at selection time)
       
   615                              
       
   616     note: when interface is an entity class, the score will reflect class
       
   617           proximity so the most specific object'll be selected
       
   618     """    
       
   619     def score_entity(self, entity):
       
   620         return self.score_interfaces(entity, entity.__class__)
   588 
   621 
   589 
   622 
   590 class relation_possible(EClassSelector):
   623 class relation_possible(EClassSelector):
   591     """accept if entity class found in the result set support the relation.
   624     """accept if entity class found in the result set support the relation.
   592 
   625