# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1234813901 -3600 # Node ID 5e5b296a657ee4da85002d7fbe2cb146c8ba9178 # Parent 8e64f12be69c9b7faada1e2aeaad48c24874ca2f# Parent 616191014b8b2998f3d73afe4946c0ca21319c92 merge diff -r 616191014b8b -r 5e5b296a657e common/appobject.py --- a/common/appobject.py Mon Feb 16 20:42:30 2009 +0100 +++ b/common/appobject.py Mon Feb 16 20:51:41 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 616191014b8b -r 5e5b296a657e common/entity.py --- a/common/entity.py Mon Feb 16 20:42:30 2009 +0100 +++ b/common/entity.py Mon Feb 16 20:51:41 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 616191014b8b -r 5e5b296a657e common/registerers.py --- a/common/registerers.py Mon Feb 16 20:42:30 2009 +0100 +++ b/common/registerers.py Mon Feb 16 20:51:41 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 616191014b8b -r 5e5b296a657e common/selectors.py --- a/common/selectors.py Mon Feb 16 20:42:30 2009 +0100 +++ b/common/selectors.py Mon Feb 16 20:51:41 2009 +0100 @@ -1,571 +1,4 @@ -"""This file contains some basic selectors required by application objects. - -A selector is responsible to score how well an object may be used with a -given result set (publishing time selection) - -If you have trouble with selectors, especially if the objet (typically -a view or a component) you want to use is not selected and you want to -know which one(s) of its selectors fail (e.g. returns 0), you can use -`traced_selection` or even direclty `TRACED_OIDS`. - -`TRACED_OIDS` is a tuple of traced object ids. The special value -'all' may be used to log selectors for all objects. - -For instance, say that the following code yields a `NoSelectableObject` -exception:: - - self.view('calendar', myrset) - -You can log the selectors involved for *calendar* by replacing the line -above by:: - - # in Python2.5 - from cubicweb.common.selectors import traced_selection - with traced_selection(): - self.view('calendar', myrset) - - # in Python2.4 - from cubicweb.common import selectors - selectors.TRACED_OIDS = ('calendar',) - self.view('calendar', myrset) - selectors.TRACED_OIDS = () - - - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" - -__docformat__ = "restructuredtext en" - -import logging - -from logilab.common.compat import all -from logilab.common.deprecation import deprecated_function - -from cubicweb import Unauthorized, NoSelectableObject, role -from cubicweb.cwvreg import DummyCursorError -from cubicweb.vregistry import chainall, chainfirst, NoSelectableObject -from cubicweb.cwconfig import CubicWebConfiguration -from cubicweb.schema import split_expression - -# helpers for debugging selectors -SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors') -TRACED_OIDS = () - -def lltrace(selector): - # don't wrap selectors if not in development mode - if CubicWebConfiguration.mode == 'installed': - return selector - def traced(cls, *args, **kwargs): - 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) - return ret - traced.__name__ = selector.__name__ - return traced - -class traced_selection(object): - """selector debugging helper. - - Typical usage is : - - >>> with traced_selection(): - ... # some code in which you want to debug selectors - ... # for all objects - - or - - >>> with traced_selection( ('oid1', 'oid2') ): - ... # some code in which you want to debug selectors - ... # for objects with id 'oid1' and 'oid2' - - """ - def __init__(self, traced='all'): - self.traced = traced - - def __enter__(self): - global TRACED_OIDS - TRACED_OIDS = self.traced - - def __exit__(self, exctype, exc, traceback): - global TRACED_OIDS - TRACED_OIDS = () - return traceback is None - -# very basic selectors ######################################################## - -def yes(cls, *args, **kwargs): - """accept everything""" - return 1 -yes_selector = deprecated_function(yes) - -@lltrace -def none_rset(cls, req, rset, *args, **kwargs): - """accept no result set""" - if rset is None: - return 1 - return 0 -norset_selector = deprecated_function(none_rset) - -@lltrace -def any_rset(cls, req, rset, *args, **kwargs): - """accept result set, whatever the number of result""" - if rset is not None: - return 1 - return 0 -rset_selector = deprecated_function(any_rset) - -@lltrace -def nonempty_rset(cls, req, rset, *args, **kwargs): - """accept any non empty result set""" - if rset is not None and rset.rowcount: - return 1 - return 0 -anyrset_selector = deprecated_function(nonempty_rset) - -@lltrace -def empty_rset(cls, req, rset, *args, **kwargs): - """accept empty result set""" - if rset is not None and rset.rowcount == 0: - return 1 - return 0 -emptyrset_selector = deprecated_function(empty_rset) - -@lltrace -def one_line_rset(cls, req, rset, row=None, *args, **kwargs): - """accept result set with a single line of result""" - if rset is not None and (row is not None or rset.rowcount == 1): - return 1 - return 0 -onelinerset_selector = deprecated_function(one_line_rset) - -@lltrace -def two_lines_rset(cls, req, rset, *args, **kwargs): - """accept result set with *at least* two lines of result""" - if rset is not None and rset.rowcount > 1: - return 1 - return 0 -twolinerset_selector = deprecated_function(two_lines_rset) - -@lltrace -def two_cols_rset(cls, req, rset, *args, **kwargs): - """accept result set with at least one line and two columns of result""" - if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1: - return 1 - return 0 -twocolrset_selector = deprecated_function(two_cols_rset) - -@lltrace -def paginated_rset(cls, req, rset, *args, **kwargs): - """accept result sets with more rows than the page size - """ - page_size = kwargs.get('page_size') - if page_size is None: - page_size = req.form.get('page_size') - if page_size is None: - page_size = req.property_value('navigation.page-size') - else: - page_size = int(page_size) - if rset is None or len(rset) <= page_size: - return 0 - return 1 -largerset_selector = deprecated_function(paginated_rset) - -@lltrace -def sorted_rset(cls, req, rset, row=None, col=None, **kwargs): - """accept sorted result set""" - rqlst = rset.syntax_tree() - if len(rqlst.children) > 1 or not rqlst.children[0].orderby: - return 0 - return 2 -sortedrset_selector = deprecated_function(sorted_rset) - -@lltrace -def one_etype_rset(cls, req, rset, *args, **kwargs): - """accept result set where entities in the first columns are all of the - same type - """ - if len(rset.column_types(0)) != 1: - return 0 - return 1 -oneetyperset_selector = deprecated_function(one_etype_rset) - -@lltrace -def two_etypes_rset(cls, req, rset, **kwargs): - """accepts resultsets containing several entity types""" - if rset: - etypes = rset.column_types(0) - if len(etypes) > 1: - return 1 - 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 - - 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) - -@lltrace -def anonymous_user(cls, req, *args, **kwargs): - """accept if user is anonymous""" - if req.cnx.anonymous_connection: - return 1 - return 0 -anonymous_selector = deprecated_function(anonymous_user) - -@lltrace -def authenticated_user(cls, req, *args, **kwargs): - """accept if user is authenticated""" - return not anonymous_user(cls, req, *args, **kwargs) -not_anonymous_selector = deprecated_function(authenticated_user) - -@lltrace -def match_form_params(cls, req, *args, **kwargs): - """check if parameters specified by the form_params attribute on - the wrapped class are specified in request form parameters - """ - score = 0 - for param in cls.form_params: - val = req.form.get(param) - if not val: - return 0 - score += 1 - return score + 1 -req_form_params_selector = deprecated_function(match_form_params) - -@lltrace -def match_kwargs(cls, req, *args, **kwargs): - """check if arguments specified by the expected_kwargs attribute on - the wrapped class are specified in given named parameters - """ - values = [] - for arg in cls.expected_kwargs: - if not arg in kwargs: - return 0 - return 1 -kwargs_selector = deprecated_function(match_kwargs) - - -# 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 -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): - """accept single entity result set if the entity match an rql condition - """ - if cls.condition: - eid = rset[row or 0][col or 0] - if 'U' in frozenset(split_expression(cls.condition)): - rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % cls.condition - else: - rql = 'Any X WHERE X eid %%(x)s, %s' % cls.condition - try: - return len(req.execute(rql, {'x': eid, 'u': req.user.eid}, 'x')) - except Unauthorized: - return 0 - - 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): - """restrict the searchstate_accept_one_selector to exclude entity's type - refered by the .etype attribute - """ - if rset.description[row or 0][col or 0] == cls.etype: - return 0 - return 1 -but_etype_selector = deprecated_function(but_etype) - -@lltrace -def etype_rtype_selector(cls, req, rset, row=None, col=None, **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. - """ - schema = cls.schema - perm = getattr(cls, 'require_permission', 'read') - if hasattr(cls, 'etype'): - eschema = schema.eschema(cls.etype) - if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)): - return 0 - if hasattr(cls, 'rtype'): - rschema = schema.rschema(cls.rtype) - if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)): - return 0 - return 1 - -@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) - -@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): - """only check if the user has add access on the entity's type refered - by the .etype attribute. - """ - if not cls.schema.eschema(cls.etype).has_perm(req, 'add'): - return 0 - return 1 -add_etype_selector = deprecated_function(user_can_add_etype) - -@lltrace -def match_context_prop(cls, req, rset, row=None, col=None, context=None, - **kwargs): - propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id)) - if not propval: - propval = cls.context - if context is not None and propval and context != propval: - return 0 - return 1 -contextprop_selector = deprecated_function(match_context_prop) - -@lltrace -def primary_view(cls, req, rset, row=None, col=None, view=None, - **kwargs): - if view is not None and not view.is_primary(): - return 0 - return 1 -primaryview_selector = deprecated_function(primary_view) - -def appobject_selectable(registry, oid): - """return a selector that will have a positive score if an object for the - given registry and object id is selectable for the input context - """ - @lltrace - def selector(cls, req, rset, *args, **kwargs): - try: - cls.vreg.select_object(registry, oid, req, rset, *args, **kwargs) - return 1 - except NoSelectableObject: - return 0 - return 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_selector = deprecated_function(accept_one) - -rql_condition = chainall(non_final_entity, one_line_rset, _rql_condition) -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_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' -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_selector = deprecated_function( - searchstate_accept_one_but_etype) +from warnings import warn +warn('moved to cubicweb.selectors', DeprecationWarning, stacklevel=2) +from cubicweb.selectors import * +from cubicweb.selectors import _rql_condition diff -r 616191014b8b -r 5e5b296a657e common/view.py --- a/common/view.py Mon Feb 16 20:42:30 2009 +0100 +++ b/common/view.py Mon Feb 16 20:51:41 2009 +0100 @@ -12,14 +12,16 @@ from logilab.mtconverter import html_escape from cubicweb import NotAnEntity, NoSelectableObject +from cubicweb.selectors import (yes, match_user_groups, implements, + nonempty_rset, none_rset) +from cubicweb.selectors import require_group_compat, accepts_compat from cubicweb.common.registerers import accepts_registerer, priority_registerer -from cubicweb.common.selectors import (chainfirst, match_user_group, accept, - nonempty_rset, empty_rset, none_rset) from cubicweb.common.appobject import AppRsetObject, ComponentMixIn from cubicweb.common.utils import UStringIO, HTMLStream _ = unicode + # robots control NOINDEX = u'' NOFOLLOW = u'' @@ -86,6 +88,7 @@ attributes are added and the `w` attribute will be set at rendering time to a write function to use. """ + __registerer__ = priority_registerer __registry__ = 'views' templatable = True @@ -293,17 +296,7 @@ def create_url(self, etype, **kwargs): """ return the url of the entity creation form for a given entity type""" return self.req.build_url('add/%s'%etype, **kwargs) - - -# concrete views base classes ################################################# - -class EntityView(View): - """base class for views applying on an entity (i.e. uniform result set) - """ - __registerer__ = accepts_registerer - __selectors__ = (accept,) - category = 'entityview' - + def field(self, label, value, row=True, show_label=True, w=None, tr=True): """ read-only field """ if w is None: @@ -319,15 +312,29 @@ w(u'') +# concrete views base classes ################################################# + +class EntityView(View): + """base class for views applying on an entity (i.e. uniform result set) + """ + # XXX deprecate + __registerer__ = accepts_registerer + __selectors__ = (implements('Any'),) + registered = accepts_compat(View.registered.im_func) + + category = 'entityview' + + class StartupView(View): """base class for views which doesn't need a particular result set to be displayed (so they can always be displayed !) """ __registerer__ = priority_registerer - __selectors__ = (match_user_group, none_rset) - require_groups = () + __selectors__ = (none_rset,) + registered = require_group_compat(View.registered.im_func) + category = 'startupview' - + def url(self): """return the url associated with this view. We can omit rql here""" return self.build_url('view', vid=self.id) @@ -346,7 +353,7 @@ result set (usually a default rql is provided by the view class) """ __registerer__ = accepts_registerer - __selectors__ = (chainfirst(none_rset, accept),) + __selectors__ = ((none_rset | implements('Any')),) default_rql = None @@ -403,13 +410,7 @@ labels.append(label) return labels - -class EmptyRsetView(View): - """base class for views applying on any empty result sets""" - __registerer__ = priority_registerer - __selectors__ = (empty_rset,) - - + # concrete template base classes ############################################## class Template(View): @@ -418,9 +419,9 @@ """ __registry__ = 'templates' __registerer__ = priority_registerer - __selectors__ = (match_user_group,) + __selectors__ = (yes,) - require_groups = () + registered = require_group_compat(View.registered.im_func) def template(self, oid, **kwargs): """shortcut to self.registry.render method on the templates registry""" @@ -433,7 +434,6 @@ There is usually at least a regular main template and a simple fallback one to display error if the first one failed """ - base_doctype = STRICT_DOCTYPE @property diff -r 616191014b8b -r 5e5b296a657e cwvreg.py --- a/cwvreg.py Mon Feb 16 20:42:30 2009 +0100 +++ b/cwvreg.py Mon Feb 16 20:51:41 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 616191014b8b -r 5e5b296a657e goa/appobjects/dbmgmt.py --- a/goa/appobjects/dbmgmt.py Mon Feb 16 20:42:30 2009 +0100 +++ b/goa/appobjects/dbmgmt.py Mon Feb 16 20:51:41 2009 +0100 @@ -2,7 +2,7 @@ restoration). :organization: Logilab -:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" @@ -13,6 +13,7 @@ from logilab.common.decorators import cached from logilab.mtconverter import html_escape +from cubicweb.common.selectors import none_rset, match_user_groups from cubicweb.common.view import StartupView from cubicweb.web import Redirect from cubicweb.goa.dbinit import fix_entities, init_persistent_schema, insert_versions @@ -39,7 +40,7 @@ which are doing datastore administration requests """ id = 'authinfo' - require_groups = ('managers',) + __selectors__ = (none_rset, match_user_groups('managers'),) def call(self): cookie = self.req.get_cookie() @@ -61,7 +62,7 @@ step by step to avoid depassing quotas """ id = 'contentinit' - require_groups = ('managers',) + __selectors__ = (none_rset, match_user_groups('managers'),) def server_session(self): ssession = self.config.repo_session(self.req.cnx.sessionid) @@ -166,7 +167,7 @@ class ContentClear(StartupView): id = 'contentclear' - require_groups = ('managers',) + __selectors__ = (none_rset, match_user_groups('managers'),) skip_etypes = ('EGroup', 'EUser') def call(self): diff -r 616191014b8b -r 5e5b296a657e goa/appobjects/sessions.py --- a/goa/appobjects/sessions.py Mon Feb 16 20:42:30 2009 +0100 +++ b/goa/appobjects/sessions.py Mon Feb 16 20:51:41 2009 +0100 @@ -1,7 +1,7 @@ """persistent sessions stored in big table :organization: Logilab -:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr XXX TODO: @@ -18,6 +18,7 @@ from cubicweb import UnknownEid, BadConnectionId from cubicweb.dbapi import Connection, ConnectionProperties, repo_connect from cubicweb.server.session import Session +from cubicweb.common.selectors import none_rset, match_user_groups from cubicweb.web import InvalidSession from cubicweb.web.application import AbstractSessionManager from cubicweb.web.application import AbstractAuthenticationManager @@ -254,7 +255,7 @@ class SessionsCleaner(StartupView): id = 'cleansessions' - require_groups = ('managers',) + __selectors__ = (none_rset, match_user_groups('managers'),) def call(self): # clean web session diff -r 616191014b8b -r 5e5b296a657e selectors.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/selectors.py Mon Feb 16 20:51:41 2009 +0100 @@ -0,0 +1,823 @@ +"""This file contains some basic selectors required by application objects. + +A selector is responsible to score how well an object may be used with a +given result set (publishing time selection) + +If you have trouble with selectors, especially if the objet (typically +a view or a component) you want to use is not selected and you want to +know which one(s) of its selectors fail (e.g. returns 0), you can use +`traced_selection` or even direclty `TRACED_OIDS`. + +`TRACED_OIDS` is a tuple of traced object ids. The special value +'all' may be used to log selectors for all objects. + +For instance, say that the following code yields a `NoSelectableObject` +exception:: + + self.view('calendar', myrset) + +You can log the selectors involved for *calendar* by replacing the line +above by:: + + # in Python2.5 + from cubicweb.common.selectors import traced_selection + with traced_selection(): + self.view('calendar', myrset) + + # in Python2.4 + from cubicweb.common import selectors + selectors.TRACED_OIDS = ('calendar',) + self.view('calendar', myrset) + selectors.TRACED_OIDS = () + + + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +__docformat__ = "restructuredtext en" + +import logging +from warnings import warn + +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.cwconfig import CubicWebConfiguration +from cubicweb.schema import split_expression + +# helpers for debugging selectors +SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors') +TRACED_OIDS = () + +def lltrace(selector): + # don't wrap selectors if not in development mode + 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 oid in TRACED_OIDS: + #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls) + print 'selector %s returned %s for %s' % (selname, ret, cls) + return ret + traced.__name__ = selector.__name__ + return traced + +class traced_selection(object): + """selector debugging helper. + + Typical usage is : + + >>> with traced_selection(): + ... # some code in which you want to debug selectors + ... # for all objects + + or + + >>> with traced_selection( ('oid1', 'oid2') ): + ... # some code in which you want to debug selectors + ... # for objects with id 'oid1' and 'oid2' + + """ + def __init__(self, traced='all'): + self.traced = traced + + def __enter__(self): + global TRACED_OIDS + TRACED_OIDS = self.traced + + def __exit__(self, exctype, exc, traceback): + global TRACED_OIDS + TRACED_OIDS = () + return traceback is None + +# very basic selectors ######################################################## + +def yes(cls, *args, **kwargs): + """accept everything""" + return 1 +yes_selector = deprecated_function(yes) + +@lltrace +def none_rset(cls, req, rset, *args, **kwargs): + """accept no result set""" + if rset is None: + return 1 + return 0 +norset_selector = deprecated_function(none_rset) + +@lltrace +def any_rset(cls, req, rset, *args, **kwargs): + """accept result set, whatever the number of result""" + if rset is not None: + return 1 + return 0 +rset_selector = deprecated_function(any_rset) + +@lltrace +def nonempty_rset(cls, req, rset, *args, **kwargs): + """accept any non empty result set""" + if rset is not None and rset.rowcount: + return 1 + return 0 +anyrset_selector = deprecated_function(nonempty_rset) + +@lltrace +def empty_rset(cls, req, rset, *args, **kwargs): + """accept empty result set""" + if rset is not None and rset.rowcount == 0: + return 1 + return 0 +emptyrset_selector = deprecated_function(empty_rset) + +@lltrace +def one_line_rset(cls, req, rset, row=None, *args, **kwargs): + """accept result set with a single line of result""" + if rset is not None and (row is not None or rset.rowcount == 1): + return 1 + return 0 +onelinerset_selector = deprecated_function(one_line_rset) + +@lltrace +def two_lines_rset(cls, req, rset, *args, **kwargs): + """accept result set with *at least* two lines of result""" + if rset is not None and rset.rowcount > 1: + return 1 + return 0 +twolinerset_selector = deprecated_function(two_lines_rset) + +@lltrace +def two_cols_rset(cls, req, rset, *args, **kwargs): + """accept result set with at least one line and two columns of result""" + if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1: + return 1 + return 0 +twocolrset_selector = deprecated_function(two_cols_rset) + +@lltrace +def paginated_rset(cls, req, rset, *args, **kwargs): + """accept result sets with more rows than the page size + """ + page_size = kwargs.get('page_size') + if page_size is None: + page_size = req.form.get('page_size') + if page_size is None: + page_size = req.property_value('navigation.page-size') + else: + page_size = int(page_size) + if rset is None or len(rset) <= page_size: + return 0 + return 1 +largerset_selector = deprecated_function(paginated_rset) + +@lltrace +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: + return 0 + return 2 +sortedrset_selector = deprecated_function(sorted_rset) + +@lltrace +def one_etype_rset(cls, req, rset, *args, **kwargs): + """accept result set where entities in the first columns are all of the + same type + """ + if len(rset.column_types(0)) != 1: + return 0 + return 1 +oneetyperset_selector = deprecated_function(one_etype_rset) + +@lltrace +def two_etypes_rset(cls, req, rset, **kwargs): + """accepts resultsets containing several entity types""" + if rset: + etypes = rset.column_types(0) + if len(etypes) > 1: + return 1 + return 0 +multitype_selector = deprecated_function(two_etypes_rset) + + +class match_search_state(Selector): + def __init__(self, *expected): + self.expected = expected + + @lltrace + 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 self.expected: + return 0 + except AttributeError: + return 1 # class doesn't care about search state, accept it + return 1 + + +class match_form_params(match_search_state): + """check if parameters specified as initializer arguments are specified + in request form parameters + """ + @lltrace + def __call__(self, cls, req, *args, **kwargs): + score = 0 + for param in self.expected: + val = req.form.get(param) + if not val: + return 0 + score += 1 + return len(self.expected) + + +class match_kwargs(match_search_state): + """check if parameters specified as initializer arguments are specified + in named parameters + """ + @lltrace + def __call__(self, cls, req, *args, **kwargs): + for arg in self.expected: + if not arg in kwargs: + return 0 + return len(self.expected) + + +@lltrace +def anonymous_user(cls, req, *args, **kwargs): + """accept if user is anonymous""" + if req.cnx.anonymous_connection: + return 1 + return 0 +anonymous_selector = deprecated_function(anonymous_user) + +@lltrace +def authenticated_user(cls, req, *args, **kwargs): + """accept if user is authenticated""" + return not anonymous_user(cls, req, *args, **kwargs) +not_anonymous_selector = deprecated_function(authenticated_user) + +# 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(req, rset, 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 + score = self.score(req, rset, row, col) + return score and (score + 1) + + def score(self, req, rset, row, col): + try: + return self.score_entity(rset.get_entity(row, col)) + except NotAnEntity: + return 0 + + 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': + score += 1 + # adjust score if the interface is an entity class + if iface is eclass: + score += len(eclass.e_schema.ancestors()) + print 'is majoration', len(eclass.e_schema.ancestors()) + 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 + print 'etype majoration', index + break + return score + + +class specified_etype_implements(implements): + """return the "interface score" for class associated to 'etype' (expected in + request form or arguments) + """ + + @lltrace + def __call__(cls, req, *args, **kwargs): + try: + etype = req.form['etype'] + except KeyError: + try: + etype = kwargs['etype'] + except KeyError: + return 0 + return self.score_class(cls.vreg.etype_class(etype), req) + + +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 takes 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): + """return 1 if the user may add some entity of the types found in the + result set (0 else) + """ + 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): + """initializer takes a function as argument (which is expected to take an + entity as argument) + + return the score returned by the function on the entity at the given row/col + (if row specified) or the sum of the score for every entities in the given + col (if row is not specified). Return 0 at the first entity scoring to zero. + """ + def __init__(self, scorefunc): + self.score_entity = scorefunc + + +class rql_condition(EntitySelector): + def __init__(self, expression): + if 'U' in frozenset(split_expression(cls.condition)): + rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression + else: + rql = 'Any X WHERE X eid %%(x)s, %s' % expression + self.rql = rql + + def score(self, req, rset, row, col): + try: + return len(req.execute(self.rql, {'x': eid, 'u': req.user.eid}, 'x')) + except Unauthorized: + return 0 + + +# XXX not so basic selectors ###################################################### + +@lltrace +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 + """ + if rset.description[row or 0][col or 0] == cls.etype: + return 0 + return 1 +but_etype_selector = deprecated_function(but_etype) + +@lltrace +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. + """ + schema = cls.schema + perm = getattr(cls, 'require_permission', 'read') + if hasattr(cls, 'etype'): + eschema = schema.eschema(cls.etype) + if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)): + return 0 + if hasattr(cls, 'rtype'): + rschema = schema.rschema(cls.rtype) + if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)): + return 0 + return 1 + +@lltrace +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 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. + """ + if not cls.schema.eschema(cls.etype).has_perm(req, 'add'): + return 0 + return 1 +add_etype_selector = deprecated_function(user_can_add_etype) + +@lltrace +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: + propval = cls.context + if context is not None and propval and context != propval: + return 0 + return 1 +contextprop_selector = deprecated_function(match_context_prop) + +@lltrace +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 + return 1 +primaryview_selector = deprecated_function(primary_view) + +def appobject_selectable(registry, oid): + """return a selector that will have a positive score if an object for the + given registry and object id is selectable for the input context + """ + @lltrace + def selector(cls, req, rset, *args, **kwargs): + try: + cls.vreg.select_object(registry, oid, req, rset, *args, **kwargs) + return 1 + except NoSelectableObject: + return 0 + 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, 'use implements') + +def accept_etype(cls, req, *args, **kwargs): + """check etype presence in request form *and* accepts conformance""" + return specified_etype_implements(*cls.accepts)(cls, req, *args) +etype_form_selector = deprecated_function(accept_etype) +accept_etype = deprecated_function(accept_etype, 'use specified_etype_implements') + +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') + +accept_one = deprecated_function(chainall(one_line_rset, accept, + name='accept_one')) +accept_one_selector = deprecated_function(accept_one) + + +@lltrace +def _rql_condition(cls, req, rset, row=None, col=0, **kwargs): + if cls.condition: + return rql_condition(cls.condition)(cls, req, rset, row, col) + return 1 +_rqlcondition_selector = deprecated_function(_rql_condition) + +rqlcondition_selector = deprecated_function(chainall(non_final_entity(), one_line_rset, _rql_condition, + name='rql_condition')) + +#req_form_params_selector = deprecated_function(match_form_params) # form_params +#kwargs_selector = deprecated_function(match_kwargs) # expected_kwargs + +# compound selectors ########################################################## + +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, + name='searchstate_accept_one') +searchstate_accept_one_selector = deprecated_function(searchstate_accept_one) + +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) + + +def require_group_compat(registered): + def plug_selector(cls, vreg): + cls = registered(cls, vreg) + if getattr(cls, 'require_groups', None): + warn('use "use match_user_groups(group1, group2)" instead of using require_groups', + DeprecationWarning) + cls.__selectors__ += (match_user_groups(cls.require_groups),) + return cls + return classmethod(plug_selector) + +def accepts_compat(registered): + def plug_selector(cls, vreg): + cls = registered(cls, vreg) + if getattr(cls, 'accepts', None): + warn('use "use match_user_groups(group1, group2)" instead of using require_groups', + DeprecationWarning) + cls.__selectors__ += (implements(*cls.accepts),) + return cls + return classmethod(plug_selector) + +def condition_compat(registered): + def plug_selector(cls, vreg): + cls = registered(cls, vreg) + if getattr(cls, 'condition', None): + warn('use "use rql_condition(expression)" instead of using condition', + DeprecationWarning) + cls.__selectors__ += (rql_condition(cls.condition),) + return cls + return classmethod(plug_selector) diff -r 616191014b8b -r 5e5b296a657e sobjects/notification.py --- a/sobjects/notification.py Mon Feb 16 20:42:30 2009 +0100 +++ b/sobjects/notification.py Mon Feb 16 20:51:41 2009 +0100 @@ -1,7 +1,7 @@ """some hooks and views to handle notification on entity's changes :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" @@ -21,7 +21,7 @@ from cubicweb.common.view import EntityView from cubicweb.common.appobject import Component from cubicweb.common.registerers import accepts_registerer -from cubicweb.common.selectors import accept +from cubicweb.common.selectors import implements from cubicweb.common.mail import format_mail from cubicweb.server.pool import PreCommitOperation @@ -38,8 +38,7 @@ """ id = 'recipients_finder' __registerer__ = accepts_registerer - __selectors__ = (accept,) - accepts = ('Any',) + __selectors__ = (implements('Any'),) user_rql = ('Any X,E,A WHERE X is EUser, X in_state S, S name "activated",' 'X primary_email E, E address A') @@ -299,7 +298,7 @@ class CardAddedView(NormalizedTextView): """get notified from new cards""" - accepts = ('Card',) + __selectors__ = (implements('Card'),) content_attr = 'synopsis' diff -r 616191014b8b -r 5e5b296a657e test/unittest_rset.py --- a/test/unittest_rset.py Mon Feb 16 20:42:30 2009 +0100 +++ b/test/unittest_rset.py Mon Feb 16 20:51:41 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 @@ -224,7 +227,8 @@ self.assertEquals(e.col, 0) self.assertEquals(e['title'], 'zou') self.assertRaises(KeyError, e.__getitem__, 'path') - self.assertEquals(e.view('text'), 'zou') + with traced_selection(): + self.assertEquals(e.view('text'), 'zou') self.assertEquals(pprelcachedict(e._related_cache), []) e = rset.get_entity(0, 1) diff -r 616191014b8b -r 5e5b296a657e test/unittest_vregistry.py --- a/test/unittest_vregistry.py Mon Feb 16 20:42:30 2009 +0100 +++ b/test/unittest_vregistry.py Mon Feb 16 20:51:41 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') + if 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 616191014b8b -r 5e5b296a657e vregistry.py --- a/vregistry.py Mon Feb 16 20:42:30 2009 +0100 +++ b/vregistry.py Mon Feb 16 20:51:41 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 616191014b8b -r 5e5b296a657e web/action.py --- a/web/action.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/action.py Mon Feb 16 20:51:41 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,86 +80,30 @@ """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): +class LinkToEntityAction(Action): """base class for actions consisting to create a new object with an initial relation set to an entity. Additionaly to EntityAction behaviour, this class is parametrized 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, + relation_possible(cls.rtype, role(cls), cls.etype, + permission='add'), + may_add_relation(cls.rtype, role(cls))) + __selectors__ = (my_selector,) + registered = accepts_compat(Action.registered.im_func) + 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 +114,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 616191014b8b -r 5e5b296a657e web/box.py --- a/web/box.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/box.py Mon Feb 16 20:51:41 2009 +0100 @@ -1,7 +1,7 @@ """abstract box 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" @@ -14,9 +14,9 @@ accepts_registerer, extresources_registerer, etype_rtype_priority_registerer) from cubicweb.common.selectors import ( - etype_rtype_selector, one_line_rset, accept, has_relation, - primary_view, match_context_prop, has_related_entities, - _rql_condition) + one_line_rset, primary_view, match_context_prop, has_related_entities, + accept_compat) +#etype_rtype_selector, has_relation, from cubicweb.common.view import Template from cubicweb.common.appobject import ReloadableMixIn @@ -106,7 +106,7 @@ user's rights) and rql attributes """ __registerer__ = etype_rtype_priority_registerer - __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,) +#XXX __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,) rql = None @@ -151,11 +151,10 @@ """base class for boxes related to a single entity""" __registerer__ = accepts_registerer __selectors__ = (one_line_rset, primary_view, - match_context_prop, etype_rtype_selector, - has_relation, accept, _rql_condition) - accepts = ('Any',) + match_context_prop,) + #etype_rtype_selector, has_relation) + registered = accepts_compat(condition_compat(BoxTemplate.registered.im_func)) context = 'incontext' - condition = None def call(self, row=0, col=0, **kwargs): """classes inheriting from EntityBoxTemplate should define cell_call""" diff -r 616191014b8b -r 5e5b296a657e web/component.py --- a/web/component.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/component.py Mon Feb 16 20:51:41 2009 +0100 @@ -1,7 +1,7 @@ """abstract component class and base components definition 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" @@ -11,9 +11,9 @@ from cubicweb.common.view import VComponent, SingletonVComponent from cubicweb.common.registerers import action_registerer from cubicweb.common.selectors import (paginated_rset, one_line_rset, - rql_condition, accept, primary_view, - match_context_prop, has_relation, - etype_rtype_selector) + primary_view, match_context_prop, + condition_compat) +#rql_condition, accept, has_relation, etype_rtype_selector from cubicweb.common.uilib import html_escape _ = unicode @@ -33,9 +33,12 @@ __registry__ = 'contentnavigation' __registerer__ = action_registerer __selectors__ = (one_line_rset, primary_view, - match_context_prop, etype_rtype_selector, - has_relation, accept, - rql_condition) + match_context_prop, +# etype_rtype_selector, +# has_relation, accept, +# rql_condition) + ) + registered = accepts_compat(condition_compat(VComponent.registered.im_func)) property_defs = { _('visible'): dict(type='Boolean', default=True, @@ -51,9 +54,7 @@ help=_('html class of the component')), } - accepts = ('Any',) context = 'navcontentbottom' # 'footer' | 'header' | 'incontext' - condition = None def call(self, view): return self.cell_call(0, 0, view) @@ -152,8 +153,10 @@ class RelatedObjectsVComponent(EntityVComponent): """a section to display some related entities""" __selectors__ = (one_line_rset, primary_view, - etype_rtype_selector, has_relation, - match_context_prop, accept) + match_context_prop, +# etype_rtype_selector, has_relation, +# accept) + ) vid = 'list' def rql(self): diff -r 616191014b8b -r 5e5b296a657e web/controller.py --- a/web/controller.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/controller.py Mon Feb 16 20:51:41 2009 +0100 @@ -2,7 +2,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" @@ -11,7 +11,7 @@ from cubicweb import typed_eid from cubicweb.common.registerers import priority_registerer -from cubicweb.common.selectors import match_user_group +from cubicweb.common.selectors import yes, require_group_compat from cubicweb.common.appobject import AppObject from cubicweb.web import LOGGER, Redirect, RequestError @@ -68,8 +68,8 @@ """ __registry__ = 'controllers' __registerer__ = priority_registerer - __selectors__ = (match_user_group,) - require_groups = () + __selectors__ = (yes,) + registered = require_group_compat(AppObject.registered.im_func) def __init__(self, *args, **kwargs): super(Controller, self).__init__(*args, **kwargs) diff -r 616191014b8b -r 5e5b296a657e web/test/unittest_viewselector.py --- a/web/test/unittest_viewselector.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/test/unittest_viewselector.py Mon Feb 16 20:51:41 2009 +0100 @@ -9,7 +9,7 @@ from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary -from cubicweb.common.selectors import match_user_group +from cubicweb.common.selectors import match_user_groups from cubicweb.web._exceptions import NoSelectableObject from cubicweb.web.action import Action @@ -411,8 +411,7 @@ class SomeAction(Action): id = 'yo' category = 'foo' - __selectors__ = (match_user_group,) - require_groups = ('owners', ) + __selectors__ = (match_user_groups('owners'),) self.vreg.register_vobject_class(SomeAction) self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions']) try: diff -r 616191014b8b -r 5e5b296a657e web/views/__init__.py --- a/web/views/__init__.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/views/__init__.py Mon Feb 16 20:51:41 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 616191014b8b -r 5e5b296a657e web/views/actions.py --- a/web/views/actions.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/views/actions.py Mon Feb 16 20:51:41 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 616191014b8b -r 5e5b296a657e web/views/basecontrollers.py --- a/web/views/basecontrollers.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/views/basecontrollers.py Mon Feb 16 20:51:41 2009 +0100 @@ -18,10 +18,10 @@ from logilab.common.decorators import cached from cubicweb import NoSelectableObject, ValidationError, typed_eid +from cubicweb.selectors import match_user_groups from cubicweb.common.selectors import yes from cubicweb.common.mail import format_mail from cubicweb.common.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS - from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed from cubicweb.web.controller import Controller from cubicweb.web.views import vid_from_rset @@ -466,7 +466,7 @@ class SendMailController(Controller): id = 'sendmail' - require_groups = ('managers', 'users') + __selectors__ = (match_user_groups('managers', 'users'),) def recipients(self): """returns an iterator on email's recipients as entities""" diff -r 616191014b8b -r 5e5b296a657e web/views/baseforms.py --- a/web/views/baseforms.py Mon Feb 16 20:42:30 2009 +0100 +++ b/web/views/baseforms.py Mon Feb 16 20:51:41 2009 +0100 @@ -17,9 +17,9 @@ from cubicweb.interfaces import IWorkflowable from cubicweb.common.utils import make_uid from cubicweb.common.uilib import cut -from cubicweb.common.selectors import (accept_etype, match_kwargs, - one_line_rset, implement_interface, - match_form_params, accept) +from cubicweb.common.selectors import (specified_etype_implements, + match_kwargs, match_form_params, + one_line_rset, implements) from cubicweb.common.view import EntityView from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param from cubicweb.web.controller import NAV_FORM_PARAMETERS @@ -90,9 +90,7 @@ id = 'statuschange' title = _('status change') - __selectors__ = (implement_interface, match_form_params) - accepts_interfaces = (IWorkflowable,) - form_params = ('treid',) + __selectors__ = (implements(IWorkflowable), match_form_params('treid')) def cell_call(self, row, col, vid='secondary'): entity = self.entity(row, col) @@ -153,8 +151,7 @@ class ClickAndEditForm(EntityForm): id = 'reledit' - __selectors__ = (match_kwargs, ) - expected_kwargs = ('rtype',) + __selectors__ = (match_kwargs('rtype'), ) #FIXME editableField class could be toggleable from userprefs @@ -219,7 +216,7 @@ dynamic default values such as the 'tomorrow' date or the user's login being connected """ - __selectors__ = (one_line_rset, accept) + __selectors__ = (one_line_rset, implements('Any')) id = 'edition' title = _('edition') @@ -526,7 +523,7 @@ class CreationForm(EditionForm): - __selectors__ = (accept_etype, ) + __selectors__ = (specified_etype_implements('Any'), ) id = 'creation' title = _('creation') @@ -639,8 +636,8 @@ class InlineEntityCreationForm(InlineFormMixIn, CreationForm): id = 'inline-creation' - __selectors__ = (match_kwargs, accept_etype) - expected_kwargs = ('ptype', 'peid', 'rtype') + __selectors__ = (match_kwargs('ptype', 'peid', 'rtype'), specified_etype_implements('Any')) + EDITION_BODY = u'''\