274 |
303 |
275 def score_entity(self, entity): |
304 def score_entity(self, entity): |
276 raise NotImplementedError() |
305 raise NotImplementedError() |
277 |
306 |
278 |
307 |
279 # very basic selectors ######################################################## |
308 class ExpectedValueSelector(Selector): |
|
309 """Take a list of expected values as initializer argument, check |
|
310 _get_value method return one of these expected values. |
|
311 """ |
|
312 def __init__(self, *expected): |
|
313 assert expected, self |
|
314 self.expected = frozenset(expected) |
|
315 |
|
316 def __str__(self): |
|
317 return '%s(%s)' % (self.__class__.__name__, |
|
318 ','.join(sorted(str(s) for s in self.expected))) |
|
319 |
|
320 @lltrace |
|
321 def __call__(self, cls, req, **kwargs): |
|
322 if self._get_value(cls, req, **kwargs) in self.expected: |
|
323 return 1 |
|
324 return 0 |
|
325 |
|
326 def _get_value(self, cls, req, **kwargs): |
|
327 raise NotImplementedError() |
|
328 |
|
329 |
|
330 # bare selectors ############################################################## |
|
331 |
|
332 class match_kwargs(ExpectedValueSelector): |
|
333 """Return non-zero score if parameter names specified as initializer |
|
334 arguments are specified in the input context. When multiple parameters are |
|
335 specified, all of them should be specified in the input context. Return a |
|
336 score corresponding to the number of expected parameters. |
|
337 """ |
|
338 |
|
339 @lltrace |
|
340 def __call__(self, cls, req, **kwargs): |
|
341 for arg in self.expected: |
|
342 if not arg in kwargs: |
|
343 return 0 |
|
344 return len(self.expected) |
|
345 |
|
346 |
|
347 class appobject_selectable(Selector): |
|
348 """return 1 if another appobject is selectable using the same input context. |
|
349 |
|
350 Initializer arguments: |
|
351 * `registry`, a registry name |
|
352 * `regid`, an object identifier in this registry |
|
353 """ |
|
354 def __init__(self, registry, regid): |
|
355 self.registry = registry |
|
356 self.regid = regid |
|
357 |
|
358 def __call__(self, cls, req, **kwargs): |
|
359 try: |
|
360 req.vreg[self.registry].select(self.regid, req, **kwargs) |
|
361 return 1 |
|
362 except NoSelectableObject: |
|
363 return 0 |
|
364 |
|
365 |
|
366 # rset selectors ############################################################## |
280 |
367 |
281 @objectify_selector |
368 @objectify_selector |
282 @lltrace |
369 @lltrace |
283 def none_rset(cls, req, rset=None, **kwargs): |
370 def none_rset(cls, req, rset=None, **kwargs): |
284 """accept no result set (e.g. given rset is None)""" |
371 """Return 1 if the result set is None (eg usually not specified).""" |
285 if rset is None: |
372 if rset is None: |
286 return 1 |
373 return 1 |
287 return 0 |
374 return 0 |
288 |
375 |
|
376 |
|
377 # XXX == ~ none_rset |
289 @objectify_selector |
378 @objectify_selector |
290 @lltrace |
379 @lltrace |
291 def any_rset(cls, req, rset=None, **kwargs): |
380 def any_rset(cls, req, rset=None, **kwargs): |
292 """accept result set, whatever the number of result it contains""" |
381 """Return 1 for any result set, whatever the number of rows in it, even 0.""" |
293 if rset is not None: |
382 if rset is not None: |
294 return 1 |
383 return 1 |
295 return 0 |
384 return 0 |
|
385 |
296 |
386 |
297 @objectify_selector |
387 @objectify_selector |
298 @lltrace |
388 @lltrace |
299 def nonempty_rset(cls, req, rset=None, **kwargs): |
389 def nonempty_rset(cls, req, rset=None, **kwargs): |
300 """accept any non empty result set""" |
390 """Return 1 for result set containing one ore more rows.""" |
301 if rset is not None and rset.rowcount: |
391 if rset is not None and rset.rowcount: |
302 return 1 |
392 return 1 |
303 return 0 |
393 return 0 |
304 |
394 |
|
395 |
|
396 # XXX == ~ nonempty_rset |
305 @objectify_selector |
397 @objectify_selector |
306 @lltrace |
398 @lltrace |
307 def empty_rset(cls, req, rset=None, **kwargs): |
399 def empty_rset(cls, req, rset=None, **kwargs): |
308 """accept empty result set""" |
400 """Return 1 for result set which doesn't contain any row.""" |
309 if rset is not None and rset.rowcount == 0: |
401 if rset is not None and rset.rowcount == 0: |
310 return 1 |
402 return 1 |
311 return 0 |
403 return 0 |
312 |
404 |
|
405 |
|
406 # XXX == multi_lines_rset(1) |
313 @objectify_selector |
407 @objectify_selector |
314 @lltrace |
408 @lltrace |
315 def one_line_rset(cls, req, rset=None, row=None, **kwargs): |
409 def one_line_rset(cls, req, rset=None, row=None, **kwargs): |
316 """if row is specified, accept result set with a single line of result, |
410 """Return 1 if the result set is of size 1 or if a specific row in the |
317 else accepts anyway |
411 result set is specified ('row' argument). |
318 """ |
412 """ |
319 if rset is not None and (row is not None or rset.rowcount == 1): |
413 if rset is not None and (row is not None or rset.rowcount == 1): |
320 return 1 |
414 return 1 |
321 return 0 |
415 return 0 |
322 |
416 |
323 @objectify_selector |
417 |
324 @lltrace |
418 class multi_lines_rset(Selector): |
325 def two_lines_rset(cls, req, rset=None, **kwargs): |
419 """If `nb`is specified, return 1 if the result set has exactly `nb` row of |
326 """accept result set with *at least* two lines of result""" |
420 result. Else (`nb` is None), return 1 if the result set contains *at least* |
327 if rset is not None and rset.rowcount > 1: |
421 two rows. |
328 return 1 |
422 """ |
329 return 0 |
423 def __init__(self, nb=None): |
330 |
424 self.expected = nb |
331 @objectify_selector |
425 |
332 @lltrace |
426 def match_expected(self, num): |
333 def two_cols_rset(cls, req, rset=None, **kwargs): |
427 if self.expected is None: |
334 """accept result set with at least one line and two columns of result""" |
428 return num > 1 |
335 if rset is not None and rset.rowcount and len(rset.rows[0]) > 1: |
429 return num == self.expected |
336 return 1 |
430 |
337 return 0 |
431 @lltrace |
|
432 def __call__(self, cls, req, rset=None, **kwargs): |
|
433 return rset is not None and self.match_expected(rset.rowcount) |
|
434 |
|
435 |
|
436 class multi_columns_rset(multi_lines_rset): |
|
437 """If `nb`is specified, return 1 if the result set has exactly `nb` column |
|
438 per row. Else (`nb` is None), return 1 if the result set contains *at least* |
|
439 two columns per row. Return 0 for empty result set. |
|
440 """ |
|
441 |
|
442 @lltrace |
|
443 def __call__(self, cls, req, rset=None, **kwargs): |
|
444 # 'or 0' since we *must not* return None |
|
445 return rset and self.match_expected(len(rset.rows[0])) or 0 |
|
446 |
338 |
447 |
339 @objectify_selector |
448 @objectify_selector |
340 @lltrace |
449 @lltrace |
341 def paginated_rset(cls, req, rset=None, **kwargs): |
450 def paginated_rset(cls, req, rset=None, **kwargs): |
342 """accept result set with more lines than the page size. |
451 """Return 1 for result set with more rows than a page size. |
343 |
452 |
344 Page size is searched in (respecting order): |
453 Page size is searched in (respecting order): |
345 * a page_size argument |
454 * a `page_size` argument |
346 * a page_size form parameters |
455 * a `page_size` form parameters |
347 * the navigation.page-size property |
456 * the :ref:`navigation.page-size` property |
348 """ |
457 """ |
|
458 if rset is None: |
|
459 return 0 |
349 page_size = kwargs.get('page_size') |
460 page_size = kwargs.get('page_size') |
350 if page_size is None: |
461 if page_size is None: |
351 page_size = req.form.get('page_size') |
462 page_size = req.form.get('page_size') |
352 if page_size is None: |
463 if page_size is None: |
353 page_size = req.property_value('navigation.page-size') |
464 page_size = req.property_value('navigation.page-size') |
354 else: |
465 else: |
355 page_size = int(page_size) |
466 page_size = int(page_size) |
356 if rset is None or rset.rowcount <= page_size: |
467 if rset.rowcount <= page_size: |
357 return 0 |
468 return 0 |
358 return 1 |
469 return 1 |
|
470 |
359 |
471 |
360 @objectify_selector |
472 @objectify_selector |
361 @lltrace |
473 @lltrace |
362 def sorted_rset(cls, req, rset=None, **kwargs): |
474 def sorted_rset(cls, req, rset=None, **kwargs): |
363 """accept sorted result set""" |
475 """Return 1 for sorted result set (e.g. from an RQL query containing an |
|
476 :ref:ORDERBY clause. |
|
477 """ |
|
478 if rset is None: |
|
479 return 0 |
364 rqlst = rset.syntax_tree() |
480 rqlst = rset.syntax_tree() |
365 if len(rqlst.children) > 1 or not rqlst.children[0].orderby: |
481 if len(rqlst.children) > 1 or not rqlst.children[0].orderby: |
366 return 0 |
482 return 0 |
367 return 2 |
483 return 2 |
368 |
484 |
|
485 |
|
486 # XXX == multi_etypes_rset(1) |
369 @objectify_selector |
487 @objectify_selector |
370 @lltrace |
488 @lltrace |
371 def one_etype_rset(cls, req, rset=None, col=0, **kwargs): |
489 def one_etype_rset(cls, req, rset=None, col=0, **kwargs): |
372 """accept result set where entities in the specified column (or 0) are all |
490 """Return 1 if the result set contains entities which are all of the same |
373 of the same type |
491 type in the column specified by the `col` argument of the input context, or |
|
492 in column 0. |
374 """ |
493 """ |
375 if rset is None: |
494 if rset is None: |
376 return 0 |
495 return 0 |
377 if len(rset.column_types(col)) != 1: |
496 if len(rset.column_types(col)) != 1: |
378 return 0 |
497 return 0 |
379 return 1 |
498 return 1 |
380 |
499 |
381 @objectify_selector |
500 |
382 @lltrace |
501 class multi_etypes_rset(multi_lines_rset): |
383 def two_etypes_rset(cls, req, rset=None, col=0, **kwargs): |
502 """If `nb` is specified, return 1 if the result set contains `nb` different |
384 """accept result set where entities in the specified column (or 0) are not |
503 types of entities in the column specified by the `col` argument of the input |
385 of the same type |
504 context, or in column 0. If `nb` is None, return 1 if the result set contains |
386 """ |
505 *at least* two different types of entities. |
387 if rset: |
506 """ |
388 etypes = rset.column_types(col) |
507 |
389 if len(etypes) > 1: |
508 @lltrace |
390 return 1 |
509 def __call__(self, cls, req, rset=None, col=0, **kwargs): |
391 return 0 |
510 # 'or 0' since we *must not* return None |
|
511 return rset and self.match_expected(len(rset.column_types(col))) or 0 |
|
512 |
|
513 |
|
514 # entity selectors ############################################################# |
392 |
515 |
393 class non_final_entity(EClassSelector): |
516 class non_final_entity(EClassSelector): |
394 """accept if entity type found in the result set is non final. |
517 """Return 1 for entity of a non final entity type(s). Remember, "final" |
395 |
518 entity types are String, Int, etc... This is equivalent to |
396 See `EClassSelector` documentation for behaviour when row is not specified. |
519 `implements('Any')` but more optimized. |
|
520 |
|
521 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
|
522 class lookup / score rules according to the input context. |
397 """ |
523 """ |
398 def score(self, cls, req, etype): |
524 def score(self, cls, req, etype): |
399 if etype in BASE_TYPES: |
525 if etype in BASE_TYPES: |
400 return 0 |
526 return 0 |
401 return 1 |
527 return 1 |
402 |
528 |
403 @objectify_selector |
529 def score_class(self, eclass, req): |
404 @lltrace |
530 return 1 # necessarily true if we're there |
405 def authenticated_user(cls, req, *args, **kwargs): |
531 |
406 """accept if user is authenticated""" |
532 |
407 if req.cnx.anonymous_connection: |
533 class implements(ImplementsMixIn, EClassSelector): |
408 return 0 |
534 """Return non-zero score for entity that are of the given type(s) or |
409 return 1 |
535 implements at least one of the given interface(s). If multiple arguments are |
410 |
536 given, matching one of them is enough. |
411 def anonymous_user(): |
537 |
412 return ~ authenticated_user() |
538 Entity types should be given as string, the corresponding class will be |
413 |
539 fetched from the entity types registry at selection time. |
414 @objectify_selector |
540 |
415 @lltrace |
541 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
416 def primary_view(cls, req, rset=None, row=None, col=0, view=None, **kwargs): |
542 class lookup / score rules according to the input context. |
417 """accept if view given as named argument is a primary view, or if no view |
543 |
418 is given |
544 .. note:: when interface is an entity class, the score will reflect class |
419 """ |
545 proximity so the most specific object will be selected. |
420 if view is not None and not view.is_primary(): |
546 """ |
421 return 0 |
547 def score_class(self, eclass, req): |
422 return 1 |
548 return self.score_interfaces(req, eclass, eclass) |
423 |
549 |
424 @objectify_selector |
550 |
425 @lltrace |
551 class score_entity(EntitySelector): |
426 def match_context_prop(cls, req, rset=None, row=None, col=0, context=None, |
552 """Return score according to an arbitrary function given as argument which |
427 **kwargs): |
553 will be called with input content entity as argument. |
428 """accept if: |
554 |
429 * no context given |
555 This is a very useful selector that will usually interest you since it |
430 * context (`basestring`) is matching the context property value for the |
556 allows a lot of things without having to write a specific selector. |
431 given cls |
557 |
432 """ |
558 See :class:`~cubicweb.selectors.EntitySelector` documentation for entity |
433 propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id)) |
559 lookup / score rules according to the input context. |
434 if not propval: |
560 """ |
435 propval = cls.context |
561 def __init__(self, scorefunc, once_is_enough=False): |
436 if context is not None and propval and context != propval: |
562 super(score_entity, self).__init__(once_is_enough) |
437 return 0 |
563 def intscore(*args, **kwargs): |
438 return 1 |
564 score = scorefunc(*args, **kwargs) |
439 |
565 if not score: |
440 |
|
441 class match_search_state(Selector): |
|
442 """accept if the current request search state is in one of the expected |
|
443 states given to the initializer |
|
444 |
|
445 :param expected: either 'normal' or 'linksearch' (eg searching for an |
|
446 object to create a relation with another) |
|
447 """ |
|
448 def __init__(self, *expected): |
|
449 assert expected, self |
|
450 self.expected = frozenset(expected) |
|
451 |
|
452 def __str__(self): |
|
453 return '%s(%s)' % (self.__class__.__name__, |
|
454 ','.join(sorted(str(s) for s in self.expected))) |
|
455 |
|
456 @lltrace |
|
457 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
|
458 try: |
|
459 if not req.search_state[0] in self.expected: |
|
460 return 0 |
566 return 0 |
461 except AttributeError: |
567 if isinstance(score, (int, long)): |
462 return 1 # class doesn't care about search state, accept it |
568 return score |
463 return 1 |
|
464 |
|
465 |
|
466 class match_form_params(match_search_state): |
|
467 """accept if parameters specified as initializer arguments are specified |
|
468 in request's form parameters |
|
469 |
|
470 :param *expected: parameters (eg `basestring`) which are expected to be |
|
471 found in request's form parameters |
|
472 """ |
|
473 |
|
474 @lltrace |
|
475 def __call__(self, cls, req, *args, **kwargs): |
|
476 score = 0 |
|
477 for param in self.expected: |
|
478 if not param in req.form: |
|
479 return 0 |
|
480 score += 1 |
|
481 return len(self.expected) |
|
482 |
|
483 |
|
484 class match_kwargs(match_search_state): |
|
485 """accept if parameters specified as initializer arguments are specified |
|
486 in named arguments given to the selector |
|
487 |
|
488 :param *expected: parameters (eg `basestring`) which are expected to be |
|
489 found in named arguments (kwargs) |
|
490 """ |
|
491 |
|
492 @lltrace |
|
493 def __call__(self, cls, req, *args, **kwargs): |
|
494 for arg in self.expected: |
|
495 if not arg in kwargs: |
|
496 return 0 |
|
497 return len(self.expected) |
|
498 |
|
499 |
|
500 class match_user_groups(match_search_state): |
|
501 """accept if logged users is in at least one of the given groups. Returned |
|
502 score is the number of groups in which the user is. |
|
503 |
|
504 If the special 'owners' group is given: |
|
505 * if row is specified check the entity at the given row/col is owned by the |
|
506 logged user |
|
507 * if row is not specified check all entities in col are owned by the logged |
|
508 user |
|
509 |
|
510 :param *required_groups: name of groups (`basestring`) in which the logged |
|
511 user should be |
|
512 """ |
|
513 |
|
514 @lltrace |
|
515 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
|
516 user = req.user |
|
517 if user is None: |
|
518 return int('guests' in self.expected) |
|
519 score = user.matching_groups(self.expected) |
|
520 if not score and 'owners' in self.expected and rset: |
|
521 if row is not None: |
|
522 if not user.owns(rset[row][col]): |
|
523 return 0 |
|
524 score = 1 |
|
525 else: |
|
526 score = all(user.owns(r[col]) for r in rset) |
|
527 return score |
|
528 |
|
529 |
|
530 class match_transition(match_search_state): |
|
531 @lltrace |
|
532 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
|
533 try: |
|
534 # XXX check this is a transition that apply to the object? |
|
535 if not kwargs['transition'].name in self.expected: |
|
536 return 0 |
|
537 except KeyError: |
|
538 return 0 |
|
539 return 1 |
|
540 |
|
541 |
|
542 class match_view(match_search_state): |
|
543 """accept if the current view is in one of the expected vid given to the |
|
544 initializer |
|
545 """ |
|
546 @lltrace |
|
547 def __call__(self, cls, req, rset=None, row=None, col=0, view=None, **kwargs): |
|
548 if view is None or not view.id in self.expected: |
|
549 return 0 |
|
550 return 1 |
|
551 |
|
552 |
|
553 class appobject_selectable(Selector): |
|
554 """accept with another appobject is selectable using selector's input |
|
555 context. |
|
556 |
|
557 :param registry: a registry name (`basestring`) |
|
558 :param oid: an object identifier (`basestring`) |
|
559 """ |
|
560 def __init__(self, registry, oid): |
|
561 self.registry = registry |
|
562 self.oid = oid |
|
563 |
|
564 def __call__(self, cls, req, **kwargs): |
|
565 try: |
|
566 cls.vreg[self.registry].select(self.oid, req, **kwargs) |
|
567 return 1 |
569 return 1 |
568 except NoSelectableObject: |
570 self.score_entity = intscore |
569 return 0 |
571 |
570 |
572 |
571 |
573 class relation_possible(EntitySelector): |
572 # not so basic selectors ###################################################### |
574 """Return 1 for entity that supports the relation, provided that the |
573 |
575 request's user may do some `action` on it (see below). |
574 class implements(ImplementsMixIn, EClassSelector): |
576 |
575 """accept if entity classes found in the result set implements at least one |
577 The relation is specified by the following initializer arguments: |
576 of the interfaces given as argument. Returned score is the number of |
578 |
577 implemented interfaces. |
579 * `rtype`, the name of the relation |
578 |
580 |
579 See `EClassSelector` documentation for behaviour when row is not specified. |
581 * `role`, the role of the entity in the relation, either 'subject' or |
580 |
582 'object', default to 'subject' |
581 :param *expected_ifaces: expected interfaces. An interface may be a class |
583 |
582 or an entity type (e.g. `basestring`) in which case |
584 * `target_etype`, optional name of an entity type that should be supported |
583 the associated class will be searched in the |
585 at the other end of the relation |
584 registry (at selection time) |
586 |
585 |
587 * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete', |
586 note: when interface is an entity class, the score will reflect class |
588 default to 'read') which must be granted to the user, else a 0 score will |
587 proximity so the most specific object'll be selected |
589 be returned |
588 """ |
590 |
589 def score_class(self, eclass, req): |
591 * `strict`, boolean (default to False) telling what to do when the user has |
590 return self.score_interfaces(eclass, eclass) |
592 not globally the permission for the action (eg the action is not granted |
591 |
593 to one of the user's groups) |
592 |
594 |
593 class specified_etype_implements(implements): |
595 - when strict is False, if there are some local role defined for this |
594 """accept if entity class specified using an 'etype' parameters in name |
596 action (e.g. using rql expressions), then the permission will be |
595 argument or request form implements at least one of the interfaces given as |
597 considered as granted |
596 argument. Returned score is the number of implemented interfaces. |
598 |
597 |
599 - when strict is True, then the permission will be actually checked for |
598 :param *expected_ifaces: expected interfaces. An interface may be a class |
600 each entity |
599 or an entity type (e.g. `basestring`) in which case |
601 |
600 the associated class will be searched in the |
602 Setting `strict` to True impacts performance for large result set since |
601 registry (at selection time) |
603 you'll then get the :class:`~cubicweb.selectors.EntitySelector` behaviour |
602 |
604 while otherwise you get the :class:`~cubicweb.selectors.EClassSelector`'s |
603 note: when interface is an entity class, the score will reflect class |
605 one. See those classes documentation for entity lookup / score rules |
604 proximity so the most specific object'll be selected |
606 according to the input context. |
605 """ |
607 """ |
606 |
608 |
607 @lltrace |
|
608 def __call__(self, cls, req, *args, **kwargs): |
|
609 try: |
|
610 etype = kwargs['etype'] |
|
611 except KeyError: |
|
612 try: |
|
613 etype = req.form['etype'] |
|
614 except KeyError: |
|
615 return 0 |
|
616 else: |
|
617 # only check this is a known type if etype comes from req.form, |
|
618 # else we want the error to propagate |
|
619 try: |
|
620 etype = cls.vreg.case_insensitive_etypes[etype.lower()] |
|
621 req.form['etype'] = etype |
|
622 except KeyError: |
|
623 return 0 |
|
624 score = self.score_class(cls.vreg['etypes'].etype_class(etype), req) |
|
625 if score: |
|
626 eschema = req.vreg.schema.eschema(etype) |
|
627 if eschema.has_local_role('add') or eschema.has_perm(req, 'add'): |
|
628 return score |
|
629 return 0 |
|
630 |
|
631 |
|
632 class entity_implements(ImplementsMixIn, EntitySelector): |
|
633 """accept if entity instances found in the result set implements at least one |
|
634 of the interfaces given as argument. Returned score is the number of |
|
635 implemented interfaces. |
|
636 |
|
637 See `EntitySelector` documentation for behaviour when row is not specified. |
|
638 |
|
639 :param *expected_ifaces: expected interfaces. An interface may be a class |
|
640 or an entity type (e.g. `basestring`) in which case |
|
641 the associated class will be searched in the |
|
642 registry (at selection time) |
|
643 |
|
644 note: when interface is an entity class, the score will reflect class |
|
645 proximity so the most specific object'll be selected |
|
646 """ |
|
647 def score_entity(self, entity): |
|
648 return self.score_interfaces(entity, entity.__class__) |
|
649 |
|
650 |
|
651 class relation_possible(EClassSelector): |
|
652 """accept if entity class found in the result set support the relation. |
|
653 |
|
654 See `EClassSelector` documentation for behaviour when row is not specified. |
|
655 |
|
656 :param rtype: a relation type (`basestring`) |
|
657 :param role: the role of the result set entity in the relation. 'subject' or |
|
658 'object', default to 'subject'. |
|
659 :param target_type: if specified, check the relation's end may be of this |
|
660 target type (`basestring`) |
|
661 :param action: a relation schema action (one of 'read', 'add', 'delete') |
|
662 which must be granted to the logged user, else a 0 score will |
|
663 be returned |
|
664 """ |
|
665 def __init__(self, rtype, role='subject', target_etype=None, |
609 def __init__(self, rtype, role='subject', target_etype=None, |
666 action='read', once_is_enough=False): |
610 action='read', strict=False, **kwargs): |
667 super(relation_possible, self).__init__(once_is_enough) |
611 super(relation_possible, self).__init__(**kwargs) |
668 self.rtype = rtype |
612 self.rtype = rtype |
669 self.role = role |
613 self.role = role |
670 self.target_etype = target_etype |
614 self.target_etype = target_etype |
671 self.action = action |
615 self.action = action |
672 |
616 self.strict = strict |
673 @lltrace |
617 |
674 def __call__(self, cls, req, *args, **kwargs): |
618 # hack hack hack |
675 rschema = cls.schema.rschema(self.rtype) |
619 def __call__(self, cls, req, **kwargs): |
676 if not (rschema.has_perm(req, self.action) |
620 if self.strict: |
677 or rschema.has_local_role(self.action)): |
621 return EntitySelector.__call__(self, cls, req, **kwargs) |
678 return 0 |
622 return EClassSelector.__call__(self, cls, req, **kwargs) |
679 if self.action != 'read': |
623 |
680 if not (rschema.has_perm(req, 'read') |
624 def score(self, *args): |
681 or rschema.has_local_role('read')): |
625 if self.strict: |
682 return 0 |
626 return EntitySelector.score(self, *args) |
683 score = super(relation_possible, self).__call__(cls, req, *args, **kwargs) |
627 return EClassSelector.score(self, *args) |
684 return score |
628 |
685 |
629 def _get_rschema(self, eclass): |
686 def score_class(self, eclass, req): |
|
687 eschema = eclass.e_schema |
630 eschema = eclass.e_schema |
688 try: |
631 try: |
689 if self.role == 'object': |
632 if self.role == 'object': |
690 rschema = eschema.objrels[self.rtype] |
633 return eschema.objrels[self.rtype] |
691 else: |
634 else: |
692 rschema = eschema.subjrels[self.rtype] |
635 return eschema.subjrels[self.rtype] |
693 except KeyError: |
636 except KeyError: |
694 return 0 |
637 return None |
|
638 |
|
639 def score_class(self, eclass, req): |
|
640 rschema = self._get_rschema(eclass) |
|
641 if rschema is None: |
|
642 return 0 # relation not supported |
|
643 eschema = eclass.e_schema |
695 if self.target_etype is not None: |
644 if self.target_etype is not None: |
696 try: |
645 try: |
697 if self.role == 'subject': |
646 rdef = rschema.role_rdef(eschema, self.target_etype, self.role) |
698 return int(self.target_etype in rschema.objects(eschema)) |
647 if not rdef.may_have_permission(self.action, req): |
699 else: |
648 return 0 |
700 return int(self.target_etype in rschema.subjects(eschema)) |
|
701 except KeyError: |
649 except KeyError: |
702 return 0 |
650 return 0 |
|
651 else: |
|
652 return rschema.may_have_permission(self.action, req, eschema, self.role) |
|
653 return 1 |
|
654 |
|
655 def score_entity(self, entity): |
|
656 rschema = self._get_rschema(entity) |
|
657 if rschema is None: |
|
658 return 0 # relation not supported |
|
659 if self.target_etype is not None: |
|
660 rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role) |
|
661 if self.role == 'subject': |
|
662 if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid): |
|
663 return 0 |
|
664 elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid): |
|
665 return 0 |
703 return 1 |
666 return 1 |
704 |
667 |
705 |
668 |
706 class partial_relation_possible(PartialSelectorMixIn, relation_possible): |
669 class partial_relation_possible(PartialSelectorMixIn, relation_possible): |
707 """partial version of the relation_possible selector |
670 """Same as :class:~`cubicweb.selectors.relation_possible`, but will look for |
708 |
671 attributes of the selected class to get information which is otherwise |
709 The selector will look for class attributes to find its missing |
672 expected by the initializer, except for `action` and `strict` which are kept |
710 information. The list of attributes required on the class |
673 as initializer arguments. |
711 for this selector are: |
674 |
712 |
675 This is useful to predefine selector of an abstract class designed to be |
713 - `rtype`: same as `rtype` parameter of the `relation_possible` selector |
676 customized. |
714 |
677 """ |
715 - `role`: this attribute will be passed to the `cubicweb.role` function |
678 def __init__(self, action='read', **kwargs): |
716 to determine the role of class in the relation |
|
717 |
|
718 - `etype` (optional): the entity type on the other side of the relation |
|
719 |
|
720 :param action: a relation schema action (one of 'read', 'add', 'delete') |
|
721 which must be granted to the logged user, else a 0 score will |
|
722 be returned |
|
723 """ |
|
724 def __init__(self, action='read', once_is_enough=False): |
|
725 super(partial_relation_possible, self).__init__(None, None, None, |
679 super(partial_relation_possible, self).__init__(None, None, None, |
726 action, once_is_enough) |
680 action, **kwargs) |
727 |
681 |
728 def complete(self, cls): |
682 def complete(self, cls): |
729 self.rtype = cls.rtype |
683 self.rtype = cls.rtype |
730 self.role = role(cls) |
684 self.role = role(cls) |
731 self.target_etype = getattr(cls, 'etype', None) |
685 self.target_etype = getattr(cls, 'etype', None) |
732 |
686 if self.target_etype is not None: |
733 |
687 warn('[3.6] please rename etype to target_etype on %s' % cls, |
734 class may_add_relation(EntitySelector): |
688 DeprecationWarning) |
735 """accept if the relation can be added to an entity found in the result set |
689 else: |
736 by the logged user. |
690 self.target_etype = getattr(cls, 'target_etype', None) |
737 |
|
738 See `EntitySelector` documentation for behaviour when row is not specified. |
|
739 |
|
740 :param rtype: a relation type (`basestring`) |
|
741 :param role: the role of the result set entity in the relation. 'subject' or |
|
742 'object', default to 'subject'. |
|
743 """ |
|
744 |
|
745 def __init__(self, rtype, role='subject', once_is_enough=False): |
|
746 super(may_add_relation, self).__init__(once_is_enough) |
|
747 self.rtype = rtype |
|
748 self.role = role |
|
749 |
|
750 def score_entity(self, entity): |
|
751 rschema = entity.schema.rschema(self.rtype) |
|
752 if self.role == 'subject': |
|
753 if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid): |
|
754 return 0 |
|
755 elif not rschema.has_perm(entity.req, 'add', toeid=entity.eid): |
|
756 return 0 |
|
757 return 1 |
|
758 |
|
759 |
|
760 class partial_may_add_relation(PartialSelectorMixIn, may_add_relation): |
|
761 """partial version of the may_add_relation selector |
|
762 |
|
763 The selector will look for class attributes to find its missing |
|
764 information. The list of attributes required on the class |
|
765 for this selector are: |
|
766 |
|
767 - `rtype`: same as `rtype` parameter of the `relation_possible` selector |
|
768 |
|
769 - `role`: this attribute will be passed to the `cubicweb.role` function |
|
770 to determine the role of class in the relation. |
|
771 |
|
772 :param action: a relation schema action (one of 'read', 'add', 'delete') |
|
773 which must be granted to the logged user, else a 0 score will |
|
774 be returned |
|
775 """ |
|
776 def __init__(self, once_is_enough=False): |
|
777 super(partial_may_add_relation, self).__init__(None, None, once_is_enough) |
|
778 |
|
779 def complete(self, cls): |
|
780 self.rtype = cls.rtype |
|
781 self.role = role(cls) |
|
782 |
691 |
783 |
692 |
784 class has_related_entities(EntitySelector): |
693 class has_related_entities(EntitySelector): |
785 """accept if entity found in the result set has some linked entities using |
694 """Return 1 if entity support the specified relation and has some linked |
786 the specified relation (optionaly filtered according to the specified target |
695 entities by this relation , optionaly filtered according to the specified |
787 type). Checks first if the relation is possible. |
696 target type. |
788 |
697 |
789 See `EntitySelector` documentation for behaviour when row is not specified. |
698 The relation is specified by the following initializer arguments: |
790 |
699 |
791 :param rtype: a relation type (`basestring`) |
700 * `rtype`, the name of the relation |
792 :param role: the role of the result set entity in the relation. 'subject' or |
701 |
793 'object', default to 'subject'. |
702 * `role`, the role of the entity in the relation, either 'subject' or |
794 :param target_type: if specified, check the relation's end may be of this |
703 'object', default to 'subject'. |
795 target type (`basestring`) |
704 |
796 """ |
705 * `target_etype`, optional name of an entity type that should be found |
797 def __init__(self, rtype, role='subject', target_etype=None, |
706 at the other end of the relation |
798 once_is_enough=False): |
707 |
799 super(has_related_entities, self).__init__(once_is_enough) |
708 See :class:`~cubicweb.selectors.EntitySelector` documentation for entity |
|
709 lookup / score rules according to the input context. |
|
710 """ |
|
711 def __init__(self, rtype, role='subject', target_etype=None, **kwargs): |
|
712 super(has_related_entities, self).__init__(**kwargs) |
800 self.rtype = rtype |
713 self.rtype = rtype |
801 self.role = role |
714 self.role = role |
802 self.target_etype = target_etype |
715 self.target_etype = target_etype |
803 |
716 |
804 def score_entity(self, entity): |
717 def score_entity(self, entity): |
805 relpossel = relation_possible(self.rtype, self.role, self.target_etype) |
718 relpossel = relation_possible(self.rtype, self.role, self.target_etype) |
806 if not relpossel.score_class(entity.__class__, entity.req): |
719 if not relpossel.score_class(entity.__class__, entity._cw): |
807 return 0 |
720 return 0 |
808 rset = entity.related(self.rtype, self.role) |
721 rset = entity.related(self.rtype, self.role) |
809 if self.target_etype: |
722 if self.target_etype: |
810 return any(r for r in rset.description if r[0] == self.target_etype) |
723 return any(r for r in rset.description if r[0] == self.target_etype) |
811 return rset and 1 or 0 |
724 return rset and 1 or 0 |
812 |
725 |
813 |
726 |
814 class partial_has_related_entities(PartialSelectorMixIn, has_related_entities): |
727 class partial_has_related_entities(PartialSelectorMixIn, has_related_entities): |
815 """partial version of the has_related_entities selector |
728 """Same as :class:~`cubicweb.selectors.has_related_entity`, but will look |
816 |
729 for attributes of the selected class to get information which is otherwise |
817 The selector will look for class attributes to find its missing |
730 expected by the initializer. |
818 information. The list of attributes required on the class |
731 |
819 for this selector are: |
732 This is useful to predefine selector of an abstract class designed to be |
820 |
733 customized. |
821 - `rtype`: same as `rtype` parameter of the `relation_possible` selector |
734 """ |
822 |
735 def __init__(self, **kwargs): |
823 - `role`: this attribute will be passed to the `cubicweb.role` function |
736 super(partial_has_related_entities, self).__init__(None, None, None, |
824 to determine the role of class in the relation. |
737 **kwargs) |
825 |
738 |
826 - `etype` (optional): the entity type on the other side of the relation |
|
827 |
|
828 :param action: a relation schema action (one of 'read', 'add', 'delete') |
|
829 which must be granted to the logged user, else a 0 score will |
|
830 be returned |
|
831 """ |
|
832 def __init__(self, once_is_enough=False): |
|
833 super(partial_has_related_entities, self).__init__(None, None, |
|
834 None, once_is_enough) |
|
835 def complete(self, cls): |
739 def complete(self, cls): |
836 self.rtype = cls.rtype |
740 self.rtype = cls.rtype |
837 self.role = role(cls) |
741 self.role = role(cls) |
838 self.target_etype = getattr(cls, 'etype', None) |
742 self.target_etype = getattr(cls, 'etype', None) |
|
743 if self.target_etype is not None: |
|
744 warn('[3.6] please rename etype to target_etype on %s' % cls, |
|
745 DeprecationWarning) |
|
746 else: |
|
747 self.target_etype = getattr(cls, 'target_etype', None) |
839 |
748 |
840 |
749 |
841 class has_permission(EntitySelector): |
750 class has_permission(EntitySelector): |
842 """accept if user has the permission to do the requested action on a result |
751 """Return non-zero score if request's user has the permission to do the |
843 set entity. |
752 requested action on the entity. `action` is an entity schema action (eg one |
844 |
753 of 'read', 'add', 'delete', 'update'). |
845 * if row is specified, return 1 if user has the permission on the entity |
754 |
846 instance found in the specified cell |
755 Here are entity lookup / scoring rules: |
847 * else return a positive score if user has the permission for every entity |
756 |
848 in the found in the specified column |
757 * if `entity` is specified, check permission is granted for this entity |
849 |
758 |
850 note: None values (resulting from some outer join in the query) are not |
759 * elif `row` is specified, check permission is granted for the entity found |
851 considered. |
760 in the specified cell |
852 |
761 |
853 :param action: an entity schema action (eg 'read'/'add'/'delete'/'update') |
762 * else check permission is granted for each entity found in the column |
854 """ |
763 specified specified by the `col` argument or in column 0 |
855 def __init__(self, action, once_is_enough=False): |
764 """ |
856 super(has_permission, self).__init__(once_is_enough) |
765 def __init__(self, action): |
857 self.action = action |
766 self.action = action |
858 |
767 |
|
768 # don't use EntitySelector.__call__ but this optimized implementation to |
|
769 # avoid considering each entity when it's not necessary |
859 @lltrace |
770 @lltrace |
860 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
771 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
|
772 if kwargs.get('entity'): |
|
773 return self.score_entity(kwargs['entity']) |
861 if rset is None: |
774 if rset is None: |
862 return 0 |
775 return 0 |
863 user = req.user |
776 user = req.user |
864 action = self.action |
777 action = self.action |
865 if row is None: |
778 if row is None: |
866 score = 0 |
779 score = 0 |
867 need_local_check = [] |
780 need_local_check = [] |
868 geteschema = cls.schema.eschema |
781 geteschema = req.vreg.schema.eschema |
869 for etype in rset.column_types(0): |
782 for etype in rset.column_types(0): |
870 if etype in BASE_TYPES: |
783 if etype in BASE_TYPES: |
871 return 0 |
784 return 0 |
872 eschema = geteschema(etype) |
785 eschema = geteschema(etype) |
873 if not user.matching_groups(eschema.get_groups(action)): |
786 if not user.matching_groups(eschema.get_groups(action)): |
895 return 1 |
808 return 1 |
896 return 0 |
809 return 0 |
897 |
810 |
898 |
811 |
899 class has_add_permission(EClassSelector): |
812 class has_add_permission(EClassSelector): |
900 """accept if logged user has the add permission on entity class found in the |
813 """Return 1 if request's user has the add permission on entity type |
901 result set, and class is not a strict subobject. |
814 specified in the `etype` initializer argument, or according to entity found |
902 |
815 in the input content if not specified. |
903 See `EClassSelector` documentation for behaviour when row is not specified. |
816 |
904 """ |
817 It also check that then entity type is not a strict subobject (e.g. may only |
905 def score(self, cls, req, etype): |
818 be used as a composed of another entity). |
906 eschema = cls.schema.eschema(etype) |
819 |
907 if not (eschema.final or eschema.is_subobject(strict=True)) \ |
820 See :class:`~cubicweb.selectors.EClassSelector` documentation for entity |
908 and eschema.has_perm(req, 'add'): |
821 class lookup / score rules according to the input context when `etype` is |
909 return 1 |
822 not specified. |
910 return 0 |
823 """ |
|
824 def __init__(self, etype=None, **kwargs): |
|
825 super(has_add_permission, self).__init__(**kwargs) |
|
826 self.etype = etype |
|
827 |
|
828 @lltrace |
|
829 def __call__(self, cls, req, **kwargs): |
|
830 if self.etype is None: |
|
831 return super(has_add_permission, self).__call__(cls, req, **kwargs) |
|
832 return self.score(cls, req, self.etype) |
|
833 |
|
834 def score_class(self, eclass, req): |
|
835 eschema = eclass.e_schema |
|
836 if eschema.final or eschema.is_subobject(strict=True) \ |
|
837 or not eschema.has_perm(req, 'add'): |
|
838 return 0 |
|
839 return 1 |
911 |
840 |
912 |
841 |
913 class rql_condition(EntitySelector): |
842 class rql_condition(EntitySelector): |
914 """accept if an arbitrary rql return some results for an eid found in the |
843 """Return non-zero score if arbitrary rql specified in `expression` |
915 result set. Returned score is the number of items returned by the rql |
844 initializer argument return some results for entity found in the input |
|
845 context. Returned score is the number of items returned by the rql |
916 condition. |
846 condition. |
917 |
847 |
918 See `EntitySelector` documentation for behaviour when row is not specified. |
848 `expression` is expected to be a string containing an rql expression, which |
919 |
849 must use 'X' variable to represent the context entity and may use 'U' to |
920 :param expression: basestring containing an rql expression, which should use |
850 represent the request's user. |
921 X variable to represent the context entity and may use U |
851 |
922 to represent the logged user |
852 See :class:`~cubicweb.selectors.EntitySelector` documentation for entity |
923 |
853 lookup / score rules according to the input context. |
924 return the sum of the number of items returned by the rql condition as score |
|
925 or 0 at the first entity scoring to zero. |
|
926 """ |
854 """ |
927 def __init__(self, expression, once_is_enough=False): |
855 def __init__(self, expression, once_is_enough=False): |
928 super(rql_condition, self).__init__(once_is_enough) |
856 super(rql_condition, self).__init__(once_is_enough) |
929 if 'U' in frozenset(split_expression(expression)): |
857 if 'U' in frozenset(split_expression(expression)): |
930 rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression |
858 rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression |
931 else: |
859 else: |
932 rql = 'Any X WHERE X eid %%(x)s, %s' % expression |
860 rql = 'Any X WHERE X eid %%(x)s, %s' % expression |
933 self.rql = rql |
861 self.rql = rql |
934 |
862 |
|
863 def __repr__(self): |
|
864 return u'<rql_condition "%s" at %x>' % (self.rql, id(self)) |
|
865 |
935 def score(self, req, rset, row, col): |
866 def score(self, req, rset, row, col): |
936 try: |
867 try: |
937 return len(req.execute(self.rql, {'x': rset[row][col], |
868 return len(req.execute(self.rql, {'x': rset[row][col], |
938 'u': req.user.eid}, 'x')) |
869 'u': req.user.eid}, 'x')) |
939 except Unauthorized: |
870 except Unauthorized: |
940 return 0 |
871 return 0 |
941 |
872 |
942 def __repr__(self): |
873 # logged user selectors ######################################################## |
943 return u'<rql_condition "%s" at %x>' % (self.rql, id(self)) |
874 |
944 |
875 @objectify_selector |
945 |
876 @lltrace |
946 class but_etype(EntitySelector): |
877 def authenticated_user(cls, req, **kwargs): |
|
878 """Return 1 if the user is authenticated (e.g. not the anonymous user). |
|
879 |
|
880 May only be used on the web side, not on the data repository side. |
|
881 """ |
|
882 if req.cnx.anonymous_connection: |
|
883 return 0 |
|
884 return 1 |
|
885 |
|
886 |
|
887 # XXX == ~ authenticated_user() |
|
888 def anonymous_user(): |
|
889 """Return 1 if the user is not authenticated (e.g. is the anonymous user). |
|
890 |
|
891 May only be used on the web side, not on the data repository side. |
|
892 """ |
|
893 return ~ authenticated_user() |
|
894 |
|
895 |
|
896 class match_user_groups(ExpectedValueSelector): |
|
897 """Return a non-zero score if request's user is in at least one of the |
|
898 groups given as initializer argument. Returned score is the number of groups |
|
899 in which the user is. |
|
900 |
|
901 If the special 'owners' group is given and `rset` is specified in the input |
|
902 context: |
|
903 |
|
904 * if `row` is specified check the entity at the given `row`/`col` (default |
|
905 to 0) is owned by the user |
|
906 |
|
907 * else check all entities in `col` (default to 0) are owned by the user |
|
908 """ |
|
909 |
|
910 @lltrace |
|
911 def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): |
|
912 user = req.user |
|
913 if user is None: |
|
914 return int('guests' in self.expected) |
|
915 score = user.matching_groups(self.expected) |
|
916 if not score and 'owners' in self.expected and rset: |
|
917 if row is not None: |
|
918 if not user.owns(rset[row][col]): |
|
919 return 0 |
|
920 score = 1 |
|
921 else: |
|
922 score = all(user.owns(r[col]) for r in rset) |
|
923 return score |
|
924 |
|
925 |
|
926 # Web request selectors ######################################################## |
|
927 |
|
928 @objectify_selector |
|
929 @lltrace |
|
930 def primary_view(cls, req, view=None, **kwargs): |
|
931 """Return 1 if: |
|
932 |
|
933 * *no view is specified* in the input context |
|
934 |
|
935 * a view is specified and its `.is_primary()` method return True |
|
936 |
|
937 This selector is usually used by contextual components that only want to |
|
938 appears for the primary view of an entity. |
|
939 """ |
|
940 if view is not None and not view.is_primary(): |
|
941 return 0 |
|
942 return 1 |
|
943 |
|
944 |
|
945 class match_view(ExpectedValueSelector): |
|
946 """Return 1 if a view is specified an as its registry id is in one of the |
|
947 expected view id given to the initializer. |
|
948 """ |
|
949 @lltrace |
|
950 def __call__(self, cls, req, view=None, **kwargs): |
|
951 if view is None or not view.__regid__ in self.expected: |
|
952 return 0 |
|
953 return 1 |
|
954 |
|
955 |
|
956 @objectify_selector |
|
957 @lltrace |
|
958 def match_context_prop(cls, req, context=None, **kwargs): |
|
959 """Return 1 if: |
|
960 |
|
961 * no `context` is specified in input context (take care to confusion, here |
|
962 `context` refers to a string given as an argument to the input context...) |
|
963 |
|
964 * specified `context` is matching the context property value for the |
|
965 appobject using this selector |
|
966 |
|
967 * the appobject's context property value is None |
|
968 |
|
969 This selector is usually used by contextual components that want to appears |
|
970 in a configurable place. |
|
971 """ |
|
972 if context is None: |
|
973 return 1 |
|
974 propval = req.property_value('%s.%s.context' % (cls.__registry__, |
|
975 cls.__regid__)) |
|
976 if not propval: |
|
977 propval = cls.context |
|
978 if propval and context != propval: |
|
979 return 0 |
|
980 return 1 |
|
981 |
|
982 |
|
983 class match_search_state(ExpectedValueSelector): |
|
984 """Return 1 if the current request search state is in one of the expected |
|
985 states given to the initializer. |
|
986 |
|
987 Known search states are either 'normal' or 'linksearch' (eg searching for an |
|
988 object to create a relation with another). |
|
989 |
|
990 This selector is usually used by action that want to appears or not according |
|
991 to the ui search state. |
|
992 """ |
|
993 |
|
994 @lltrace |
|
995 def __call__(self, cls, req, **kwargs): |
|
996 try: |
|
997 if not req.search_state[0] in self.expected: |
|
998 return 0 |
|
999 except AttributeError: |
|
1000 return 1 # class doesn't care about search state, accept it |
|
1001 return 1 |
|
1002 |
|
1003 |
|
1004 class match_form_params(ExpectedValueSelector): |
|
1005 """Return non-zero score if parameter names specified as initializer |
|
1006 arguments are specified in request's form parameters. When multiple |
|
1007 parameters are specified, all of them should be found in req.form. Return a |
|
1008 score corresponding to the number of expected parameters. |
|
1009 """ |
|
1010 |
|
1011 @lltrace |
|
1012 def __call__(self, cls, req, **kwargs): |
|
1013 for param in self.expected: |
|
1014 if not param in req.form: |
|
1015 return 0 |
|
1016 return len(self.expected) |
|
1017 |
|
1018 |
|
1019 class specified_etype_implements(implements): |
|
1020 """Return non-zero score if the entity type specified by an 'etype' key |
|
1021 searched in (by priority) input context kwargs and request form parameters |
|
1022 match a known entity type (case insensitivly), and it's associated entity |
|
1023 class is of one of the type(s) given to the initializer or implements at |
|
1024 least one of the given interfaces. If multiple arguments are given, matching |
|
1025 one of them is enough. |
|
1026 |
|
1027 Entity types should be given as string, the corresponding class will be |
|
1028 fetched from the entity types registry at selection time. |
|
1029 |
|
1030 .. note:: when interface is an entity class, the score will reflect class |
|
1031 proximity so the most specific object will be selected. |
|
1032 |
|
1033 This selector is usually used by views holding entity creation forms (since |
|
1034 we've no result set to work on). |
|
1035 """ |
|
1036 |
|
1037 @lltrace |
|
1038 def __call__(self, cls, req, **kwargs): |
|
1039 try: |
|
1040 etype = kwargs['etype'] |
|
1041 except KeyError: |
|
1042 try: |
|
1043 etype = req.form['etype'] |
|
1044 except KeyError: |
|
1045 return 0 |
|
1046 else: |
|
1047 # only check this is a known type if etype comes from req.form, |
|
1048 # else we want the error to propagate |
|
1049 try: |
|
1050 etype = req.vreg.case_insensitive_etypes[etype.lower()] |
|
1051 req.form['etype'] = etype |
|
1052 except KeyError: |
|
1053 return 0 |
|
1054 score = self.score_class(req.vreg['etypes'].etype_class(etype), req) |
|
1055 if score: |
|
1056 eschema = req.vreg.schema.eschema(etype) |
|
1057 if eschema.has_local_role('add') or eschema.has_perm(req, 'add'): |
|
1058 return score |
|
1059 return 0 |
|
1060 |
|
1061 |
|
1062 # Other selectors ############################################################## |
|
1063 |
|
1064 |
|
1065 class match_transition(ExpectedValueSelector): |
|
1066 """Return 1 if: |
|
1067 |
|
1068 * a `transition` argument is found in the input context which |
|
1069 has a `.name` attribute matching one of the expected names given to the |
|
1070 initializer |
|
1071 |
|
1072 * no transition specified. |
|
1073 """ |
|
1074 @lltrace |
|
1075 def __call__(self, cls, req, transition=None, **kwargs): |
|
1076 # XXX check this is a transition that apply to the object? |
|
1077 if transition is None: |
|
1078 return 1 |
|
1079 if transition is not None and getattr(transition, 'name', None) in self.expected: |
|
1080 return 1 |
|
1081 return 0 |
|
1082 |
|
1083 |
|
1084 ## deprecated stuff ############################################################ |
|
1085 |
|
1086 entity_implements = class_renamed('entity_implements', implements) |
|
1087 |
|
1088 class _but_etype(EntitySelector): |
947 """accept if the given entity types are not found in the result set. |
1089 """accept if the given entity types are not found in the result set. |
948 |
1090 |
949 See `EntitySelector` documentation for behaviour when row is not specified. |
1091 See `EntitySelector` documentation for behaviour when row is not specified. |
950 |
1092 |
951 :param *etypes: entity types (`basestring`) which should be refused |
1093 :param *etypes: entity types (`basestring`) which should be refused |
952 """ |
1094 """ |
953 def __init__(self, *etypes): |
1095 def __init__(self, *etypes): |
954 super(but_etype, self).__init__() |
1096 super(_but_etype, self).__init__() |
955 self.but_etypes = etypes |
1097 self.but_etypes = etypes |
956 |
1098 |
957 def score(self, req, rset, row, col): |
1099 def score(self, req, rset, row, col): |
958 if rset.description[row][col] in self.but_etypes: |
1100 if rset.description[row][col] in self.but_etypes: |
959 return 0 |
1101 return 0 |
960 return 1 |
1102 return 1 |
961 |
1103 |
962 |
1104 but_etype = class_renamed('but_etype', _but_etype, 'use ~implements(*etypes) instead') |
963 class score_entity(EntitySelector): |
1105 |
964 """accept if some arbitrary function return a positive score for an entity |
1106 |
965 found in the result set. |
1107 # XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)? |
966 |
1108 # take care at the implementation though (looking for the 'row' argument's |
967 See `EntitySelector` documentation for behaviour when row is not specified. |
1109 # value) |
968 |
1110 two_lines_rset = class_renamed('two_lines_rset', multi_lines_rset) |
969 :param scorefunc: callable expected to take an entity as argument and to |
1111 two_cols_rset = class_renamed('two_cols_rset', multi_columns_rset) |
970 return a score >= 0 |
1112 two_etypes_rset = class_renamed('two_etypes_rset', multi_etypes_rset) |
971 """ |
|
972 def __init__(self, scorefunc, once_is_enough=False): |
|
973 super(score_entity, self).__init__(once_is_enough) |
|
974 def intscore(*args, **kwargs): |
|
975 score = scorefunc(*args, **kwargs) |
|
976 if not score: |
|
977 return 0 |
|
978 if isinstance(score, (int, long)): |
|
979 return score |
|
980 return 1 |
|
981 self.score_entity = intscore |
|
982 |
|
983 |
|
984 # XXX DEPRECATED ############################################################## |
|
985 # XXX remove when deprecated functions are removed |
|
986 filterwarnings('ignore', |
|
987 category=DeprecationWarning, |
|
988 module='cubicweb.selectors') |
|
989 from cubicweb.vregistry import chainall |
|
990 |
|
991 yes_selector = deprecated()(yes) |
|
992 norset_selector = deprecated()(none_rset) |
|
993 rset_selector = deprecated()(any_rset) |
|
994 anyrset_selector = deprecated()(nonempty_rset) |
|
995 emptyrset_selector = deprecated()(empty_rset) |
|
996 onelinerset_selector = deprecated()(one_line_rset) |
|
997 twolinerset_selector = deprecated()(two_lines_rset) |
|
998 twocolrset_selector = deprecated()(two_cols_rset) |
|
999 largerset_selector = deprecated()(paginated_rset) |
|
1000 sortedrset_selector = deprecated()(sorted_rset) |
|
1001 oneetyperset_selector = deprecated()(one_etype_rset) |
|
1002 multitype_selector = deprecated()(two_etypes_rset) |
|
1003 anonymous_selector = deprecated()(anonymous_user) |
|
1004 not_anonymous_selector = deprecated()(authenticated_user) |
|
1005 primaryview_selector = deprecated()(primary_view) |
|
1006 contextprop_selector = deprecated()(match_context_prop) |
|
1007 |
|
1008 @deprecated('use non_final_entity instead of %s') |
|
1009 def nfentity_selector(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1010 return non_final_entity()(cls, req, rset, row, col) |
|
1011 |
|
1012 @deprecated('use implements instead of %s') |
|
1013 def implement_interface(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1014 return implements(*cls.accepts_interfaces)(cls, req, rset, row, col) |
|
1015 _interface_selector = deprecated()(implement_interface) |
|
1016 interface_selector = deprecated()(implement_interface) |
|
1017 |
|
1018 @deprecated('use specified_etype_implements instead of %s') |
|
1019 def accept_etype(cls, req, *args, **kwargs): |
|
1020 """check etype presence in request form *and* accepts conformance""" |
|
1021 return specified_etype_implements(*cls.accepts)(cls, req, *args) |
|
1022 etype_form_selector = accept_etype |
|
1023 |
|
1024 @deprecated('use match_search_state instead of %s') |
|
1025 def searchstate_selector(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1026 return match_search_state(cls.search_states)(cls, req, rset, row, col) |
|
1027 |
|
1028 @deprecated('use match_user_groups instead of %s') |
|
1029 def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1030 return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs) |
|
1031 in_group_selector = match_user_group |
|
1032 |
|
1033 @deprecated('use relation_possible instead of %s') |
|
1034 def has_relation(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1035 return relation_possible(cls.rtype, role(cls), cls.etype, |
|
1036 getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs) |
|
1037 |
|
1038 @deprecated('use relation_possible instead of %s') |
|
1039 def one_has_relation(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1040 return relation_possible(cls.rtype, role(cls), cls.etype, |
|
1041 getattr(cls, 'require_permission', 'read', |
|
1042 once_is_enough=True))(cls, req, rset, row, col, **kwargs) |
|
1043 |
|
1044 @deprecated('use implements instead of %s') |
|
1045 def accept_rset(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1046 """simply delegate to cls.accept_rset method""" |
|
1047 return implements(*cls.accepts)(cls, req, rset, row=row, col=col) |
|
1048 accept_rset_selector = accept_rset |
|
1049 |
|
1050 accept = chainall(non_final_entity(), accept_rset, name='accept') |
|
1051 accept = deprecated('use implements selector')(accept) |
|
1052 accept_selector = deprecated()(accept) |
|
1053 |
|
1054 accept_one = deprecated()(chainall(one_line_rset, accept, |
|
1055 name='accept_one')) |
|
1056 accept_one_selector = deprecated()(accept_one) |
|
1057 |
|
1058 |
|
1059 def _rql_condition(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1060 if cls.condition: |
|
1061 return rql_condition(cls.condition)(cls, req, rset, row, col) |
|
1062 return 1 |
|
1063 _rqlcondition_selector = deprecated()(_rql_condition) |
|
1064 |
|
1065 rqlcondition_selector = deprecated()(chainall(non_final_entity(), one_line_rset, _rql_condition, |
|
1066 name='rql_condition')) |
|
1067 |
|
1068 @deprecated('use but_etype instead of %s') |
|
1069 def but_etype_selector(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1070 return but_etype(cls.etype)(cls, req, rset, row, col) |
|
1071 |
|
1072 @lltrace |
|
1073 def etype_rtype_selector(cls, req, rset=None, row=None, col=0, **kwargs): |
|
1074 schema = cls.schema |
|
1075 perm = getattr(cls, 'require_permission', 'read') |
|
1076 if hasattr(cls, 'etype'): |
|
1077 eschema = schema.eschema(cls.etype) |
|
1078 if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)): |
|
1079 return 0 |
|
1080 if hasattr(cls, 'rtype'): |
|
1081 rschema = schema.rschema(cls.rtype) |
|
1082 if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)): |
|
1083 return 0 |
|
1084 return 1 |
|
1085 etype_rtype_selector = deprecated()(etype_rtype_selector) |
|
1086 |
|
1087 #req_form_params_selector = deprecated()(match_form_params) # form_params |
|
1088 #kwargs_selector = deprecated()(match_kwargs) # expected_kwargs |
|
1089 |
|
1090 # compound selectors ########################################################## |
|
1091 |
|
1092 searchstate_accept = chainall(nonempty_rset(), accept, |
|
1093 name='searchstate_accept') |
|
1094 searchstate_accept_selector = deprecated()(searchstate_accept) |
|
1095 |
|
1096 searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition, |
|
1097 name='searchstate_accept_one') |
|
1098 searchstate_accept_one_selector = deprecated()(searchstate_accept_one) |
|
1099 |
|
1100 searchstate_accept = deprecated()(searchstate_accept) |
|
1101 searchstate_accept_one = deprecated()(searchstate_accept_one) |
|
1102 |
|
1103 # end of deprecation section ################################################## |
|
1104 |
|
1105 def unbind_method(selector): |
|
1106 def new_selector(registered): |
|
1107 # get the unbound method |
|
1108 if hasattr(registered, 'im_func'): |
|
1109 registered = registered.im_func |
|
1110 # don't rebind since it will be done automatically during |
|
1111 # the assignment, inside the destination class body |
|
1112 return selector(registered) |
|
1113 new_selector.__name__ = selector.__name__ |
|
1114 return new_selector |
|
1115 |
|
1116 |
|
1117 def deprecate(registered, msg): |
|
1118 # get the unbound method |
|
1119 if hasattr(registered, 'im_func'): |
|
1120 registered = registered.im_func |
|
1121 def _deprecate(cls, vreg): |
|
1122 warn(msg, DeprecationWarning) |
|
1123 return registered(cls, vreg) |
|
1124 return _deprecate |
|
1125 |
|
1126 @unbind_method |
|
1127 def require_group_compat(registered): |
|
1128 def plug_selector(cls, vreg): |
|
1129 cls = registered(cls, vreg) |
|
1130 if getattr(cls, 'require_groups', None): |
|
1131 warn('use "match_user_groups(group1, group2)" instead of using require_groups', |
|
1132 DeprecationWarning) |
|
1133 cls.__select__ &= match_user_groups(cls.require_groups) |
|
1134 return cls |
|
1135 return plug_selector |
|
1136 |
|
1137 @unbind_method |
|
1138 def accepts_compat(registered): |
|
1139 def plug_selector(cls, vreg): |
|
1140 cls = registered(cls, vreg) |
|
1141 if getattr(cls, 'accepts', None): |
|
1142 warn('use "implements("EntityType", IFace)" instead of using accepts on %s' |
|
1143 % cls, |
|
1144 DeprecationWarning) |
|
1145 cls.__select__ &= implements(*cls.accepts) |
|
1146 return cls |
|
1147 return plug_selector |
|
1148 |
|
1149 @unbind_method |
|
1150 def accepts_etype_compat(registered): |
|
1151 def plug_selector(cls, vreg): |
|
1152 cls = registered(cls, vreg) |
|
1153 if getattr(cls, 'accepts', None): |
|
1154 warn('use "specified_etype_implements("EntityType", IFace)" instead of using accepts', |
|
1155 DeprecationWarning) |
|
1156 cls.__select__ &= specified_etype_implements(*cls.accepts) |
|
1157 return cls |
|
1158 return plug_selector |
|
1159 |
|
1160 @unbind_method |
|
1161 def condition_compat(registered): |
|
1162 def plug_selector(cls, vreg): |
|
1163 cls = registered(cls, vreg) |
|
1164 if getattr(cls, 'condition', None): |
|
1165 warn('use "use rql_condition(expression)" instead of using condition', |
|
1166 DeprecationWarning) |
|
1167 cls.__select__ &= rql_condition(cls.condition) |
|
1168 return cls |
|
1169 return plug_selector |
|
1170 |
|
1171 @unbind_method |
|
1172 def has_relation_compat(registered): |
|
1173 def plug_selector(cls, vreg): |
|
1174 cls = registered(cls, vreg) |
|
1175 if getattr(cls, 'etype', None): |
|
1176 warn('use relation_possible selector instead of using etype_rtype', |
|
1177 DeprecationWarning) |
|
1178 cls.__select__ &= relation_possible(cls.rtype, role(cls), |
|
1179 getattr(cls, 'etype', None), |
|
1180 action=getattr(cls, 'require_permission', 'read')) |
|
1181 return cls |
|
1182 return plug_selector |
|
1183 |
|