# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1234805173 -3600 # Node ID 99f5852f8604a59ce5d7c5b7b18a2e7321cb75b1 # Parent 66ff0b2f7d03a8f3c8e644e4eca8bc9538750c6e major selector refactoring (mostly to avoid looking for select parameters on the target class), start accept / interface unification) diff -r 66ff0b2f7d03 -r 99f5852f8604 common/appobject.py --- 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 diff -r 66ff0b2f7d03 -r 99f5852f8604 common/entity.py --- 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 diff -r 66ff0b2f7d03 -r 99f5852f8604 common/registerers.py --- 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() diff -r 66ff0b2f7d03 -r 99f5852f8604 common/selectors.py --- 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) diff -r 66ff0b2f7d03 -r 99f5852f8604 cwvreg.py --- 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 diff -r 66ff0b2f7d03 -r 99f5852f8604 test/unittest_rset.py --- 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 diff -r 66ff0b2f7d03 -r 99f5852f8604 test/unittest_vregistry.py --- 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) diff -r 66ff0b2f7d03 -r 99f5852f8604 vregistry.py --- 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 ``|`` diff -r 66ff0b2f7d03 -r 99f5852f8604 web/action.py --- 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, diff -r 66ff0b2f7d03 -r 99f5852f8604 web/views/__init__.py --- 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) diff -r 66ff0b2f7d03 -r 99f5852f8604 web/views/actions.py --- 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') - diff -r 66ff0b2f7d03 -r 99f5852f8604 web/views/baseviews.py --- 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'%s [...]' % ( html_escape(linksearch_select_url(self.req, erset)), self.req._('select this entity'), diff -r 66ff0b2f7d03 -r 99f5852f8604 web/views/bookmark.py --- 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""" diff -r 66ff0b2f7d03 -r 99f5852f8604 web/views/embedding.py --- 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 ' ''' % (entity.absolute_url(), entity.absolute_url())) - self.w(u'\n' % entity.eid) self.w(u'%s\n' % html_escape(entity.dc_long_title())) if entity.surname: @@ -68,11 +86,13 @@ self.w(u'%s\n' % html_escape(emailaddr)) self.w(u'\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""" diff -r 66ff0b2f7d03 -r 99f5852f8604 web/views/idownloadable.py --- 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'

%s %s

' @@ -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'
') self.wview(self.id, rset, row=i, col=0) self.w(u'
') - - @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) diff -r 66ff0b2f7d03 -r 99f5852f8604 web/views/management.py --- 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'' % (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'):