|
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'] |