major selector refactoring (mostly to avoid looking for select parameters on the target class), start accept / interface unification)
--- a/common/appobject.py Mon Feb 16 16:24:24 2009 +0100
+++ b/common/appobject.py Mon Feb 16 18:26:13 2009 +0100
@@ -304,91 +304,6 @@
if first in ('insert', 'set', 'delete'):
raise Unauthorized(self.req._('only select queries are authorized'))
- # .accepts handling utilities #############################################
-
- accepts = ('Any',)
-
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- """apply the following rules:
- * if row is None, return the sum of values returned by the method
- for each entity's type in the result set. If any score is 0,
- return 0.
- * if row is specified, return the value returned by the method with
- the entity's type of this row
- """
- if row is None:
- score = 0
- for etype in rset.column_types(0):
- accepted = cls.accept(req.user, etype)
- if not accepted:
- return 0
- score += accepted
- return score
- return cls.accept(req.user, rset.description[row][col or 0])
-
- @classmethod
- def accept(cls, user, etype):
- """score etype, returning better score on exact match"""
- if 'Any' in cls.accepts:
- return 1
- eschema = cls.schema.eschema(etype)
- matching_types = [e.type for e in eschema.ancestors()]
- matching_types.append(etype)
- for index, basetype in enumerate(matching_types):
- if basetype in cls.accepts:
- return 2 + index
- return 0
-
- # .rtype handling utilities ##############################################
-
- @classmethod
- def relation_possible(cls, etype):
- """tell if a relation with etype entity is possible according to
- mixed class'.etype, .rtype and .target attributes
-
- XXX should probably be moved out to a function
- """
- schema = cls.schema
- rtype = cls.rtype
- eschema = schema.eschema(etype)
- if hasattr(cls, 'role'):
- role = cls.role
- elif cls.target == 'subject':
- role = 'object'
- else:
- role = 'subject'
- # check if this relation is possible according to the schema
- try:
- if role == 'object':
- rschema = eschema.object_relation(rtype)
- else:
- rschema = eschema.subject_relation(rtype)
- except KeyError:
- return False
- if hasattr(cls, 'etype'):
- letype = cls.etype
- try:
- if role == 'object':
- return etype in rschema.objects(letype)
- else:
- return etype in rschema.subjects(letype)
- except KeyError, ex:
- return False
- return True
-
-
- # XXX deprecated (since 2.43) ##########################
-
- @obsolete('use req.datadir_url')
- def datadir_url(self):
- """return url of the application's data directory"""
- return self.req.datadir_url
-
- @obsolete('use req.external_resource()')
- def external_resource(self, rid, default=_MARKER):
- return self.req.external_resource(rid, default)
-
class AppObject(AppRsetObject):
"""base class for application objects which are not selected
--- a/common/entity.py Mon Feb 16 16:24:24 2009 +0100
+++ b/common/entity.py Mon Feb 16 18:26:13 2009 +0100
@@ -10,6 +10,7 @@
from logilab.common.compat import all
from logilab.common.decorators import cached
from logilab.mtconverter import TransformData, TransformError
+
from rql.utils import rqlvar_maker
from cubicweb import Unauthorized
--- a/common/registerers.py Mon Feb 16 16:24:24 2009 +0100
+++ b/common/registerers.py Mon Feb 16 18:26:13 2009 +0100
@@ -5,7 +5,7 @@
to the application's schema or to already registered object
:organization: Logilab
-:copyright: 2006-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2006-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -84,13 +84,14 @@
# remove it latter if no object is implementing accepted interfaces
if _accepts_interfaces(self.vobject):
return self.vobject
- if not 'Any' in self.vobject.accepts:
- for ertype in self.vobject.accepts:
- if ertype in self.schema:
- break
- else:
- self.skip()
- return None
+# XXX no more .accepts attribute
+# if not 'Any' in self.vobject.accepts:
+# for ertype in self.vobject.accepts:
+# if ertype in self.schema:
+# break
+# else:
+# self.skip()
+# return None
for required in getattr(self.vobject, 'requires', ()):
if required not in self.schema:
self.skip()
--- a/common/selectors.py Mon Feb 16 16:24:24 2009 +0100
+++ b/common/selectors.py Mon Feb 16 18:26:13 2009 +0100
@@ -43,10 +43,13 @@
from logilab.common.compat import all
from logilab.common.deprecation import deprecated_function
+from logilab.common.interface import implements as implements_iface
+
+from yams import BASE_TYPES
from cubicweb import Unauthorized, NoSelectableObject, role
+from cubicweb.vregistry import NoSelectableObject, Selector, chainall, chainfirst
from cubicweb.cwvreg import DummyCursorError
-from cubicweb.vregistry import chainall, chainfirst, NoSelectableObject
from cubicweb.cwconfig import CubicWebConfiguration
from cubicweb.schema import split_expression
@@ -59,9 +62,15 @@
if CubicWebConfiguration.mode == 'installed':
return selector
def traced(cls, *args, **kwargs):
+ if isinstance(cls, Selector):
+ selname = cls.__class__.__name__
+ oid = args[0].id
+ else:
+ selname = selector.__name__
+ oid = cls.id
ret = selector(cls, *args, **kwargs)
- if TRACED_OIDS == 'all' or cls.id in TRACED_OIDS:
- SELECTOR_LOGGER.warning('selector %s returned %s for %s', selector.__name__, ret, cls)
+ if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
+ SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
return ret
traced.__name__ = selector.__name__
return traced
@@ -174,7 +183,7 @@
largerset_selector = deprecated_function(paginated_rset)
@lltrace
-def sorted_rset(cls, req, rset, row=None, col=None, **kwargs):
+def sorted_rset(cls, req, rset, row=None, col=0, **kwargs):
"""accept sorted result set"""
rqlst = rset.syntax_tree()
if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
@@ -202,21 +211,24 @@
return 0
multitype_selector = deprecated_function(two_etypes_rset)
-@lltrace
-def match_search_state(cls, req, rset, row=None, col=None, **kwargs):
- """checks if the current search state is in a .search_states attribute of
- the wrapped class
+
+class match_search_state(Selector):
+ def __init__(self, *expected_states):
+ self.expected_states = expected_states
+
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ """checks if the current request search state is in one of the expected states
+ the wrapped class
- search state should be either 'normal' or 'linksearch' (eg searching for an
- object to create a relation with another)
- """
- try:
- if not req.search_state[0] in cls.search_states:
- return 0
- except AttributeError:
- return 1 # class doesn't care about search state, accept it
- return 1
-searchstate_selector = deprecated_function(match_search_state)
+ search state should be either 'normal' or 'linksearch' (eg searching for an
+ object to create a relation with another)
+ """
+ try:
+ if not req.search_state[0] in cls.search_states:
+ return 0
+ except AttributeError:
+ return 1 # class doesn't care about search state, accept it
+ return 1
@lltrace
def anonymous_user(cls, req, *args, **kwargs):
@@ -258,46 +270,331 @@
return 1
kwargs_selector = deprecated_function(match_kwargs)
+# abstract selectors ##########################################################
+
+class EClassSelector(Selector):
+ """abstract class for selectors working on the entity classes of the result
+ set
+ """
+ once_is_enough = False
+
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ if not rset:
+ return 0
+ score = 0
+ if row is None:
+ for etype in rset.column_types(col):
+ if etype is None: # outer join
+ continue
+ if etype in BASE_TYPES:
+ return 0
+ escore = self.score_class(cls.vreg.etype_class(etype), req)
+ if not escore:
+ return 0
+ elif self.once_is_enough:
+ return escore
+ score += escore
+ else:
+ etype = rset.description[row][col]
+ if etype is not None and not etype in BASE_TYPES:
+ score = self.score_class(cls.vreg.etype_class(etype), req)
+ return score and (score + 1)
+
+ def score_class(self, eclass, req):
+ raise NotImplementedError()
+
+
+class EntitySelector(Selector):
+ """abstract class for selectors working on the entity instances of the
+ result set
+ """
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ if not rset:
+ return 0
+ score = 0
+ if row is None:
+ for row, rowvalue in enumerate(rset.rows):
+ if rowvalue[col] is None: # outer join
+ continue
+ try:
+ escore = self.score_entity(rset.get_entity(row, col))
+ except NotAnEntity:
+ return 0
+ if not escore:
+ return 0
+ score += escore
+ else:
+ etype = rset.description[row][col]
+ if etype is not None: # outer join
+ try:
+ score = self.score_entity(rset.get_entity(row, col))
+ except NotAnEntity:
+ return 0
+ return score and (score + 1)
+
+ def score_entity(self, entity):
+ raise NotImplementedError()
# not so basic selectors ######################################################
+class implements(EClassSelector):
+ """initializer takes a list of interfaces or entity types as argument
+
+ * if row is None, return the number of implemented interfaces for each
+ entity's class in the result set at the specified column (or column 0).
+ If any class has no matching interface, return 0.
+ * if row is specified, return number of implemented interfaces by the
+ entity's class at this row (and column)
+
+ if some interface is an entity class, the score will reflect class
+ proximity so the most specific object'll be selected
+ """
+
+ def __init__(self, *expected_ifaces):
+ self.expected_ifaces = expected_ifaces
+
+ def score_class(self, eclass, req):
+ score = 0
+ for iface in self.expected_ifaces:
+ if isinstance(iface, basestring):
+ # entity type
+ iface = eclass.vreg.etype_class(iface)
+ if implements_iface(eclass, iface):
+ score += 1
+ if getattr(iface, '__registry__', None) == 'etypes':
+ # adjust score if the interface is an entity class
+ if iface is eclass:
+ score += len(eclass.e_schema.ancestors()) + 1
+ else:
+ parents = [e.type for e in eclass.e_schema.ancestors()]
+ for index, etype in enumerate(reversed(parents)):
+ basecls = eclass.vreg.etype_class(etype)
+ if iface is basecls:
+ score += index + 1
+ break
+ return score
+
+
+class relation_possible(EClassSelector):
+ """initializer takes relation name as argument and an optional role (default
+ as subject) and target type (default to unspecified)
+
+ * if row is None, return 1 if every entity's class in the result set at the
+ specified column (or column 0) may have this relation (as role). If target
+ type is specified, check the relation's end may be of this target type.
+
+ * if row is specified, check relation is supported by the entity's class at
+ this row (and column)
+ """
+ def __init__(self, rtype, role='subject', target_etype=None,
+ permission='read', once_is_enough=False):
+ self.rtype = rtype
+ self.role = role
+ self.target_etype = target_etype
+ self.permission = permission
+ self.once_is_enough = once_is_enough
+
+ @lltrace
+ def __call__(self, cls, *args, **kwargs):
+ rschema = cls.schema.rschema(self.rtype)
+ if not (rschema.has_perm(req, self.permission)
+ or rschema.has_local_role(self.permission)):
+ return 0
+ return super(relation_possible, self)(cls, *args, **kwargs)
+
+ def score_class(self, eclass, req):
+ eschema = eclass.e_schema
+ try:
+ if self.role == 'object':
+ rschema = eschema.object_relation(self.rtype)
+ else:
+ rschema = eschema.subject_relation(self.rtype)
+ except KeyError:
+ return 0
+ if self.target_etype is not None:
+ try:
+ if self.role == 'object':
+ return self.target_etype in rschema.objects(eschema)
+ else:
+ return self.target_etype in rschema.subjects(eschema)
+ except KeyError, ex:
+ return 0
+ return 1
+
+
+class non_final_entity(EClassSelector):
+ """initializer takes no argument
+
+ * if row is None, return 1 if there are only non final entity's class in the
+ result set at the specified column (or column 0)
+ * if row is specified, return 1 if entity's class at this row (and column)
+ isn't final
+ """
+ def score_class(self, eclass, req):
+ return int(not eclass.e_schema.is_final())
+
+
+class match_user_groups(Selector):
+ """initializer takes users group as argument
+
+ * check logged user is in one of the given groups. If special 'owners' group
+ 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
+ """
+
+ def __init__(self, *required_groups):
+ self.required_groups = required_groups
+
+ @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.require_groups)
+ score = user.matching_groups(self.require_groups)
+ if not score and 'owners' in self.require_groups and rset:
+ nbowned = 0
+ if row is not None:
+ if not user.owns(rset[row][col]):
+ return 0
+ score = 1
+ else:
+ score = all(user.owns(r[col or 0]) for r in rset)
+ return 0
+
+
+class has_editable_relation(EntitySelector):
+ """initializer takes no argument
+
+ * if row is specified check the entity at the given row/col has some
+ relation editable by the logged user
+ * if row is not specified check all entities in col are owned have some
+ relation editable by the logged userlogged user
+ """
+
+ def score_entity(self, entity):
+ # if user has no update right but it can modify some relation,
+ # display action anyway
+ for dummy in entity.srelations_by_category(('generic', 'metadata'),
+ 'add'):
+ return 1
+ for rschema, targetschemas, role in entity.relations_by_category(
+ ('primary', 'secondary'), 'add'):
+ if not rschema.is_final():
+ return 1
+ return 0
+
+
+class may_add_relation(EntitySelector):
+ """initializer a relation type and optional role (default to 'subject') as
+ argument
+
+ if row is specified check the relation may be added to the entity at the
+ given row/col (if row specified) or to every entities in the given col (if
+ row is not specified)
+ """
+
+ def __init__(self, rtype, role='subject'):
+ 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(req, 'add', fromeid=entity.eid):
+ return False
+ elif not rschema.has_perm(req, 'add', toeid=entity.eid):
+ return False
+ return True
+
+
+class has_permission(EntitySelector):
+ """initializer takes a schema action (eg 'read'/'add'/'delete'/'update') as
+ argument
+
+ * if row is specified check user has permission to do the requested action
+ on the entity at the given row/col
+ * if row is specified check user has permission to do the requested action
+ on all entities in the given col
+ """
+ def __init__(self, schema_action):
+ self.schema_action = schema_action
+
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ user = req.user
+ action = self.schema_action
+ if row is None:
+ score = 0
+ need_local_check = []
+ geteschema = cls.schema.eschema
+ for etype in rset.column_types(0):
+ if etype in BASE_TYPES:
+ return 0
+ eschema = geteschema(etype)
+ if not user.matching_groups(eschema.get_groups(action)):
+ if eschema.has_local_role(action):
+ # have to ckeck local roles
+ need_local_check.append(eschema)
+ continue
+ else:
+ # even a local role won't be enough
+ return 0
+ score += accepted
+ if need_local_check:
+ # check local role for entities of necessary types
+ for i, row in enumerate(rset):
+ if not rset.description[i][0] in need_local_check:
+ continue
+ if not self.score_entity(rset.get_entity(i, col)):
+ return 0
+ score += 1
+ return score
+ if rset.description[row][col] in BASE_TYPES:
+ return 0
+ return self.score_entity(rset.get_entity(row, col))
+
+ def score_entity(self, entity):
+ if entity.has_perm(self.schema_action):
+ return 1
+ return 0
+
+
+class has_add_permission(EClassSelector):
+
+ def score_class(self, eclass, req):
+ eschema = eclass.e_schema
+ if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
+ and eschema.has_perm(req, 'add'):
+ return 1
+ return 0
+
+
+class score_entity(EntitySelector):
+ def __init__(self, scorefunc):
+ self.score_entity = scorefunc
+
+# XXX not so basic selectors ######################################################
+
@lltrace
def accept_etype(cls, req, *args, **kwargs):
"""check etype presence in request form *and* accepts conformance"""
- if 'etype' not in req.form and 'etype' not in kwargs:
- return 0
try:
etype = req.form['etype']
except KeyError:
- etype = kwargs['etype']
- # value is a list or a tuple if web request form received several
- # values for etype parameter
- assert isinstance(etype, basestring), "got multiple etype parameters in req.form"
- if 'Any' in cls.accepts:
- return 1
- # no Any found, we *need* exact match
- if etype not in cls.accepts:
- return 0
- # exact match must return a greater value than 'Any'-match
- return 2
+ try:
+ etype = kwargs['etype']
+ except KeyError:
+ return 0
+ return implements(*cls.accepts).score_class(cls.vreg.etype_class(etype), req)
etype_form_selector = deprecated_function(accept_etype)
@lltrace
-def _non_final_entity(cls, req, rset, row=None, col=None, **kwargs):
- """accept non final entities
- if row is not specified, use the first one
- if col is not specified, use the first one
- """
- etype = rset.description[row or 0][col or 0]
- if etype is None: # outer join
- return 0
- if cls.schema.eschema(etype).is_final():
- return 0
- return 1
-_nfentity_selector = deprecated_function(_non_final_entity)
-
-@lltrace
-def _rql_condition(cls, req, rset, row=None, col=None, **kwargs):
+def _rql_condition(cls, req, rset, row=None, col=0, **kwargs):
"""accept single entity result set if the entity match an rql condition
"""
if cls.condition:
@@ -313,89 +610,9 @@
return 1
_rqlcondition_selector = deprecated_function(_rql_condition)
-
+
@lltrace
-def _implement_interface(cls, req, rset, row=None, col=None, **kwargs):
- """accept uniform result sets, and apply the following rules:
-
- * wrapped class must have a accepts_interfaces attribute listing the
- accepted ORed interfaces
- * if row is None, return the sum of values returned by the method
- for each entity's class in the result set. If any score is 0,
- return 0.
- * if row is specified, return the value returned by the method with
- the entity's class of this row
- """
- # XXX this selector can be refactored : extract the code testing
- # for entity schema / interface compliance
- score = 0
- # check 'accepts' to give priority to more specific classes
- if row is None:
- for etype in rset.column_types(col or 0):
- eclass = cls.vreg.etype_class(etype)
- escore = 0
- for iface in cls.accepts_interfaces:
- escore += iface.is_implemented_by(eclass)
- if not escore:
- return 0
- score += escore
- accepts = set(getattr(cls, 'accepts', ()))
- # if accepts is defined on the vobject, eclass must match
- if accepts:
- eschema = eclass.e_schema
- etypes = set([eschema] + eschema.ancestors())
- if accepts & etypes:
- score += 2
- elif 'Any' not in accepts:
- return 0
- return score + 1
- etype = rset.description[row][col or 0]
- if etype is None: # outer join
- return 0
- eclass = cls.vreg.etype_class(etype)
- for iface in cls.accepts_interfaces:
- score += iface.is_implemented_by(eclass)
- if score:
- accepts = set(getattr(cls, 'accepts', ()))
- # if accepts is defined on the vobject, eclass must match
- if accepts:
- eschema = eclass.e_schema
- etypes = set([eschema] + eschema.ancestors())
- if accepts & etypes:
- score += 1
- elif 'Any' not in accepts:
- return 0
- score += 1
- return score
-_interface_selector = deprecated_function(_implement_interface)
-
-@lltrace
-def score_entity_selector(cls, req, rset, row=None, col=None, **kwargs):
- if row is None:
- rows = xrange(rset.rowcount)
- else:
- rows = (row,)
- for row in rows:
- try:
- score = cls.score_entity(rset.get_entity(row, col or 0))
- except DummyCursorError:
- # get a dummy cursor error, that means we are currently
- # using a dummy rset to list possible views for an entity
- # type, not for an actual result set. In that case, we
- # don't care of the value, consider the object as selectable
- return 1
- if not score:
- return 0
- return 1
-
-@lltrace
-def accept_rset(cls, req, rset, row=None, col=None, **kwargs):
- """simply delegate to cls.accept_rset method"""
- return cls.accept_rset(req, rset, row=row, col=col)
-accept_rset_selector = deprecated_function(accept_rset)
-
-@lltrace
-def but_etype(cls, req, rset, row=None, col=None, **kwargs):
+def but_etype(cls, req, rset, row=None, col=0, **kwargs):
"""restrict the searchstate_accept_one_selector to exclude entity's type
refered by the .etype attribute
"""
@@ -405,7 +622,7 @@
but_etype_selector = deprecated_function(but_etype)
@lltrace
-def etype_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
+def etype_rtype_selector(cls, req, rset, row=None, col=0, **kwargs):
"""only check if the user has read access on the entity's type refered
by the .etype attribute and on the relations's type refered by the
.rtype attribute if set.
@@ -423,75 +640,11 @@
return 1
@lltrace
-def has_relation(cls, req, rset, row=None, col=None, **kwargs):
- """check if the user has read access on the relations's type refered by the
- .rtype attribute of the class, and if all entities types in the
- result set has this relation.
- """
- if hasattr(cls, 'rtype'):
- rschema = cls.schema.rschema(cls.rtype)
- perm = getattr(cls, 'require_permission', 'read')
- if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
- return 0
- if row is None:
- for etype in rset.column_types(col or 0):
- if not cls.relation_possible(etype):
- return 0
- elif not cls.relation_possible(rset.description[row][col or 0]):
- return 0
- return 1
-accept_rtype_selector = deprecated_function(has_relation)
+def has_related_entities(cls, req, rset, row=None, col=0, **kwargs):
+ return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
@lltrace
-def one_has_relation(cls, req, rset, row=None, col=None, **kwargs):
- """check if the user has read access on the relations's type refered by the
- .rtype attribute of the class, and if at least one entity type in the
- result set has this relation.
- """
- rschema = cls.schema.rschema(cls.rtype)
- perm = getattr(cls, 'require_permission', 'read')
- if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
- return 0
- if row is None:
- for etype in rset.column_types(col or 0):
- if cls.relation_possible(etype):
- return 1
- elif cls.relation_possible(rset.description[row][col or 0]):
- return 1
- return 0
-one_has_relation_selector = deprecated_function(one_has_relation)
-
-@lltrace
-def has_related_entities(cls, req, rset, row=None, col=None, **kwargs):
- return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
-
-
-@lltrace
-def match_user_group(cls, req, rset=None, row=None, col=None, **kwargs):
- """select according to user's groups"""
- if not cls.require_groups:
- return 1
- user = req.user
- if user is None:
- return int('guests' in cls.require_groups)
- score = 0
- if 'owners' in cls.require_groups and rset:
- if row is not None:
- eid = rset[row][col or 0]
- if user.owns(eid):
- score = 1
- else:
- score = all(user.owns(r[col or 0]) for r in rset)
- score += user.matching_groups(cls.require_groups)
- if score:
- # add 1 so that an object with one matching group take priority
- # on an object without require_groups
- return score + 1
- return 0
-in_group_selector = deprecated_function(match_user_group)
-
-@lltrace
-def user_can_add_etype(cls, req, rset, row=None, col=None, **kwargs):
+def user_can_add_etype(cls, req, rset, row=None, col=0, **kwargs):
"""only check if the user has add access on the entity's type refered
by the .etype attribute.
"""
@@ -501,7 +654,7 @@
add_etype_selector = deprecated_function(user_can_add_etype)
@lltrace
-def match_context_prop(cls, req, rset, row=None, col=None, context=None,
+def match_context_prop(cls, req, rset, row=None, col=0, context=None,
**kwargs):
propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
if not propval:
@@ -512,7 +665,7 @@
contextprop_selector = deprecated_function(match_context_prop)
@lltrace
-def primary_view(cls, req, rset, row=None, col=None, view=None,
+def primary_view(cls, req, rset, row=None, col=0, view=None,
**kwargs):
if view is not None and not view.is_primary():
return 0
@@ -533,39 +686,70 @@
return selector
+
+# XXX DEPRECATED ##############################################################
+
+def nfentity_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return non_final_entity()(cls, req, rset, row, col)
+nfentity_selector = deprecated_function(nfentity_selector)
+
+def implement_interface(cls, req, rset, row=None, col=0, **kwargs):
+ return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
+_interface_selector = deprecated_function(implement_interface)
+interface_selector = deprecated_function(implement_interface)
+implement_interface = deprecated_function(implement_interface)
+
+def searchstate_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return match_search_state(cls.search_states)(cls, req, rset, row, col)
+searchstate_selector = deprecated_function(searchstate_selector)
+
+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 = deprecated_function(match_user_group)
+match_user_group = deprecated_function(match_user_group)
+
+def has_relation(cls, req, rset, 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)
+has_relation = deprecated_function(has_relation)
+
+def one_has_relation(cls, req, rset, 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)
+one_has_relation = deprecated_function(one_has_relation, 'use relation_possible selector')
+
+def accept_rset(cls, req, rset, 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 = deprecated_function(accept_rset)
+accept_rset = deprecated_function(accept_rset, 'use implements selector')
+
+accept = chainall(non_final_entity(), accept_rset, name='accept')
+accept_selector = deprecated_function(accept)
+accept = deprecated_function(accept, 'use implements selector')
+
# compound selectors ##########################################################
-non_final_entity = chainall(nonempty_rset, _non_final_entity)
-non_final_entity.__name__ = 'non_final_entity'
-nfentity_selector = deprecated_function(non_final_entity)
-
-implement_interface = chainall(non_final_entity, _implement_interface)
-implement_interface.__name__ = 'implement_interface'
-interface_selector = deprecated_function(implement_interface)
-
-accept = chainall(non_final_entity, accept_rset)
-accept.__name__ = 'accept'
-accept_selector = deprecated_function(accept)
-
-accept_one = chainall(one_line_rset, accept)
-accept_one.__name__ = 'accept_one'
+accept_one = deprecated_function(chainall(one_line_rset, accept,
+ name='accept_one'))
accept_one_selector = deprecated_function(accept_one)
-rql_condition = chainall(non_final_entity, one_line_rset, _rql_condition)
-rql_condition.__name__ = 'rql_condition'
+rql_condition = chainall(non_final_entity(), one_line_rset, _rql_condition,
+ name='rql_condition')
rqlcondition_selector = deprecated_function(rql_condition)
-searchstate_accept = chainall(nonempty_rset, match_search_state, accept)
-searchstate_accept.__name__ = 'searchstate_accept'
+searchstate_accept = chainall(nonempty_rset, match_search_state, accept,
+ name='searchstate_accept')
searchstate_accept_selector = deprecated_function(searchstate_accept)
searchstate_accept_one = chainall(one_line_rset, match_search_state,
- accept, _rql_condition)
-searchstate_accept_one.__name__ = 'searchstate_accept_one'
+ accept, _rql_condition,
+ name='searchstate_accept_one')
searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
-searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype)
-searchstate_accept_one_but_etype.__name__ = 'searchstate_accept_one_but_etype'
+searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype,
+ name='searchstate_accept_one_but_etype')
searchstate_accept_one_but_etype_selector = deprecated_function(
searchstate_accept_one_but_etype)
--- a/cwvreg.py Mon Feb 16 16:24:24 2009 +0100
+++ b/cwvreg.py Mon Feb 16 18:26:13 2009 +0100
@@ -1,7 +1,7 @@
"""extend the generic VRegistry with some cubicweb specific stuff
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -9,6 +9,7 @@
from warnings import warn
from logilab.common.decorators import cached, clear_cache
+from logilab.common.interface import extend
from rql import RQLHelper
@@ -129,6 +130,8 @@
default to a dump of the class registered for 'Any'
"""
etype = str(etype)
+ if etype == 'Any':
+ return self.select(self.registry_objects('etypes', 'Any'), 'Any')
eschema = self.schema.eschema(etype)
baseschemas = [eschema] + eschema.ancestors()
# browse ancestors from most specific to most generic and
@@ -136,12 +139,21 @@
for baseschema in baseschemas:
btype = str(baseschema)
try:
- return self.select(self.registry_objects('etypes', btype), etype)
+ cls = self.select(self.registry_objects('etypes', btype), etype)
+ break
except ObjectNotFound:
pass
- # no entity class for any of the ancestors, fallback to the default one
- return self.select(self.registry_objects('etypes', 'Any'), etype)
-
+ else:
+ # no entity class for any of the ancestors, fallback to the default
+ # one
+ cls = self.select(self.registry_objects('etypes', 'Any'), etype)
+ # add class itself to the list of implemented interfaces, as well as the
+ # Any entity class so we can select according to class using the
+ # `implements` selector
+ extend(cls, cls)
+ extend(cls, self.etype_class('Any'))
+ return cls
+
def render(self, registry, oid, req, **context):
"""select an object in a given registry and render it
--- a/test/unittest_rset.py Mon Feb 16 16:24:24 2009 +0100
+++ b/test/unittest_rset.py Mon Feb 16 18:26:13 2009 +0100
@@ -1,8 +1,11 @@
# coding: utf-8
"""unit tests for module cubicweb.common.utils"""
+from __future__ import with_statement
from logilab.common.testlib import TestCase, unittest_main
+
from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.common.selectors import traced_selection
from urlparse import urlsplit
from rql import parse
--- a/test/unittest_vregistry.py Mon Feb 16 16:24:24 2009 +0100
+++ b/test/unittest_vregistry.py Mon Feb 16 18:26:13 2009 +0100
@@ -21,7 +21,8 @@
def test_load(self):
self.vreg.load_file(join(BASE, 'web', 'views'), 'euser.py')
self.vreg.load_file(join(BASE, 'web', 'views'), 'baseviews.py')
- fpvc = [v for v in self.vreg.registry_objects('views', 'primary') if v.accepts[0] == 'EUser'][0]
+ fpvc = [v for v in self.vreg.registry_objects('views', 'primary')
+ i f v.__module__ == 'cubicweb.web.views.euser'][0]
fpv = fpvc(None, None)
# don't want a TypeError due to super call
self.assertRaises(AttributeError, fpv.render_entity_attributes, None, None)
--- a/vregistry.py Mon Feb 16 16:24:24 2009 +0100
+++ b/vregistry.py Mon Feb 16 18:26:13 2009 +0100
@@ -513,7 +513,7 @@
# advanced selector building functions ########################################
-def chainall(*selectors):
+def chainall(*selectors, **kwargs):
"""return a selector chaining given selectors. If one of
the selectors fail, selection will fail, else the returned score
will be the sum of each selector'score
@@ -527,9 +527,11 @@
return 0
score += partscore
return score
+ if 'name' in kwargs:
+ selector.__name__ = kwargs['name']
return selector
-def chainfirst(*selectors):
+def chainfirst(*selectors, **kwargs):
"""return a selector chaining given selectors. If all
the selectors fail, selection will fail, else the returned score
will be the first non-zero selector score
@@ -541,10 +543,13 @@
if partscore:
return partscore
return 0
+ if 'name' in kwargs:
+ selector.__name__ = kwargs['name']
return selector
# selector base classes and operations ########################################
+
class Selector(object):
"""base class for selector classes providing implementation
for operators ``&`` and ``|``
--- a/web/action.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/action.py Mon Feb 16 18:26:13 2009 +0100
@@ -1,14 +1,15 @@
"""abstract action classes for CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
+from cubicweb import target
from cubicweb.common.appobject import AppRsetObject
from cubicweb.common.registerers import action_registerer
-from cubicweb.common.selectors import add_etype_selector, \
+from cubicweb.common.selectors import user_can_add_etype, \
match_search_state, searchstate_accept_one, \
searchstate_accept_one_but_etype
@@ -21,9 +22,7 @@
"""
__registry__ = 'actions'
__registerer__ = action_registerer
- __selectors__ = (match_search_state,)
- # by default actions don't appear in link search mode
- search_states = ('normal',)
+
property_defs = {
'visible': dict(type='Boolean', default=True,
help=_('display the action or not')),
@@ -37,53 +36,6 @@
site_wide = True # don't want user to configuration actions eproperties
category = 'moreactions'
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- user = req.user
- action = cls.schema_action
- if row is None:
- score = 0
- need_local_check = []
- geteschema = cls.schema.eschema
- for etype in rset.column_types(0):
- accepted = cls.accept(user, etype)
- if not accepted:
- return 0
- if action:
- eschema = geteschema(etype)
- if not user.matching_groups(eschema.get_groups(action)):
- if eschema.has_local_role(action):
- # have to ckeck local roles
- need_local_check.append(eschema)
- continue
- else:
- # even a local role won't be enough
- return 0
- score += accepted
- if need_local_check:
- # check local role for entities of necessary types
- for i, row in enumerate(rset):
- if not rset.description[i][0] in need_local_check:
- continue
- if not cls.has_permission(rset.get_entity(i, 0), action):
- return 0
- score += 1
- return score
- col = col or 0
- etype = rset.description[row][col]
- score = cls.accept(user, etype)
- if score and action:
- if not cls.has_permission(rset.get_entity(row, col), action):
- return 0
- return score
-
- @classmethod
- def has_permission(cls, entity, action):
- """defined in a separated method to ease overriding (see ModifyAction
- for instance)
- """
- return entity.has_perm(action)
-
def url(self):
"""return the url associated with this action"""
raise NotImplementedError
@@ -94,6 +46,7 @@
if self.category:
return 'box' + self.category.capitalize()
+
class UnregisteredAction(Action):
"""non registered action used to build boxes. Unless you set them
explicitly, .vreg and .schema attributes at least are None.
@@ -115,7 +68,7 @@
"""link to the entity creation form. Concrete class must set .etype and
may override .vid
"""
- __selectors__ = (add_etype_selector, match_search_state)
+ __selectors__ = (user_can_add_etype,)
vid = 'creation'
etype = None
@@ -127,21 +80,9 @@
"""an action for an entity. By default entity actions are only
displayable on single entity result if accept match.
"""
- __selectors__ = (searchstate_accept_one,)
- schema_action = None
- condition = None
+ # XXX deprecate
- @classmethod
- def accept(cls, user, etype):
- score = super(EntityAction, cls).accept(user, etype)
- if not score:
- return 0
- # check if this type of entity has the necessary relation
- if hasattr(cls, 'rtype') and not cls.relation_possible(etype):
- return 0
- return score
-
class LinkToEntityAction(EntityAction):
"""base class for actions consisting to create a new object
with an initial relation set to an entity.
@@ -149,64 +90,19 @@
using .etype, .rtype and .target attributes to check if the
action apply and if the logged user has access to it
"""
- etype = None
- rtype = None
- target = None
+ def my_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return chainall(match_search_state('normal'),
+ one_line_rset, accept,
+ relation_possible(cls.rtype, role(cls), cls.etype,
+ permission='add'),
+ may_add_relation(cls.rtype, role(cls)))
+ __selectors__ = my_selector,
+
category = 'addrelated'
-
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- entity = rset.get_entity(row or 0, col or 0)
- # check if this type of entity has the necessary relation
- if hasattr(cls, 'rtype') and not cls.relation_possible(entity.e_schema):
- return 0
- score = cls.accept(req.user, entity.e_schema)
- if not score:
- return 0
- if not cls.check_perms(req, entity):
- return 0
- return score
-
- @classmethod
- def check_perms(cls, req, entity):
- if not cls.check_rtype_perm(req, entity):
- return False
- # XXX document this:
- # if user can create the relation, suppose it can create the entity
- # this is because we usually can't check "add" permission before the
- # entity has actually been created, and schema security should be
- # defined considering this
- #if not cls.check_etype_perm(req, entity):
- # return False
- return True
-
- @classmethod
- def check_etype_perm(cls, req, entity):
- eschema = cls.schema.eschema(cls.etype)
- if not eschema.has_perm(req, 'add'):
- #print req.user.login, 'has no add perm on etype', cls.etype
- return False
- #print 'etype perm ok', cls
- return True
-
- @classmethod
- def check_rtype_perm(cls, req, entity):
- rschema = cls.schema.rschema(cls.rtype)
- # cls.target is telling us if we want to add the subject or object of
- # the relation
- if cls.target == 'subject':
- if not rschema.has_perm(req, 'add', toeid=entity.eid):
- #print req.user.login, 'has no add perm on subject rel', cls.rtype, 'with', entity
- return False
- elif not rschema.has_perm(req, 'add', fromeid=entity.eid):
- #print req.user.login, 'has no add perm on object rel', cls.rtype, 'with', entity
- return False
- #print 'rtype perm ok', cls
- return True
-
+
def url(self):
current_entity = self.rset.get_entity(self.row or 0, self.col or 0)
- linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, self.target)
+ linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, target(self))
return self.build_url(vid='creation', etype=self.etype,
__linkto=linkto,
__redirectpath=current_entity.rest_path(), # should not be url quoted!
@@ -217,5 +113,10 @@
"""LinkToEntity action where the action is not usable on the same
entity's type as the one refered by the .etype attribute
"""
- __selectors__ = (searchstate_accept_one_but_etype,)
+ def my_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return chainall(match_search_state('normal'),
+ but_etype, one_line_rset, accept,
+ relation_possible(cls.rtype, role(cls), cls.etype),
+ may_add_relation(cls.rtype, role(cls)))
+ __selectors__ = my_selector,
--- a/web/views/__init__.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/__init__.py Mon Feb 16 18:26:13 2009 +0100
@@ -1,7 +1,7 @@
"""Views/forms and actions for the CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -67,30 +67,16 @@
return 'outofcontext-search'
return 'list'
return 'table'
-
-def linksearch_match(req, rset):
- """when searching an entity to create a relation, return True if entities in
- the given rset may be used as relation end
- """
- try:
- searchedtype = req.search_state[1][-1]
- except IndexError:
- return 0 # no searching for association
- for etype in rset.column_types(0):
- if etype != searchedtype:
- return 0
- return 1
def linksearch_select_url(req, rset):
"""when searching an entity to create a relation, return an url to select
entities in the given rset
"""
req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
- target, link_eid, r_type, searchedtype = req.search_state[1]
+ target, eid, r_type, searchedtype = req.search_state[1]
if target == 'subject':
- id_fmt = '%s:%s:%%s' % (link_eid, r_type)
+ id_fmt = '%s:%s:%%s' % (eid, r_type)
else:
- id_fmt = '%%s:%s:%s' % (r_type, link_eid)
+ id_fmt = '%%s:%s:%s' % (r_type, eid)
triplets = '-'.join(id_fmt % row[0] for row in rset.rows)
- return "javascript: selectForAssociation('%s', '%s');" % (triplets,
- link_eid)
+ return "javascript: selectForAssociation('%s', '%s');" % (triplets, eid)
--- a/web/views/actions.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/actions.py Mon Feb 16 18:26:13 2009 +0100
@@ -6,70 +6,86 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.selectors import (searchstate_accept, match_user_group, yes,
- one_line_rset, two_lines_rset, one_etype_rset,
- authenticated_user,
- match_search_state, chainfirst, chainall)
+from cubicweb.common.selectors import (
+ yes, one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
+ non_final_entity,
+ authenticated_user, match_user_groups, match_search_state,
+ has_editable_relation, has_permission, has_add_permission,
+ )
-from cubicweb.web.action import Action, EntityAction, LinkToEntityAction
-from cubicweb.web.views import linksearch_select_url, linksearch_match
+from cubicweb.web.action import Action
+from cubicweb.web.views import linksearch_select_url
from cubicweb.web.views.baseviews import vid_from_rset
_ = unicode
+def match_searched_etype(cls, req, rset, row=None, col=None, **kwargs):
+ return req.match_search_state(rset)
+
+def view_is_not_default_view(cls, req, rset, row, col, **kwargs):
+ # interesting if it propose another view than the current one
+ vid = req.form.get('vid')
+ if vid and vid != vid_from_rset(req, rset, cls.schema):
+ return 1
+ return 0
+
+def addable_etype_empty_rset(cls, req, rset, **kwargs):
+ if rset is not None and not rset.rowcount:
+ rqlst = rset.syntax_tree()
+ if len(rqlst.children) > 1:
+ return 0
+ select = rqlst.children[0]
+ if len(select.defined_vars) == 1 and len(select.solutions) == 1:
+ rset._searched_etype = select.solutions[0].itervalues().next()
+ eschema = cls.schema.eschema(rset._searched_etype)
+ if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
+ and eschema.has_perm(req, 'add'):
+ return 1
+ return 0
+
# generic primary actions #####################################################
-class SelectAction(EntityAction):
+class SelectAction(Action):
"""base class for link search actions. By default apply on
any size entity result search it the current state is 'linksearch'
if accept match.
"""
- category = 'mainactions'
- __selectors__ = (searchstate_accept,)
- search_states = ('linksearch',)
- order = 0
+ id = 'select'
+ __selectors__ = (match_search_state('linksearch'),
+ match_searched_etype)
- id = 'select'
title = _('select')
-
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- return linksearch_match(req, rset)
+ category = 'mainactions'
+ order = 0
def url(self):
return linksearch_select_url(self.req, self.rset)
class CancelSelectAction(Action):
+ id = 'cancel'
+ __selectors__ = (match_search_state('linksearch'),)
+
+ title = _('cancel select')
category = 'mainactions'
- search_states = ('linksearch',)
order = 10
- id = 'cancel'
- title = _('cancel select')
-
def url(self):
- target, link_eid, r_type, searched_type = self.req.search_state[1]
- return self.build_url(rql="Any X WHERE X eid %s" % link_eid,
+ target, eid, r_type, searched_type = self.req.search_state[1]
+ return self.build_url(str(eid),
vid='edition', __mode='normal')
class ViewAction(Action):
- category = 'mainactions'
- __selectors__ = (match_user_group, searchstate_accept)
- require_groups = ('users', 'managers')
- order = 0
-
id = 'view'
- title = _('view')
+ __selectors__ = (match_search_state('normal'),
+ match_user_groups('users', 'managers'),
+ view_is_not_default_view,
+ non_final_entity())
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- # interesting if it propose another view than the current one
- vid = req.form.get('vid')
- if vid and vid != vid_from_rset(req, rset, cls.schema):
- return 1
- return 0
+ title = _('view')
+ category = 'mainactions'
+ order = 0
def url(self):
params = self.req.form.copy()
@@ -79,76 +95,60 @@
**params)
-class ModifyAction(EntityAction):
- category = 'mainactions'
- __selectors__ = (one_line_rset, searchstate_accept)
- #__selectors__ = searchstate_accept,
- schema_action = 'update'
- order = 10
-
+class ModifyAction(Action):
id = 'edit'
- title = _('modify')
+ __selectors__ = (match_search_state('normal'),
+ one_line_rset,
+ has_permission('update') | has_editable_relation('add'))
- @classmethod
- def has_permission(cls, entity, action):
- if entity.has_perm(action):
- return True
- # if user has no update right but it can modify some relation,
- # display action anyway
- for dummy in entity.srelations_by_category(('generic', 'metadata'),
- 'add'):
- return True
- for rschema, targetschemas, role in entity.relations_by_category(
- ('primary', 'secondary'), 'add'):
- if not rschema.is_final():
- return True
- return False
+ title = _('modify')
+ category = 'mainactions'
+ order = 10
def url(self):
entity = self.rset.get_entity(self.row or 0, self.col or 0)
return entity.absolute_url(vid='edition')
-class MultipleEditAction(EntityAction):
+class MultipleEditAction(Action):
+ id = 'muledit' # XXX get strange conflicts if id='edit'
+ __selectors__ = (match_search_state('normal'),
+ two_lines_rset, one_etype_rset,
+ has_permission('update'))
+
+ title = _('modify')
category = 'mainactions'
- __selectors__ = (two_lines_rset, one_etype_rset,
- searchstate_accept)
- schema_action = 'update'
order = 10
- id = 'muledit' # XXX get strange conflicts if id='edit'
- title = _('modify')
-
def url(self):
return self.build_url('view', rql=self.rset.rql, vid='muledit')
# generic secondary actions ###################################################
-class ManagePermissions(LinkToEntityAction):
- accepts = ('Any',)
- category = 'moreactions'
+class ManagePermissions(Action):
id = 'addpermission'
+ __selectors__ = (
+ (match_user_groups('managers')
+ | relation_possible('require_permission', 'subject', 'EPermission')),
+ )
+
title = _('manage permissions')
+ category = 'moreactions'
order = 100
-
- etype = 'EPermission'
- rtype = 'require_permission'
- target = 'object'
def url(self):
return self.rset.get_entity(0, 0).absolute_url(vid='security')
-class DeleteAction(EntityAction):
+class DeleteAction(Action):
+ id = 'delete'
+ __selectors__ = (has_permission('delete'),)
+
+ title = _('delete')
category = 'moreactions'
- __selectors__ = (searchstate_accept,)
- schema_action = 'delete'
order = 20
- id = 'delete'
- title = _('delete')
-
def url(self):
if len(self.rset) == 1:
entity = self.rset.get_entity(0, 0)
@@ -156,14 +156,14 @@
return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
-class CopyAction(EntityAction):
+class CopyAction(Action):
+ id = 'copy'
+ __selectors__ = (has_permission('add'),)
+
+ title = _('copy')
category = 'moreactions'
- schema_action = 'add'
order = 30
- id = 'copy'
- title = _('copy')
-
def url(self):
entity = self.rset.get_entity(self.row or 0, self.col or 0)
return entity.absolute_url(vid='copy')
@@ -173,35 +173,16 @@
"""when we're seeing more than one entity with the same type, propose to
add a new one
"""
+ id = 'addentity'
+ __selectors__ = (match_search_state('normal'),
+ (addable_etype_empty_rset
+ # XXX has_add_permission in the middle so '&' is available
+ | (two_lines_rset & has_add_permission() & one_etype_rset ))
+ )
+
category = 'moreactions'
- id = 'addentity'
order = 40
- def etype_rset_selector(cls, req, rset, **kwargs):
- if rset is not None and not rset.rowcount:
- rqlst = rset.syntax_tree()
- if len(rqlst.children) > 1:
- return 0
- select = rqlst.children[0]
- if len(select.defined_vars) == 1 and len(select.solutions) == 1:
- rset._searched_etype = select.solutions[0].itervalues().next()
- eschema = cls.schema.eschema(rset._searched_etype)
- if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
- and eschema.has_perm(req, 'add'):
- return 1
- return 0
-
- def has_add_perm_selector(cls, req, rset, **kwargs):
- eschema = cls.schema.eschema(rset.description[0][0])
- if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
- and eschema.has_perm(req, 'add'):
- return 1
- return 0
- __selectors__ = (match_search_state,
- chainfirst(etype_rset_selector,
- chainall(two_lines_rset, one_etype_rset,
- has_add_perm_selector)))
-
@property
def rsettype(self):
if self.rset:
@@ -219,36 +200,36 @@
# logged user actions #########################################################
class UserPreferencesAction(Action):
+ id = 'myprefs'
+ __selectors__ = (authenticated_user,)
+
+ title = _('user preferences')
category = 'useractions'
- __selectors__ = authenticated_user,
order = 10
-
- id = 'myprefs'
- title = _('user preferences')
def url(self):
return self.build_url(self.id)
class UserInfoAction(Action):
+ id = 'myinfos'
+ __selectors__ = (authenticated_user,)
+
+ title = _('personnal informations')
category = 'useractions'
- __selectors__ = authenticated_user,
order = 20
-
- id = 'myinfos'
- title = _('personnal informations')
def url(self):
return self.build_url('euser/%s'%self.req.user.login, vid='edition')
class LogoutAction(Action):
+ id = 'logout'
+ __selectors__ = (authenticated_user,)
+
+ title = _('logout')
category = 'useractions'
- __selectors__ = authenticated_user,
order = 30
-
- id = 'logout'
- title = _('logout')
def url(self):
return self.build_url(self.id)
@@ -257,60 +238,35 @@
# site actions ################################################################
class ManagersAction(Action):
+ __abstract__ = True
+ __selectors__ = (match_user_groups('managers'),)
+
category = 'siteactions'
- __abstract__ = True
- __selectors__ = match_user_group,
- require_groups = ('managers',)
def url(self):
return self.build_url(self.id)
class SiteConfigurationAction(ManagersAction):
- order = 10
id = 'siteconfig'
title = _('site configuration')
+ order = 10
class ManageAction(ManagersAction):
- order = 20
id = 'manage'
title = _('manage')
+ order = 20
class ViewSchemaAction(Action):
+ id = 'schema'
+ __selectors__ = (yes,)
+
+ title = _("site schema")
category = 'siteactions'
- id = 'schema'
- title = _("site schema")
- __selectors__ = yes,
order = 30
def url(self):
return self.build_url(self.id)
-
-# content type specific actions ###############################################
-
-class FollowAction(EntityAction):
- category = 'mainactions'
- accepts = ('Bookmark',)
-
- id = 'follow'
- title = _('follow')
-
- def url(self):
- return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
-
-class UserPreferencesEntityAction(EntityAction):
- __selectors__ = EntityAction.__selectors__ + (one_line_rset, match_user_group,)
- require_groups = ('owners', 'managers')
- category = 'mainactions'
- accepts = ('EUser',)
-
- id = 'prefs'
- title = _('preferences')
-
- def url(self):
- login = self.rset.get_entity(self.row or 0, self.col or 0).login
- return self.build_url('euser/%s'%login, vid='epropertiesform')
-
--- a/web/views/baseviews.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/baseviews.py Mon Feb 16 18:26:13 2009 +0100
@@ -29,7 +29,7 @@
ajax_replace_url, rql_for_eid, simple_sgml_tag)
from cubicweb.common.view import EntityView, AnyRsetView, EmptyRsetView
from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
-from cubicweb.web.views import vid_from_rset, linksearch_select_url, linksearch_match
+from cubicweb.web.views import vid_from_rset, linksearch_select_url
_ = unicode
@@ -826,7 +826,7 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
erset = entity.as_rset()
- if linksearch_match(self.req, erset):
+ if self.req.match_search_state(erset):
self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
html_escape(linksearch_select_url(self.req, erset)),
self.req._('select this entity'),
--- a/web/views/bookmark.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/bookmark.py Mon Feb 16 18:26:13 2009 +0100
@@ -9,13 +9,26 @@
from logilab.mtconverter import html_escape
from cubicweb import Unauthorized
+from cubicweb.common.selectors import implements
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
+from cubicweb.web.action import Action
from cubicweb.web.box import UserRQLBoxTemplate
from cubicweb.web.views.baseviews import PrimaryView
+class FollowAction(Action):
+ id = 'follow'
+ __selectors__ = (implements('Bookmark'),)
+
+ title = _('follow')
+ category = 'mainactions'
+
+ def url(self):
+ return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
+
+
class BookmarkPrimaryView(PrimaryView):
- accepts = ('Bookmark',)
+ __selectors__ = (implements('Bookmark'),)
def cell_call(self, row, col):
"""the primary view for bookmark entity"""
--- a/web/views/embedding.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/embedding.py Mon Feb 16 18:26:13 2009 +0100
@@ -3,7 +3,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -17,8 +17,8 @@
from cubicweb import urlquote # XXX should use view.url_quote method
from cubicweb.interfaces import IEmbedable
from cubicweb.common.uilib import soup2xhtml
-from cubicweb.common.selectors import (one_line_rset, score_entity_selector,
- match_search_state, implement_interface)
+from cubicweb.common.selectors import (one_line_rset, score_entity,
+ match_search_state, implements)
from cubicweb.common.view import NOINDEX, NOFOLLOW
from cubicweb.web.controller import Controller
from cubicweb.web.action import Action
@@ -75,30 +75,28 @@
return self.vreg.main_template(req, self.template, body=body)
+def entity_has_embedable_url(entity):
+ """return 1 if the entity provides an allowed embedable url"""
+ url = entity.embeded_url()
+ if not url or not url.strip():
+ return 0
+ allowed = entity.config['embed-allowed']
+ if allowed is None or not allowed.match(url):
+ return 0
+ return 1
+
+
class EmbedAction(Action):
"""display an 'embed' link on entity implementing `embeded_url` method
if the returned url match embeding configuration
"""
id = 'embed'
- controller = 'embed'
- __selectors__ = (one_line_rset, match_search_state,
- implement_interface, score_entity_selector)
- accepts_interfaces = (IEmbedable,)
+ __selectors__ = (one_line_rset, match_search_state('normal'),
+ implements(IEmbedable),
+ score_entity(entity_has_embedable_url))
title = _('embed')
-
- @classmethod
- def score_entity(cls, entity):
- """return a score telling how well I can display the given
- entity instance (required by the value_selector)
- """
- url = entity.embeded_url()
- if not url or not url.strip():
- return 0
- allowed = cls.config['embed-allowed']
- if allowed is None or not allowed.match(url):
- return 0
- return 1
+ controller = 'embed'
def url(self, row=0):
entity = self.rset.get_entity(row, 0)
@@ -132,6 +130,7 @@
url = '%s?custom_css=%s' % (url, self.custom_css)
return '<a href="%s"' % url
+
class absolutize_links:
def __init__(self, embedded_url, tag, custom_css=None):
self.embedded_url = embedded_url
@@ -152,7 +151,8 @@
for rgx, repl in filters:
body = rgx.sub(repl, body)
return body
-
+
+
def embed_external_page(url, prefix, headers=None, custom_css=None):
req = Request(url, headers=(headers or {}))
content = urlopen(req).read()
--- a/web/views/euser.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/euser.py Mon Feb 16 18:26:13 2009 +0100
@@ -10,12 +10,30 @@
from logilab.mtconverter import html_escape
from cubicweb.schema import display_name
+from cubicweb.common.selectors import one_line_rset, implements, match_user_groups
from cubicweb.web import INTERNAL_FIELD_VALUE
from cubicweb.web.form import EntityForm
+from cubicweb.web.action import Action
from cubicweb.web.views.baseviews import PrimaryView, EntityView
+
+class UserPreferencesEntityAction(Action):
+ id = 'prefs'
+ __selectors__ = (one_line_rset,
+ implements('EUser'),
+ match_user_groups('owners', 'managers'))
+
+ title = _('preferences')
+ category = 'mainactions'
+
+ def url(self):
+ login = self.rset.get_entity(self.row or 0, self.col or 0).login
+ return self.build_url('euser/%s'%login, vid='epropertiesform')
+
+
class EUserPrimaryView(PrimaryView):
- accepts = ('EUser',)
+ __selectors__ = (implements('EUser'),)
+
skip_attrs = ('firstname', 'surname')
def iter_relations(self, entity):
@@ -34,7 +52,8 @@
]
class FoafView(EntityView):
id = 'foaf'
- accepts = ('EUser',)
+ __selectors__ = (implements('EUser'),)
+
title = _('foaf')
templatable = False
content_type = 'text/xml'
@@ -54,7 +73,6 @@
<foaf:maker rdf:resource="%s"/>
<foaf:primaryTopic rdf:resource="%s"/>
</foaf:PersonalProfileDocument>''' % (entity.absolute_url(), entity.absolute_url()))
-
self.w(u'<foaf:Person rdf:ID="%s">\n' % entity.eid)
self.w(u'<foaf:name>%s</foaf:name>\n' % html_escape(entity.dc_long_title()))
if entity.surname:
@@ -68,11 +86,13 @@
self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
self.w(u'</foaf:Person>\n')
+
class FoafUsableView(FoafView):
id = 'foaf_usable'
def call(self):
self.cell_call(0, 0)
+
class EditGroups(EntityForm):
"""displays a simple euser / egroups editable table"""
--- a/web/views/idownloadable.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/idownloadable.py Mon Feb 16 18:26:13 2009 +0100
@@ -1,7 +1,7 @@
"""Specific views for entities implementing IDownloadable
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -10,8 +10,8 @@
from cubicweb.interfaces import IDownloadable
from cubicweb.common.mttransforms import ENGINE
-from cubicweb.common.selectors import (one_line_rset, score_entity_selector,
- implement_interface, match_context_prop)
+from cubicweb.common.selectors import (one_line_rset, score_entity,
+ implements, match_context_prop)
from cubicweb.web.box import EntityBoxTemplate
from cubicweb.web.views import baseviews
@@ -35,8 +35,7 @@
class DownloadBox(EntityBoxTemplate):
id = 'download_box'
- __selectors__ = (one_line_rset, implement_interface, match_context_prop)
- accepts_interfaces = (IDownloadable,)
+ __selectors__ = (one_line_rset, implements(IDownloadable), match_context_prop)
order = 10
def cell_call(self, row, col, title=None, label=None, **kwargs):
entity = self.entity(row, col)
@@ -44,12 +43,11 @@
class DownloadView(baseviews.EntityView):
- """this view is replacing the deprecated 'download' controller and allow downloading
- of entities providing the necessary interface
+ """this view is replacing the deprecated 'download' controller and allow
+ downloading of entities providing the necessary interface
"""
id = 'download'
- __selectors__ = (one_line_rset, implement_interface)
- accepts_interfaces = (IDownloadable,)
+ __selectors__ = (one_line_rset, implements(IDownloadable))
templatable = False
content_type = 'application/octet-stream'
@@ -76,10 +74,9 @@
class DownloadLinkView(baseviews.EntityView):
"""view displaying a link to download the file"""
id = 'downloadlink'
+ __selectors__ = (implements(IDownloadable),)
title = None # should not be listed in possible views
- __selectors__ = (implement_interface,)
- accepts_interfaces = (IDownloadable,)
def cell_call(self, row, col, title=None, **kwargs):
entity = self.entity(row, col)
@@ -89,9 +86,8 @@
class IDownloadablePrimaryView(baseviews.PrimaryView):
- __selectors__ = (implement_interface,)
+ __selectors__ = (implements(IDownloadable),)
#skip_attrs = ('eid', 'data',) # XXX
- accepts_interfaces = (IDownloadable,)
def render_entity_title(self, entity):
self.w(u'<h1>%s %s</h1>'
@@ -122,10 +118,7 @@
class IDownloadableLineView(baseviews.OneLineView):
- __selectors__ = (implement_interface,)
- # don't kick default oneline view
- accepts_interfaces = (IDownloadable,)
-
+ __selectors__ = (implements(IDownloadable),)
def cell_call(self, row, col, title=None, **kwargs):
"""the secondary view is a link to download the file"""
@@ -137,11 +130,18 @@
(url, name, durl, self.req._('download')))
+def is_image(entity):
+ mt = entity.download_content_type()
+ if not (mt and mt.startswith('image/')):
+ return 0
+ return 1
+
class ImageView(baseviews.EntityView):
- __selectors__ = (implement_interface, score_entity_selector)
id = 'image'
+ __selectors__ = (implements(IDownloadable),
+ score_entity(is_image))
+
title = _('image')
- accepts_interfaces = (IDownloadable,)
def call(self):
rset = self.rset
@@ -149,13 +149,6 @@
self.w(u'<div class="efile">')
self.wview(self.id, rset, row=i, col=0)
self.w(u'</div>')
-
- @classmethod
- def score_entity(cls, entity):
- mt = entity.download_content_type()
- if not (mt and mt.startswith('image/')):
- return 0
- return 1
def cell_call(self, row, col):
entity = self.entity(row, col)
--- a/web/views/management.py Mon Feb 16 16:24:24 2009 +0100
+++ b/web/views/management.py Mon Feb 16 18:26:13 2009 +0100
@@ -14,7 +14,7 @@
from cubicweb.common.utils import UStringIO
from cubicweb.common.view import AnyRsetView, StartupView, EntityView
from cubicweb.common.uilib import html_traceback, rest_traceback
-from cubicweb.common.selectors import (yes, one_line_rset,
+from cubicweb.common.selectors import (yes, one_line_rset, match_user_groups,
accept_rset, none_rset,
chainfirst, chainall)
from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
@@ -293,7 +293,7 @@
def css_class(someclass):
return someclass and 'class="%s"' % someclass or ''
-class SystemEpropertiesForm(FormMixIn, StartupView):
+class SystemEPropertiesForm(FormMixIn, StartupView):
controller = 'edit'
id = 'systemepropertiesform'
title = _('site configuration')
@@ -461,24 +461,23 @@
w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('edits-pkey', eid), ''))
-class EpropertiesForm(SystemEpropertiesForm):
+
+def is_user_prefs(cls, req, rset, row, col):
+ return req.user.eid == rset[row or 0 ][col or 0]
+
+class EPropertiesForm(SystemEPropertiesForm):
id = 'epropertiesform'
- title = _('preferences')
- require_groups = ('users', 'managers') # we don't want guests to be able to come here
- __selectors__ = chainfirst(none_rset,
- chainall(one_line_rset, accept_rset)),
+ __selectors__ = (
+ # we don't want guests to be able to come here
+ match_user_groups('users', 'managers'),
+ chainfirst(none_rset),
+ chainall(one_line_rset, is_user_prefs),
+ chainall(one_line_rset, match_user_groups('managers'))
+ )
+
accepts = ('EUser',)
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- if row is None:
- row = 0
- score = super(EpropertiesForm, cls).accept_rset(req, rset, row, col)
- # check current user is the rset user or he is in the managers group
- if score and (req.user.eid == rset[row][col or 0]
- or req.user.matching_groups('managers')):
- return score
- return 0
+ title = _('preferences')
@property
def user(self):
@@ -493,7 +492,7 @@
'P for_user U, U eid %(x)s', {'x': self.user.eid})
def form_row_hiddens(self, w, entity, key):
- super(EpropertiesForm, self).form_row_hiddens(w, entity, key)
+ super(EPropertiesForm, self).form_row_hiddens(w, entity, key)
# if user is in the managers group and the property is being created,
# we have to set for_user explicitly
if not entity.has_eid() and self.user.matching_groups('managers'):