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 |