--- a/selectors.py Fri Feb 12 15:18:00 2010 +0100
+++ b/selectors.py Wed Mar 24 10:23:31 2010 +0100
@@ -43,18 +43,18 @@
__docformat__ = "restructuredtext en"
import logging
-from warnings import warn, filterwarnings
+from warnings import warn
+from logilab.common.deprecation import class_renamed
from logilab.common.compat import all, any
-from logilab.common.deprecation import deprecated
from logilab.common.interface import implements as implements_iface
from yams import BASE_TYPES
-from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
- role, typed_eid)
+from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role
# even if not used, let yes here so it's importable through this module
from cubicweb.appobject import Selector, objectify_selector, yes
+from cubicweb.vregistry import class_regid
from cubicweb.cwconfig import CubicWebConfiguration
from cubicweb.schema import split_expression
@@ -75,13 +75,14 @@
else:
selname = selector.__name__
vobj = cls
- oid = vobj.id
+ oid = class_regid(vobj)
ret = selector(cls, *args, **kwargs)
if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
#SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
- print '%s -> %s for %s' % (selname, ret, vobj)
+ print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
return ret
traced.__name__ = selector.__name__
+ traced.__doc__ = selector.__doc__
return traced
class traced_selection(object):
@@ -113,13 +114,13 @@
return traceback is None
-def score_interface(cls_or_inst, cls, iface):
+def score_interface(etypesreg, cls_or_inst, cls, iface):
"""Return XXX if the give object (maybe an instance or class) implements
the interface.
"""
if getattr(iface, '__registry__', None) == 'etypes':
# adjust score if the interface is an entity class
- parents = cls_or_inst.parent_classes()
+ parents = etypesreg.parent_classes(cls_or_inst.__regid__)
if iface is cls:
return len(parents) + 4
if iface is parents[-1]: # Any
@@ -134,7 +135,7 @@
return 0
-# abstract selectors ##########################################################
+# abstract selectors / mixin helpers ###########################################
class PartialSelectorMixIn(object):
"""convenience mix-in for selectors that will look into the containing
@@ -158,32 +159,43 @@
return '%s(%s)' % (self.__class__.__name__,
','.join(str(s) for s in self.expected_ifaces))
- def score_interfaces(self, cls_or_inst, cls):
+ def score_interfaces(self, req, cls_or_inst, cls):
score = 0
- vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
+ etypesreg = req.vreg['etypes']
for iface in self.expected_ifaces:
if isinstance(iface, basestring):
# entity type
try:
- iface = vreg['etypes'].etype_class(iface)
+ iface = etypesreg.etype_class(iface)
except KeyError:
continue # entity type not in the schema
- score += score_interface(cls_or_inst, cls, iface)
+ score += score_interface(etypesreg, cls_or_inst, cls, iface)
return score
class EClassSelector(Selector):
- """abstract class for selectors working on the entity classes of the result
- set. Its __call__ method has the following behaviour:
+ """abstract class for selectors working on *entity class(es)* specified
+ explicitly or found of the result set.
+
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, return score for this entity's class
- * if row is specified, return the score returned by the score_class method
- called with the entity class found in the specified cell
- * else return the sum of score returned by the score_class method for each
- entity type found in the specified column, unless:
+ * elif `row` is specified, return score for the class of the entity
+ found in the specified cell, using column specified by `col` or 0
+
+ * else return the sum of scores for each entity class found in the column
+ specified specified by the `col` argument or in column 0 if not specified,
+ unless:
+
+ - `once_is_enough` is False (the default) and some entity class is scored
+ to 0, in which case 0 is returned
+
- `once_is_enough` is True, in which case the first non-zero score is
returned
- - `once_is_enough` is False, in which case if score_class return 0, 0 is
- returned
+
+ - `accept_none` is False and some cell in the column has a None value
+ (this may occurs with outer join)
"""
def __init__(self, once_is_enough=False, accept_none=True):
self.once_is_enough = once_is_enough
@@ -191,6 +203,8 @@
@lltrace
def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ if kwargs.get('entity'):
+ return self.score_class(kwargs['entity'].__class__, req)
if not rset:
return 0
score = 0
@@ -216,29 +230,42 @@
def score(self, cls, req, etype):
if etype in BASE_TYPES:
return 0
- return self.score_class(cls.vreg['etypes'].etype_class(etype), req)
+ return self.score_class(req.vreg['etypes'].etype_class(etype), req)
def score_class(self, eclass, req):
raise NotImplementedError()
class EntitySelector(EClassSelector):
- """abstract class for selectors working on the entity instances of the
- result set. Its __call__ method has the following behaviour:
+ """abstract class for selectors working on *entity instance(s)* specified
+ explicitly or found of the result set.
+
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, return score for this entity
- * if 'entity' find in kwargs, return the score returned by the score_entity
- method for this entity
- * if row is specified, return the score returned by the score_entity method
- called with the entity instance found in the specified cell
- * else return the sum of score returned by the score_entity method for each
- entity found in the specified column, unless:
+ * elif `row` is specified, return score for the entity found in the
+ specified cell, using column specified by `col` or 0
+
+ * else return the sum of scores for each entity found in the column
+ specified specified by the `col` argument or in column 0 if not specified,
+ unless:
+
+ - `once_is_enough` is False (the default) and some entity is scored
+ to 0, in which case 0 is returned
+
- `once_is_enough` is True, in which case the first non-zero score is
returned
- - `once_is_enough` is False, in which case if score_class return 0, 0 is
- returned
+
+ - `accept_none` is False and some cell in the column has a None value
+ (this may occurs with outer join)
- note: None values (resulting from some outer join in the query) are not
- considered.
+ .. note::
+ using EntitySelector or EClassSelector as base selector class impacts
+ performance, since when no entity or row is specified the later works on
+ every different *entity class* found in the result set, while the former
+ works on each *entity* (eg each row of the result set), which may be much
+ more costly.
"""
@lltrace
@@ -252,6 +279,8 @@
col = col or 0
for row, rowvalue in enumerate(rset.rows):
if rowvalue[col] is None: # outer join
+ if not self.accept_none:
+ return 0
continue
escore = self.score(req, rset, row, col)
if not escore and not self.once_is_enough:
@@ -276,174 +305,9 @@
raise NotImplementedError()
-# very basic selectors ########################################################
-
-@objectify_selector
-@lltrace
-def none_rset(cls, req, rset=None, **kwargs):
- """accept no result set (e.g. given rset is None)"""
- if rset is None:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def any_rset(cls, req, rset=None, **kwargs):
- """accept result set, whatever the number of result it contains"""
- if rset is not None:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def nonempty_rset(cls, req, rset=None, **kwargs):
- """accept any non empty result set"""
- if rset is not None and rset.rowcount:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def empty_rset(cls, req, rset=None, **kwargs):
- """accept empty result set"""
- if rset is not None and rset.rowcount == 0:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def one_line_rset(cls, req, rset=None, row=None, **kwargs):
- """if row is specified, accept result set with a single line of result,
- else accepts anyway
- """
- if rset is not None and (row is not None or rset.rowcount == 1):
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def two_lines_rset(cls, req, rset=None, **kwargs):
- """accept result set with *at least* two lines of result"""
- if rset is not None and rset.rowcount > 1:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def two_cols_rset(cls, req, rset=None, **kwargs):
- """accept result set with at least one line and two columns of result"""
- if rset is not None and rset.rowcount and len(rset.rows[0]) > 1:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def paginated_rset(cls, req, rset=None, **kwargs):
- """accept result set with more lines than the page size.
-
- Page size is searched in (respecting order):
- * a page_size argument
- * a page_size form parameters
- * the navigation.page-size property
- """
- page_size = kwargs.get('page_size')
- if page_size is None:
- page_size = req.form.get('page_size')
- if page_size is None:
- page_size = req.property_value('navigation.page-size')
- else:
- page_size = int(page_size)
- if rset is None or rset.rowcount <= page_size:
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def sorted_rset(cls, req, rset=None, **kwargs):
- """accept sorted result set"""
- rqlst = rset.syntax_tree()
- if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
- return 0
- return 2
-
-@objectify_selector
-@lltrace
-def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
- """accept result set where entities in the specified column (or 0) are all
- of the same type
- """
- if rset is None:
- return 0
- if len(rset.column_types(col)) != 1:
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def two_etypes_rset(cls, req, rset=None, col=0, **kwargs):
- """accept result set where entities in the specified column (or 0) are not
- of the same type
- """
- if rset:
- etypes = rset.column_types(col)
- if len(etypes) > 1:
- return 1
- return 0
-
-class non_final_entity(EClassSelector):
- """accept if entity type found in the result set is non final.
-
- See `EClassSelector` documentation for behaviour when row is not specified.
- """
- def score(self, cls, req, etype):
- if etype in BASE_TYPES:
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def authenticated_user(cls, req, *args, **kwargs):
- """accept if user is authenticated"""
- if req.cnx.anonymous_connection:
- return 0
- return 1
-
-def anonymous_user():
- return ~ authenticated_user()
-
-@objectify_selector
-@lltrace
-def primary_view(cls, req, rset=None, row=None, col=0, view=None, **kwargs):
- """accept if view given as named argument is a primary view, or if no view
- is given
- """
- if view is not None and not view.is_primary():
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def match_context_prop(cls, req, rset=None, row=None, col=0, context=None,
- **kwargs):
- """accept if:
- * no context given
- * context (`basestring`) is matching the context property value for the
- given cls
- """
- propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
- if not propval:
- propval = cls.context
- if context is not None and propval and context != propval:
- return 0
- return 1
-
-
-class match_search_state(Selector):
- """accept if the current request search state is in one of the expected
- states given to the initializer
-
- :param expected: either 'normal' or 'linksearch' (eg searching for an
- object to create a relation with another)
+class ExpectedValueSelector(Selector):
+ """Take a list of expected values as initializer argument, check
+ _get_value method return one of these expected values.
"""
def __init__(self, *expected):
assert expected, self
@@ -454,356 +318,405 @@
','.join(sorted(str(s) for s in self.expected)))
@lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- try:
- if not req.search_state[0] in self.expected:
- return 0
- except AttributeError:
- return 1 # class doesn't care about search state, accept it
- return 1
+ def __call__(self, cls, req, **kwargs):
+ if self._get_value(cls, req, **kwargs) in self.expected:
+ return 1
+ return 0
+
+ def _get_value(self, cls, req, **kwargs):
+ raise NotImplementedError()
-class match_form_params(match_search_state):
- """accept if parameters specified as initializer arguments are specified
- in request's form parameters
+# bare selectors ##############################################################
- :param *expected: parameters (eg `basestring`) which are expected to be
- found in request's form parameters
+class match_kwargs(ExpectedValueSelector):
+ """Return non-zero score if parameter names specified as initializer
+ arguments are specified in the input context. When multiple parameters are
+ specified, all of them should be specified in the input context. Return a
+ score corresponding to the number of expected parameters.
"""
@lltrace
- def __call__(self, cls, req, *args, **kwargs):
- score = 0
- for param in self.expected:
- if not param in req.form:
- return 0
- score += 1
- return len(self.expected)
-
-
-class match_kwargs(match_search_state):
- """accept if parameters specified as initializer arguments are specified
- in named arguments given to the selector
-
- :param *expected: parameters (eg `basestring`) which are expected to be
- found in named arguments (kwargs)
- """
-
- @lltrace
- def __call__(self, cls, req, *args, **kwargs):
+ def __call__(self, cls, req, **kwargs):
for arg in self.expected:
if not arg in kwargs:
return 0
return len(self.expected)
-class match_user_groups(match_search_state):
- """accept if logged users is in at least one of the given groups. Returned
- score is the number of groups in which the user is.
-
- If the special 'owners' group is given:
- * if row is specified check the entity at the given row/col is owned by the
- logged user
- * if row is not specified check all entities in col are owned by the logged
- user
-
- :param *required_groups: name of groups (`basestring`) in which the logged
- user should be
- """
-
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- user = req.user
- if user is None:
- return int('guests' in self.expected)
- score = user.matching_groups(self.expected)
- if not score and 'owners' in self.expected and rset:
- if row is not None:
- if not user.owns(rset[row][col]):
- return 0
- score = 1
- else:
- score = all(user.owns(r[col]) for r in rset)
- return score
-
+class appobject_selectable(Selector):
+ """return 1 if another appobject is selectable using the same input context.
-class match_transition(match_search_state):
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- try:
- # XXX check this is a transition that apply to the object?
- if not kwargs['transition'].name in self.expected:
- return 0
- except KeyError:
- return 0
- return 1
-
-
-class match_view(match_search_state):
- """accept if the current view is in one of the expected vid given to the
- initializer
+ Initializer arguments:
+ * `registry`, a registry name
+ * `regid`, an object identifier in this registry
"""
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, view=None, **kwargs):
- if view is None or not view.id in self.expected:
- return 0
- return 1
-
-
-class appobject_selectable(Selector):
- """accept with another appobject is selectable using selector's input
- context.
-
- :param registry: a registry name (`basestring`)
- :param oid: an object identifier (`basestring`)
- """
- def __init__(self, registry, oid):
+ def __init__(self, registry, regid):
self.registry = registry
- self.oid = oid
+ self.regid = regid
def __call__(self, cls, req, **kwargs):
try:
- cls.vreg[self.registry].select(self.oid, req, **kwargs)
+ req.vreg[self.registry].select(self.regid, req, **kwargs)
return 1
except NoSelectableObject:
return 0
-# not so basic selectors ######################################################
+# rset selectors ##############################################################
-class implements(ImplementsMixIn, EClassSelector):
- """accept if entity classes found in the result set implements at least one
- of the interfaces given as argument. Returned score is the number of
- implemented interfaces.
+@objectify_selector
+@lltrace
+def none_rset(cls, req, rset=None, **kwargs):
+ """Return 1 if the result set is None (eg usually not specified)."""
+ if rset is None:
+ return 1
+ return 0
- See `EClassSelector` documentation for behaviour when row is not specified.
- :param *expected_ifaces: expected interfaces. An interface may be a class
- or an entity type (e.g. `basestring`) in which case
- the associated class will be searched in the
- registry (at selection time)
+# XXX == ~ none_rset
+@objectify_selector
+@lltrace
+def any_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for any result set, whatever the number of rows in it, even 0."""
+ if rset is not None:
+ return 1
+ return 0
- note: when interface is an entity class, the score will reflect class
- proximity so the most specific object'll be selected
- """
- def score_class(self, eclass, req):
- return self.score_interfaces(eclass, eclass)
+
+@objectify_selector
+@lltrace
+def nonempty_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set containing one ore more rows."""
+ if rset is not None and rset.rowcount:
+ return 1
+ return 0
-class specified_etype_implements(implements):
- """accept if entity class specified using an 'etype' parameters in name
- argument or request form implements at least one of the interfaces given as
- argument. Returned score is the number of implemented interfaces.
+# XXX == ~ nonempty_rset
+@objectify_selector
+@lltrace
+def empty_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set which doesn't contain any row."""
+ if rset is not None and rset.rowcount == 0:
+ return 1
+ return 0
+
+
+# XXX == multi_lines_rset(1)
+@objectify_selector
+@lltrace
+def one_line_rset(cls, req, rset=None, row=None, **kwargs):
+ """Return 1 if the result set is of size 1 or if a specific row in the
+ result set is specified ('row' argument).
+ """
+ if rset is not None and (row is not None or rset.rowcount == 1):
+ return 1
+ return 0
+
- :param *expected_ifaces: expected interfaces. An interface may be a class
- or an entity type (e.g. `basestring`) in which case
- the associated class will be searched in the
- registry (at selection time)
+class multi_lines_rset(Selector):
+ """If `nb`is specified, return 1 if the result set has exactly `nb` row of
+ result. Else (`nb` is None), return 1 if the result set contains *at least*
+ two rows.
+ """
+ def __init__(self, nb=None):
+ self.expected = nb
- note: when interface is an entity class, the score will reflect class
- proximity so the most specific object'll be selected
+ def match_expected(self, num):
+ if self.expected is None:
+ return num > 1
+ return num == self.expected
+
+ @lltrace
+ def __call__(self, cls, req, rset=None, **kwargs):
+ return rset is not None and self.match_expected(rset.rowcount)
+
+
+class multi_columns_rset(multi_lines_rset):
+ """If `nb`is specified, return 1 if the result set has exactly `nb` column
+ per row. Else (`nb` is None), return 1 if the result set contains *at least*
+ two columns per row. Return 0 for empty result set.
"""
@lltrace
- def __call__(self, cls, req, *args, **kwargs):
- try:
- etype = kwargs['etype']
- except KeyError:
- try:
- etype = req.form['etype']
- except KeyError:
- return 0
- else:
- # only check this is a known type if etype comes from req.form,
- # else we want the error to propagate
- try:
- etype = cls.vreg.case_insensitive_etypes[etype.lower()]
- req.form['etype'] = etype
- except KeyError:
- return 0
- score = self.score_class(cls.vreg['etypes'].etype_class(etype), req)
- if score:
- eschema = req.vreg.schema.eschema(etype)
- if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
- return score
+ def __call__(self, cls, req, rset=None, **kwargs):
+ # 'or 0' since we *must not* return None
+ return rset and self.match_expected(len(rset.rows[0])) or 0
+
+
+@objectify_selector
+@lltrace
+def paginated_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set with more rows than a page size.
+
+ Page size is searched in (respecting order):
+ * a `page_size` argument
+ * a `page_size` form parameters
+ * the :ref:`navigation.page-size` property
+ """
+ if rset is None:
+ return 0
+ page_size = kwargs.get('page_size')
+ if page_size is None:
+ page_size = req.form.get('page_size')
+ if page_size is None:
+ page_size = req.property_value('navigation.page-size')
+ else:
+ page_size = int(page_size)
+ if rset.rowcount <= page_size:
return 0
+ return 1
+
+
+@objectify_selector
+@lltrace
+def sorted_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for sorted result set (e.g. from an RQL query containing an
+ :ref:ORDERBY clause.
+ """
+ if rset is None:
+ return 0
+ rqlst = rset.syntax_tree()
+ if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
+ return 0
+ return 2
+
+
+# XXX == multi_etypes_rset(1)
+@objectify_selector
+@lltrace
+def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
+ """Return 1 if the result set contains entities which are all of the same
+ type in the column specified by the `col` argument of the input context, or
+ in column 0.
+ """
+ if rset is None:
+ return 0
+ if len(rset.column_types(col)) != 1:
+ return 0
+ return 1
+
+
+class multi_etypes_rset(multi_lines_rset):
+ """If `nb` is specified, return 1 if the result set contains `nb` different
+ types of entities in the column specified by the `col` argument of the input
+ context, or in column 0. If `nb` is None, return 1 if the result set contains
+ *at least* two different types of entities.
+ """
+
+ @lltrace
+ def __call__(self, cls, req, rset=None, col=0, **kwargs):
+ # 'or 0' since we *must not* return None
+ return rset and self.match_expected(len(rset.column_types(col))) or 0
-class entity_implements(ImplementsMixIn, EntitySelector):
- """accept if entity instances found in the result set implements at least one
- of the interfaces given as argument. Returned score is the number of
- implemented interfaces.
+# entity selectors #############################################################
+
+class non_final_entity(EClassSelector):
+ """Return 1 for entity of a non final entity type(s). Remember, "final"
+ entity types are String, Int, etc... This is equivalent to
+ `implements('Any')` but more optimized.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+ class lookup / score rules according to the input context.
+ """
+ def score(self, cls, req, etype):
+ if etype in BASE_TYPES:
+ return 0
+ return 1
+
+ def score_class(self, eclass, req):
+ return 1 # necessarily true if we're there
- :param *expected_ifaces: expected interfaces. An interface may be a class
- or an entity type (e.g. `basestring`) in which case
- the associated class will be searched in the
- registry (at selection time)
+
+class implements(ImplementsMixIn, EClassSelector):
+ """Return non-zero score for entity that are of the given type(s) or
+ implements at least one of the given interface(s). If multiple arguments are
+ given, matching one of them is enough.
+
+ Entity types should be given as string, the corresponding class will be
+ fetched from the entity types registry at selection time.
- note: when interface is an entity class, the score will reflect class
- proximity so the most specific object'll be selected
+ See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+ class lookup / score rules according to the input context.
+
+ .. note:: when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
"""
- def score_entity(self, entity):
- return self.score_interfaces(entity, entity.__class__)
+ def score_class(self, eclass, req):
+ return self.score_interfaces(req, eclass, eclass)
-class relation_possible(EClassSelector):
- """accept if entity class found in the result set support the relation.
+class score_entity(EntitySelector):
+ """Return score according to an arbitrary function given as argument which
+ will be called with input content entity as argument.
+
+ This is a very useful selector that will usually interest you since it
+ allows a lot of things without having to write a specific selector.
- See `EClassSelector` documentation for behaviour when row is not specified.
+ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+ lookup / score rules according to the input context.
+ """
+ def __init__(self, scorefunc, once_is_enough=False):
+ super(score_entity, self).__init__(once_is_enough)
+ def intscore(*args, **kwargs):
+ score = scorefunc(*args, **kwargs)
+ if not score:
+ return 0
+ if isinstance(score, (int, long)):
+ return score
+ return 1
+ self.score_entity = intscore
+
+
+class relation_possible(EntitySelector):
+ """Return 1 for entity that supports the relation, provided that the
+ request's user may do some `action` on it (see below).
+
+ The relation is specified by the following initializer arguments:
+
+ * `rtype`, the name of the relation
- :param rtype: a relation type (`basestring`)
- :param role: the role of the result set entity in the relation. 'subject' or
- 'object', default to 'subject'.
- :param target_type: if specified, check the relation's end may be of this
- target type (`basestring`)
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
+ * `role`, the role of the entity in the relation, either 'subject' or
+ 'object', default to 'subject'
+
+ * `target_etype`, optional name of an entity type that should be supported
+ at the other end of the relation
+
+ * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete',
+ default to 'read') which must be granted to the user, else a 0 score will
+ be returned
+
+ * `strict`, boolean (default to False) telling what to do when the user has
+ not globally the permission for the action (eg the action is not granted
+ to one of the user's groups)
+
+ - when strict is False, if there are some local role defined for this
+ action (e.g. using rql expressions), then the permission will be
+ considered as granted
+
+ - when strict is True, then the permission will be actually checked for
+ each entity
+
+ Setting `strict` to True impacts performance for large result set since
+ you'll then get the :class:`~cubicweb.selectors.EntitySelector` behaviour
+ while otherwise you get the :class:`~cubicweb.selectors.EClassSelector`'s
+ one. See those classes documentation for entity lookup / score rules
+ according to the input context.
"""
+
def __init__(self, rtype, role='subject', target_etype=None,
- action='read', once_is_enough=False):
- super(relation_possible, self).__init__(once_is_enough)
+ action='read', strict=False, **kwargs):
+ super(relation_possible, self).__init__(**kwargs)
self.rtype = rtype
self.role = role
self.target_etype = target_etype
self.action = action
+ self.strict = strict
- @lltrace
- def __call__(self, cls, req, *args, **kwargs):
- rschema = cls.schema.rschema(self.rtype)
- if not (rschema.has_perm(req, self.action)
- or rschema.has_local_role(self.action)):
- return 0
- if self.action != 'read':
- if not (rschema.has_perm(req, 'read')
- or rschema.has_local_role('read')):
- return 0
- score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
- return score
+ # hack hack hack
+ def __call__(self, cls, req, **kwargs):
+ if self.strict:
+ return EntitySelector.__call__(self, cls, req, **kwargs)
+ return EClassSelector.__call__(self, cls, req, **kwargs)
- def score_class(self, eclass, req):
+ def score(self, *args):
+ if self.strict:
+ return EntitySelector.score(self, *args)
+ return EClassSelector.score(self, *args)
+
+ def _get_rschema(self, eclass):
eschema = eclass.e_schema
try:
if self.role == 'object':
- rschema = eschema.objrels[self.rtype]
+ return eschema.objrels[self.rtype]
else:
- rschema = eschema.subjrels[self.rtype]
+ return eschema.subjrels[self.rtype]
except KeyError:
- return 0
+ return None
+
+ def score_class(self, eclass, req):
+ rschema = self._get_rschema(eclass)
+ if rschema is None:
+ return 0 # relation not supported
+ eschema = eclass.e_schema
if self.target_etype is not None:
try:
- if self.role == 'subject':
- return int(self.target_etype in rschema.objects(eschema))
- else:
- return int(self.target_etype in rschema.subjects(eschema))
+ rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
+ if not rdef.may_have_permission(self.action, req):
+ return 0
except KeyError:
return 0
+ else:
+ return rschema.may_have_permission(self.action, req, eschema, self.role)
+ return 1
+
+ def score_entity(self, entity):
+ rschema = self._get_rschema(entity)
+ if rschema is None:
+ return 0 # relation not supported
+ if self.target_etype is not None:
+ rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
+ if self.role == 'subject':
+ if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid):
+ return 0
+ elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid):
+ return 0
return 1
class partial_relation_possible(PartialSelectorMixIn, relation_possible):
- """partial version of the relation_possible selector
-
- The selector will look for class attributes to find its missing
- information. The list of attributes required on the class
- for this selector are:
-
- - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+ """Same as :class:~`cubicweb.selectors.relation_possible`, but will look for
+ attributes of the selected class to get information which is otherwise
+ expected by the initializer, except for `action` and `strict` which are kept
+ as initializer arguments.
- - `role`: this attribute will be passed to the `cubicweb.role` function
- to determine the role of class in the relation
-
- - `etype` (optional): the entity type on the other side of the relation
-
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
+ This is useful to predefine selector of an abstract class designed to be
+ customized.
"""
- def __init__(self, action='read', once_is_enough=False):
+ def __init__(self, action='read', **kwargs):
super(partial_relation_possible, self).__init__(None, None, None,
- action, once_is_enough)
+ action, **kwargs)
def complete(self, cls):
self.rtype = cls.rtype
self.role = role(cls)
self.target_etype = getattr(cls, 'etype', None)
-
-
-class may_add_relation(EntitySelector):
- """accept if the relation can be added to an entity found in the result set
- by the logged user.
-
- See `EntitySelector` documentation for behaviour when row is not specified.
-
- :param rtype: a relation type (`basestring`)
- :param role: the role of the result set entity in the relation. 'subject' or
- 'object', default to 'subject'.
- """
-
- def __init__(self, rtype, role='subject', once_is_enough=False):
- super(may_add_relation, self).__init__(once_is_enough)
- self.rtype = rtype
- self.role = role
-
- def score_entity(self, entity):
- rschema = entity.schema.rschema(self.rtype)
- if self.role == 'subject':
- if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid):
- return 0
- elif not rschema.has_perm(entity.req, 'add', toeid=entity.eid):
- return 0
- return 1
-
-
-class partial_may_add_relation(PartialSelectorMixIn, may_add_relation):
- """partial version of the may_add_relation selector
-
- The selector will look for class attributes to find its missing
- information. The list of attributes required on the class
- for this selector are:
-
- - `rtype`: same as `rtype` parameter of the `relation_possible` selector
-
- - `role`: this attribute will be passed to the `cubicweb.role` function
- to determine the role of class in the relation.
-
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
- """
- def __init__(self, once_is_enough=False):
- super(partial_may_add_relation, self).__init__(None, None, once_is_enough)
-
- def complete(self, cls):
- self.rtype = cls.rtype
- self.role = role(cls)
+ if self.target_etype is not None:
+ warn('[3.6] please rename etype to target_etype on %s' % cls,
+ DeprecationWarning)
+ else:
+ self.target_etype = getattr(cls, 'target_etype', None)
class has_related_entities(EntitySelector):
- """accept if entity found in the result set has some linked entities using
- the specified relation (optionaly filtered according to the specified target
- type). Checks first if the relation is possible.
+ """Return 1 if entity support the specified relation and has some linked
+ entities by this relation , optionaly filtered according to the specified
+ target type.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ The relation is specified by the following initializer arguments:
+
+ * `rtype`, the name of the relation
- :param rtype: a relation type (`basestring`)
- :param role: the role of the result set entity in the relation. 'subject' or
- 'object', default to 'subject'.
- :param target_type: if specified, check the relation's end may be of this
- target type (`basestring`)
+ * `role`, the role of the entity in the relation, either 'subject' or
+ 'object', default to 'subject'.
+
+ * `target_etype`, optional name of an entity type that should be found
+ at the other end of the relation
+
+ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+ lookup / score rules according to the input context.
"""
- def __init__(self, rtype, role='subject', target_etype=None,
- once_is_enough=False):
- super(has_related_entities, self).__init__(once_is_enough)
+ def __init__(self, rtype, role='subject', target_etype=None, **kwargs):
+ super(has_related_entities, self).__init__(**kwargs)
self.rtype = rtype
self.role = role
self.target_etype = target_etype
def score_entity(self, entity):
relpossel = relation_possible(self.rtype, self.role, self.target_etype)
- if not relpossel.score_class(entity.__class__, entity.req):
+ if not relpossel.score_class(entity.__class__, entity._cw):
return 0
rset = entity.related(self.rtype, self.role)
if self.target_etype:
@@ -812,52 +725,52 @@
class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
- """partial version of the has_related_entities selector
-
- The selector will look for class attributes to find its missing
- information. The list of attributes required on the class
- for this selector are:
-
- - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+ """Same as :class:~`cubicweb.selectors.has_related_entity`, but will look
+ for attributes of the selected class to get information which is otherwise
+ expected by the initializer.
- - `role`: this attribute will be passed to the `cubicweb.role` function
- to determine the role of class in the relation.
-
- - `etype` (optional): the entity type on the other side of the relation
+ This is useful to predefine selector of an abstract class designed to be
+ customized.
+ """
+ def __init__(self, **kwargs):
+ super(partial_has_related_entities, self).__init__(None, None, None,
+ **kwargs)
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
- """
- def __init__(self, once_is_enough=False):
- super(partial_has_related_entities, self).__init__(None, None,
- None, once_is_enough)
def complete(self, cls):
self.rtype = cls.rtype
self.role = role(cls)
self.target_etype = getattr(cls, 'etype', None)
+ if self.target_etype is not None:
+ warn('[3.6] please rename etype to target_etype on %s' % cls,
+ DeprecationWarning)
+ else:
+ self.target_etype = getattr(cls, 'target_etype', None)
class has_permission(EntitySelector):
- """accept if user has the permission to do the requested action on a result
- set entity.
+ """Return non-zero score if request's user has the permission to do the
+ requested action on the entity. `action` is an entity schema action (eg one
+ of 'read', 'add', 'delete', 'update').
- * if row is specified, return 1 if user has the permission on the entity
- instance found in the specified cell
- * else return a positive score if user has the permission for every entity
- in the found in the specified column
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, check permission is granted for this entity
- note: None values (resulting from some outer join in the query) are not
- considered.
+ * elif `row` is specified, check permission is granted for the entity found
+ in the specified cell
- :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
+ * else check permission is granted for each entity found in the column
+ specified specified by the `col` argument or in column 0
"""
- def __init__(self, action, once_is_enough=False):
- super(has_permission, self).__init__(once_is_enough)
+ def __init__(self, action):
self.action = action
+ # don't use EntitySelector.__call__ but this optimized implementation to
+ # avoid considering each entity when it's not necessary
@lltrace
def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ if kwargs.get('entity'):
+ return self.score_entity(kwargs['entity'])
if rset is None:
return 0
user = req.user
@@ -865,7 +778,7 @@
if row is None:
score = 0
need_local_check = []
- geteschema = cls.schema.eschema
+ geteschema = req.vreg.schema.eschema
for etype in rset.column_types(0):
if etype in BASE_TYPES:
return 0
@@ -897,32 +810,47 @@
class has_add_permission(EClassSelector):
- """accept if logged user has the add permission on entity class found in the
- result set, and class is not a strict subobject.
+ """Return 1 if request's user has the add permission on entity type
+ specified in the `etype` initializer argument, or according to entity found
+ in the input content if not specified.
- See `EClassSelector` documentation for behaviour when row is not specified.
+ It also check that then entity type is not a strict subobject (e.g. may only
+ be used as a composed of another entity).
+
+ See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+ class lookup / score rules according to the input context when `etype` is
+ not specified.
"""
- def score(self, cls, req, etype):
- eschema = cls.schema.eschema(etype)
- if not (eschema.final or eschema.is_subobject(strict=True)) \
- and eschema.has_perm(req, 'add'):
- return 1
- return 0
+ def __init__(self, etype=None, **kwargs):
+ super(has_add_permission, self).__init__(**kwargs)
+ self.etype = etype
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ if self.etype is None:
+ return super(has_add_permission, self).__call__(cls, req, **kwargs)
+ return self.score(cls, req, self.etype)
+
+ def score_class(self, eclass, req):
+ eschema = eclass.e_schema
+ if eschema.final or eschema.is_subobject(strict=True) \
+ or not eschema.has_perm(req, 'add'):
+ return 0
+ return 1
class rql_condition(EntitySelector):
- """accept if an arbitrary rql return some results for an eid found in the
- result set. Returned score is the number of items returned by the rql
+ """Return non-zero score if arbitrary rql specified in `expression`
+ initializer argument return some results for entity found in the input
+ context. Returned score is the number of items returned by the rql
condition.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ `expression` is expected to be a string containing an rql expression, which
+ must use 'X' variable to represent the context entity and may use 'U' to
+ represent the request's user.
- :param expression: basestring containing an rql expression, which should use
- X variable to represent the context entity and may use U
- to represent the logged user
-
- return the sum of the number of items returned by the rql condition as score
- or 0 at the first entity scoring to zero.
+ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+ lookup / score rules according to the input context.
"""
def __init__(self, expression, once_is_enough=False):
super(rql_condition, self).__init__(once_is_enough)
@@ -932,6 +860,9 @@
rql = 'Any X WHERE X eid %%(x)s, %s' % expression
self.rql = rql
+ def __repr__(self):
+ return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+
def score(self, req, rset, row, col):
try:
return len(req.execute(self.rql, {'x': rset[row][col],
@@ -939,11 +870,222 @@
except Unauthorized:
return 0
- def __repr__(self):
- return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+# logged user selectors ########################################################
+
+@objectify_selector
+@lltrace
+def authenticated_user(cls, req, **kwargs):
+ """Return 1 if the user is authenticated (e.g. not the anonymous user).
+
+ May only be used on the web side, not on the data repository side.
+ """
+ if req.cnx.anonymous_connection:
+ return 0
+ return 1
+
+
+# XXX == ~ authenticated_user()
+def anonymous_user():
+ """Return 1 if the user is not authenticated (e.g. is the anonymous user).
+
+ May only be used on the web side, not on the data repository side.
+ """
+ return ~ authenticated_user()
+
+
+class match_user_groups(ExpectedValueSelector):
+ """Return a non-zero score if request's user is in at least one of the
+ groups given as initializer argument. Returned score is the number of groups
+ in which the user is.
+
+ If the special 'owners' group is given and `rset` is specified in the input
+ context:
+
+ * if `row` is specified check the entity at the given `row`/`col` (default
+ to 0) is owned by the user
+
+ * else check all entities in `col` (default to 0) are owned by the user
+ """
+
+ @lltrace
+ def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ user = req.user
+ if user is None:
+ return int('guests' in self.expected)
+ score = user.matching_groups(self.expected)
+ if not score and 'owners' in self.expected and rset:
+ if row is not None:
+ if not user.owns(rset[row][col]):
+ return 0
+ score = 1
+ else:
+ score = all(user.owns(r[col]) for r in rset)
+ return score
+
+
+# Web request selectors ########################################################
+
+@objectify_selector
+@lltrace
+def primary_view(cls, req, view=None, **kwargs):
+ """Return 1 if:
+
+ * *no view is specified* in the input context
+
+ * a view is specified and its `.is_primary()` method return True
+
+ This selector is usually used by contextual components that only want to
+ appears for the primary view of an entity.
+ """
+ if view is not None and not view.is_primary():
+ return 0
+ return 1
+
+
+class match_view(ExpectedValueSelector):
+ """Return 1 if a view is specified an as its registry id is in one of the
+ expected view id given to the initializer.
+ """
+ @lltrace
+ def __call__(self, cls, req, view=None, **kwargs):
+ if view is None or not view.__regid__ in self.expected:
+ return 0
+ return 1
-class but_etype(EntitySelector):
+@objectify_selector
+@lltrace
+def match_context_prop(cls, req, context=None, **kwargs):
+ """Return 1 if:
+
+ * no `context` is specified in input context (take care to confusion, here
+ `context` refers to a string given as an argument to the input context...)
+
+ * specified `context` is matching the context property value for the
+ appobject using this selector
+
+ * the appobject's context property value is None
+
+ This selector is usually used by contextual components that want to appears
+ in a configurable place.
+ """
+ if context is None:
+ return 1
+ propval = req.property_value('%s.%s.context' % (cls.__registry__,
+ cls.__regid__))
+ if not propval:
+ propval = cls.context
+ if propval and context != propval:
+ return 0
+ return 1
+
+
+class match_search_state(ExpectedValueSelector):
+ """Return 1 if the current request search state is in one of the expected
+ states given to the initializer.
+
+ Known search states are either 'normal' or 'linksearch' (eg searching for an
+ object to create a relation with another).
+
+ This selector is usually used by action that want to appears or not according
+ to the ui search state.
+ """
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ try:
+ if not req.search_state[0] in self.expected:
+ return 0
+ except AttributeError:
+ return 1 # class doesn't care about search state, accept it
+ return 1
+
+
+class match_form_params(ExpectedValueSelector):
+ """Return non-zero score if parameter names specified as initializer
+ arguments are specified in request's form parameters. When multiple
+ parameters are specified, all of them should be found in req.form. Return a
+ score corresponding to the number of expected parameters.
+ """
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ for param in self.expected:
+ if not param in req.form:
+ return 0
+ return len(self.expected)
+
+
+class specified_etype_implements(implements):
+ """Return non-zero score if the entity type specified by an 'etype' key
+ searched in (by priority) input context kwargs and request form parameters
+ match a known entity type (case insensitivly), and it's associated entity
+ class is of one of the type(s) given to the initializer or implements at
+ least one of the given interfaces. If multiple arguments are given, matching
+ one of them is enough.
+
+ Entity types should be given as string, the corresponding class will be
+ fetched from the entity types registry at selection time.
+
+ .. note:: when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
+
+ This selector is usually used by views holding entity creation forms (since
+ we've no result set to work on).
+ """
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ try:
+ etype = kwargs['etype']
+ except KeyError:
+ try:
+ etype = req.form['etype']
+ except KeyError:
+ return 0
+ else:
+ # only check this is a known type if etype comes from req.form,
+ # else we want the error to propagate
+ try:
+ etype = req.vreg.case_insensitive_etypes[etype.lower()]
+ req.form['etype'] = etype
+ except KeyError:
+ return 0
+ score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
+ if score:
+ eschema = req.vreg.schema.eschema(etype)
+ if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
+ return score
+ return 0
+
+
+# Other selectors ##############################################################
+
+
+class match_transition(ExpectedValueSelector):
+ """Return 1 if:
+
+ * a `transition` argument is found in the input context which
+ has a `.name` attribute matching one of the expected names given to the
+ initializer
+
+ * no transition specified.
+ """
+ @lltrace
+ def __call__(self, cls, req, transition=None, **kwargs):
+ # XXX check this is a transition that apply to the object?
+ if transition is None:
+ return 1
+ if transition is not None and getattr(transition, 'name', None) in self.expected:
+ return 1
+ return 0
+
+
+## deprecated stuff ############################################################
+
+entity_implements = class_renamed('entity_implements', implements)
+
+class _but_etype(EntitySelector):
"""accept if the given entity types are not found in the result set.
See `EntitySelector` documentation for behaviour when row is not specified.
@@ -951,7 +1093,7 @@
:param *etypes: entity types (`basestring`) which should be refused
"""
def __init__(self, *etypes):
- super(but_etype, self).__init__()
+ super(_but_etype, self).__init__()
self.but_etypes = etypes
def score(self, req, rset, row, col):
@@ -959,225 +1101,12 @@
return 0
return 1
-
-class score_entity(EntitySelector):
- """accept if some arbitrary function return a positive score for an entity
- found in the result set.
-
- See `EntitySelector` documentation for behaviour when row is not specified.
-
- :param scorefunc: callable expected to take an entity as argument and to
- return a score >= 0
- """
- def __init__(self, scorefunc, once_is_enough=False):
- super(score_entity, self).__init__(once_is_enough)
- def intscore(*args, **kwargs):
- score = scorefunc(*args, **kwargs)
- if not score:
- return 0
- if isinstance(score, (int, long)):
- return score
- return 1
- self.score_entity = intscore
-
-
-# XXX DEPRECATED ##############################################################
-# XXX remove when deprecated functions are removed
-filterwarnings('ignore',
- category=DeprecationWarning,
- module='cubicweb.selectors')
-from cubicweb.vregistry import chainall
-
-yes_selector = deprecated()(yes)
-norset_selector = deprecated()(none_rset)
-rset_selector = deprecated()(any_rset)
-anyrset_selector = deprecated()(nonempty_rset)
-emptyrset_selector = deprecated()(empty_rset)
-onelinerset_selector = deprecated()(one_line_rset)
-twolinerset_selector = deprecated()(two_lines_rset)
-twocolrset_selector = deprecated()(two_cols_rset)
-largerset_selector = deprecated()(paginated_rset)
-sortedrset_selector = deprecated()(sorted_rset)
-oneetyperset_selector = deprecated()(one_etype_rset)
-multitype_selector = deprecated()(two_etypes_rset)
-anonymous_selector = deprecated()(anonymous_user)
-not_anonymous_selector = deprecated()(authenticated_user)
-primaryview_selector = deprecated()(primary_view)
-contextprop_selector = deprecated()(match_context_prop)
-
-@deprecated('use non_final_entity instead of %s')
-def nfentity_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- return non_final_entity()(cls, req, rset, row, col)
-
-@deprecated('use implements instead of %s')
-def implement_interface(cls, req, rset=None, row=None, col=0, **kwargs):
- return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
-_interface_selector = deprecated()(implement_interface)
-interface_selector = deprecated()(implement_interface)
-
-@deprecated('use specified_etype_implements instead of %s')
-def accept_etype(cls, req, *args, **kwargs):
- """check etype presence in request form *and* accepts conformance"""
- return specified_etype_implements(*cls.accepts)(cls, req, *args)
-etype_form_selector = accept_etype
-
-@deprecated('use match_search_state instead of %s')
-def searchstate_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- return match_search_state(cls.search_states)(cls, req, rset, row, col)
-
-@deprecated('use match_user_groups instead of %s')
-def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
- return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs)
-in_group_selector = match_user_group
-
-@deprecated('use relation_possible instead of %s')
-def has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
- return relation_possible(cls.rtype, role(cls), cls.etype,
- getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use relation_possible instead of %s')
-def one_has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
- return relation_possible(cls.rtype, role(cls), cls.etype,
- getattr(cls, 'require_permission', 'read',
- once_is_enough=True))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use implements instead of %s')
-def accept_rset(cls, req, rset=None, row=None, col=0, **kwargs):
- """simply delegate to cls.accept_rset method"""
- return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
-accept_rset_selector = accept_rset
-
-accept = chainall(non_final_entity(), accept_rset, name='accept')
-accept = deprecated('use implements selector')(accept)
-accept_selector = deprecated()(accept)
-
-accept_one = deprecated()(chainall(one_line_rset, accept,
- name='accept_one'))
-accept_one_selector = deprecated()(accept_one)
+but_etype = class_renamed('but_etype', _but_etype, 'use ~implements(*etypes) instead')
-def _rql_condition(cls, req, rset=None, row=None, col=0, **kwargs):
- if cls.condition:
- return rql_condition(cls.condition)(cls, req, rset, row, col)
- return 1
-_rqlcondition_selector = deprecated()(_rql_condition)
-
-rqlcondition_selector = deprecated()(chainall(non_final_entity(), one_line_rset, _rql_condition,
- name='rql_condition'))
-
-@deprecated('use but_etype instead of %s')
-def but_etype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- return but_etype(cls.etype)(cls, req, rset, row, col)
-
-@lltrace
-def etype_rtype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- schema = cls.schema
- perm = getattr(cls, 'require_permission', 'read')
- if hasattr(cls, 'etype'):
- eschema = schema.eschema(cls.etype)
- if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
- return 0
- if hasattr(cls, 'rtype'):
- rschema = schema.rschema(cls.rtype)
- if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
- return 0
- return 1
-etype_rtype_selector = deprecated()(etype_rtype_selector)
-
-#req_form_params_selector = deprecated()(match_form_params) # form_params
-#kwargs_selector = deprecated()(match_kwargs) # expected_kwargs
-
-# compound selectors ##########################################################
-
-searchstate_accept = chainall(nonempty_rset(), accept,
- name='searchstate_accept')
-searchstate_accept_selector = deprecated()(searchstate_accept)
-
-searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
- name='searchstate_accept_one')
-searchstate_accept_one_selector = deprecated()(searchstate_accept_one)
-
-searchstate_accept = deprecated()(searchstate_accept)
-searchstate_accept_one = deprecated()(searchstate_accept_one)
-
-# end of deprecation section ##################################################
-
-def unbind_method(selector):
- def new_selector(registered):
- # get the unbound method
- if hasattr(registered, 'im_func'):
- registered = registered.im_func
- # don't rebind since it will be done automatically during
- # the assignment, inside the destination class body
- return selector(registered)
- new_selector.__name__ = selector.__name__
- return new_selector
-
-
-def deprecate(registered, msg):
- # get the unbound method
- if hasattr(registered, 'im_func'):
- registered = registered.im_func
- def _deprecate(cls, vreg):
- warn(msg, DeprecationWarning)
- return registered(cls, vreg)
- return _deprecate
-
-@unbind_method
-def require_group_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'require_groups', None):
- warn('use "match_user_groups(group1, group2)" instead of using require_groups',
- DeprecationWarning)
- cls.__select__ &= match_user_groups(cls.require_groups)
- return cls
- return plug_selector
-
-@unbind_method
-def accepts_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'accepts', None):
- warn('use "implements("EntityType", IFace)" instead of using accepts on %s'
- % cls,
- DeprecationWarning)
- cls.__select__ &= implements(*cls.accepts)
- return cls
- return plug_selector
-
-@unbind_method
-def accepts_etype_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'accepts', None):
- warn('use "specified_etype_implements("EntityType", IFace)" instead of using accepts',
- DeprecationWarning)
- cls.__select__ &= specified_etype_implements(*cls.accepts)
- return cls
- return plug_selector
-
-@unbind_method
-def condition_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'condition', None):
- warn('use "use rql_condition(expression)" instead of using condition',
- DeprecationWarning)
- cls.__select__ &= rql_condition(cls.condition)
- return cls
- return plug_selector
-
-@unbind_method
-def has_relation_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'etype', None):
- warn('use relation_possible selector instead of using etype_rtype',
- DeprecationWarning)
- cls.__select__ &= relation_possible(cls.rtype, role(cls),
- getattr(cls, 'etype', None),
- action=getattr(cls, 'require_permission', 'read'))
- return cls
- return plug_selector
-
+# XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)?
+# take care at the implementation though (looking for the 'row' argument's
+# value)
+two_lines_rset = class_renamed('two_lines_rset', multi_lines_rset)
+two_cols_rset = class_renamed('two_cols_rset', multi_columns_rset)
+two_etypes_rset = class_renamed('two_etypes_rset', multi_etypes_rset)