--- 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
--- 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
--- 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()
--- 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
--- 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'<meta name="ROBOTS" content="NOINDEX" />'
NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
@@ -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'</div>')
+# 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
--- 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
--- 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):
--- 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
--- /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)
--- 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'
--- 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)
--- 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)
--- 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 ``|``
--- 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,
--- 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"""
--- 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):
--- 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)
--- 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:
--- 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)
--- 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')
-
--- 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"""
--- 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'''\
<div id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
@@ -678,8 +675,7 @@
class InlineEntityEditionForm(InlineFormMixIn, EditionForm):
id = 'inline-edition'
- __selectors__ = (accept, match_kwargs)
- expected_kwargs = ('ptype', 'peid', 'rtype')
+ __selectors__ = (implements('Any'), match_kwargs('ptype', 'peid', 'rtype'))
EDITION_BODY = u'''\
<div onclick="restoreInlinedEntity('%(parenteid)s', '%(rtype)s', '%(eid)s')" id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
@@ -881,8 +877,7 @@
class UnrelatedDivs(EntityView):
id = 'unrelateddivs'
- __selectors__ = (match_form_params,)
- form_params = ('relation',)
+ __selectors__ = (match_form_params('relation',),)
@property
def limit(self):
@@ -993,7 +988,6 @@
THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
"""
id = 'combobox'
- accepts = ('Any',)
title = None
def cell_call(self, row, col):
--- a/web/views/baseviews.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/baseviews.py Mon Feb 16 20:51:41 2009 +0100
@@ -22,14 +22,14 @@
from logilab.mtconverter import TransformError, html_escape, xml_escape
from cubicweb import Unauthorized, NoSelectableObject, typed_eid
-from cubicweb.common.selectors import (yes, nonempty_rset, accept,
- one_line_rset, match_search_state,
+from cubicweb.common.selectors import (yes, empty_rset, nonempty_rset, one_line_rset,
+ accept, match_search_state,
match_form_params, accept_rset)
from cubicweb.common.uilib import (cut, printable_value, UnicodeCSVWriter,
ajax_replace_url, rql_for_eid, simple_sgml_tag)
-from cubicweb.common.view import EntityView, AnyRsetView, EmptyRsetView
+from cubicweb.common.view import EntityView, AnyRsetView, View
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
@@ -42,8 +42,9 @@
cell_call = call
-class NoResultView(EmptyRsetView):
+class NoResultView(View):
"""default view when no result has been found"""
+ __selectors__ = (empty_rset,)
id = 'noresult'
def call(self, **kwargs):
@@ -826,7 +827,7 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
erset = entity.as_rset()
- if linksearch_match(self.req, erset):
+ if self.req.match_search_state(erset):
self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
html_escape(linksearch_select_url(self.req, erset)),
self.req._('select this entity'),
--- a/web/views/bookmark.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/bookmark.py Mon Feb 16 20:51:41 2009 +0100
@@ -9,13 +9,26 @@
from logilab.mtconverter import html_escape
from cubicweb import Unauthorized
+from cubicweb.common.selectors import implements
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
+from cubicweb.web.action import Action
from cubicweb.web.box import UserRQLBoxTemplate
from cubicweb.web.views.baseviews import PrimaryView
+class FollowAction(Action):
+ id = 'follow'
+ __selectors__ = (implements('Bookmark'),)
+
+ title = _('follow')
+ category = 'mainactions'
+
+ def url(self):
+ return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
+
+
class BookmarkPrimaryView(PrimaryView):
- accepts = ('Bookmark',)
+ __selectors__ = (implements('Bookmark'),)
def cell_call(self, row, col):
"""the primary view for bookmark entity"""
--- a/web/views/boxes.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/boxes.py Mon Feb 16 20:51:41 2009 +0100
@@ -17,7 +17,7 @@
from logilab.mtconverter import html_escape
-from cubicweb.common.selectors import any_rset, appobject_selectable
+from cubicweb.selectors import any_rset, appobject_selectable, match_user_groups
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
from cubicweb.web.box import BoxTemplate, ExtResourcesBoxTemplate
@@ -167,10 +167,10 @@
class PossibleViewsBox(BoxTemplate):
"""display a box containing links to all possible views"""
id = 'possible_views_box'
+ __selectors__ = (match_user_groups('users', 'managers'),)
title = _('possible views')
order = 10
- require_groups = ('users', 'managers')
visible = False
def call(self, **kwargs):
--- a/web/views/debug.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/debug.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,6 +11,7 @@
from logilab.mtconverter import html_escape
+from cubicweb.selectors import none_rset, match_user_groups
from cubicweb.common.view import StartupView
def dict_to_html(w, dict):
@@ -21,11 +22,12 @@
w(u'<li><span class="label">%s</span>: <span>%s</span></li>' % (
html_escape(str(key)), html_escape(repr(dict[key]))))
w(u'</ul>')
+
class DebugView(StartupView):
id = 'debug'
+ __selectors__ = (none_rset, match_user_groups('managers'),)
title = _('server debug information')
- require_groups = ('managers',)
def call(self, **kwargs):
"""display server information"""
--- a/web/views/embedding.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/embedding.py Mon Feb 16 20:51:41 2009 +0100
@@ -3,7 +3,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -17,8 +17,8 @@
from cubicweb import urlquote # XXX should use view.url_quote method
from cubicweb.interfaces import IEmbedable
from cubicweb.common.uilib import soup2xhtml
-from cubicweb.common.selectors import (one_line_rset, score_entity_selector,
- match_search_state, implement_interface)
+from cubicweb.common.selectors import (one_line_rset, score_entity,
+ match_search_state, implements)
from cubicweb.common.view import NOINDEX, NOFOLLOW
from cubicweb.web.controller import Controller
from cubicweb.web.action import Action
@@ -75,30 +75,28 @@
return self.vreg.main_template(req, self.template, body=body)
+def entity_has_embedable_url(entity):
+ """return 1 if the entity provides an allowed embedable url"""
+ url = entity.embeded_url()
+ if not url or not url.strip():
+ return 0
+ allowed = entity.config['embed-allowed']
+ if allowed is None or not allowed.match(url):
+ return 0
+ return 1
+
+
class EmbedAction(Action):
"""display an 'embed' link on entity implementing `embeded_url` method
if the returned url match embeding configuration
"""
id = 'embed'
- controller = 'embed'
- __selectors__ = (one_line_rset, match_search_state,
- implement_interface, score_entity_selector)
- accepts_interfaces = (IEmbedable,)
+ __selectors__ = (one_line_rset, match_search_state('normal'),
+ implements(IEmbedable),
+ score_entity(entity_has_embedable_url))
title = _('embed')
-
- @classmethod
- def score_entity(cls, entity):
- """return a score telling how well I can display the given
- entity instance (required by the value_selector)
- """
- url = entity.embeded_url()
- if not url or not url.strip():
- return 0
- allowed = cls.config['embed-allowed']
- if allowed is None or not allowed.match(url):
- return 0
- return 1
+ controller = 'embed'
def url(self, row=0):
entity = self.rset.get_entity(row, 0)
@@ -132,6 +130,7 @@
url = '%s?custom_css=%s' % (url, self.custom_css)
return '<a href="%s"' % url
+
class absolutize_links:
def __init__(self, embedded_url, tag, custom_css=None):
self.embedded_url = embedded_url
@@ -152,7 +151,8 @@
for rgx, repl in filters:
body = rgx.sub(repl, body)
return body
-
+
+
def embed_external_page(url, prefix, headers=None, custom_css=None):
req = Request(url, headers=(headers or {}))
content = urlopen(req).read()
--- a/web/views/euser.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/euser.py Mon Feb 16 20:51:41 2009 +0100
@@ -10,12 +10,30 @@
from logilab.mtconverter import html_escape
from cubicweb.schema import display_name
+from cubicweb.common.selectors import one_line_rset, implements, match_user_groups
from cubicweb.web import INTERNAL_FIELD_VALUE
from cubicweb.web.form import EntityForm
+from cubicweb.web.action import Action
from cubicweb.web.views.baseviews import PrimaryView, EntityView
+
+class UserPreferencesEntityAction(Action):
+ id = 'prefs'
+ __selectors__ = (one_line_rset,
+ implements('EUser'),
+ match_user_groups('owners', 'managers'))
+
+ title = _('preferences')
+ category = 'mainactions'
+
+ def url(self):
+ login = self.rset.get_entity(self.row or 0, self.col or 0).login
+ return self.build_url('euser/%s'%login, vid='epropertiesform')
+
+
class EUserPrimaryView(PrimaryView):
- accepts = ('EUser',)
+ __selectors__ = (implements('EUser'),)
+
skip_attrs = ('firstname', 'surname')
def iter_relations(self, entity):
@@ -34,7 +52,8 @@
]
class FoafView(EntityView):
id = 'foaf'
- accepts = ('EUser',)
+ __selectors__ = (implements('EUser'),)
+
title = _('foaf')
templatable = False
content_type = 'text/xml'
@@ -54,7 +73,6 @@
<foaf:maker rdf:resource="%s"/>
<foaf:primaryTopic rdf:resource="%s"/>
</foaf:PersonalProfileDocument>''' % (entity.absolute_url(), entity.absolute_url()))
-
self.w(u'<foaf:Person rdf:ID="%s">\n' % entity.eid)
self.w(u'<foaf:name>%s</foaf:name>\n' % html_escape(entity.dc_long_title()))
if entity.surname:
@@ -68,11 +86,13 @@
self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
self.w(u'</foaf:Person>\n')
+
class FoafUsableView(FoafView):
id = 'foaf_usable'
def call(self):
self.cell_call(0, 0)
+
class EditGroups(EntityForm):
"""displays a simple euser / egroups editable table"""
--- a/web/views/idownloadable.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/idownloadable.py Mon Feb 16 20:51:41 2009 +0100
@@ -1,7 +1,7 @@
"""Specific views for entities implementing IDownloadable
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -10,8 +10,8 @@
from cubicweb.interfaces import IDownloadable
from cubicweb.common.mttransforms import ENGINE
-from cubicweb.common.selectors import (one_line_rset, score_entity_selector,
- implement_interface, match_context_prop)
+from cubicweb.common.selectors import (one_line_rset, score_entity,
+ implements, match_context_prop)
from cubicweb.web.box import EntityBoxTemplate
from cubicweb.web.views import baseviews
@@ -35,8 +35,7 @@
class DownloadBox(EntityBoxTemplate):
id = 'download_box'
- __selectors__ = (one_line_rset, implement_interface, match_context_prop)
- accepts_interfaces = (IDownloadable,)
+ __selectors__ = (one_line_rset, implements(IDownloadable), match_context_prop)
order = 10
def cell_call(self, row, col, title=None, label=None, **kwargs):
entity = self.entity(row, col)
@@ -44,12 +43,11 @@
class DownloadView(baseviews.EntityView):
- """this view is replacing the deprecated 'download' controller and allow downloading
- of entities providing the necessary interface
+ """this view is replacing the deprecated 'download' controller and allow
+ downloading of entities providing the necessary interface
"""
id = 'download'
- __selectors__ = (one_line_rset, implement_interface)
- accepts_interfaces = (IDownloadable,)
+ __selectors__ = (one_line_rset, implements(IDownloadable))
templatable = False
content_type = 'application/octet-stream'
@@ -76,10 +74,9 @@
class DownloadLinkView(baseviews.EntityView):
"""view displaying a link to download the file"""
id = 'downloadlink'
+ __selectors__ = (implements(IDownloadable),)
title = None # should not be listed in possible views
- __selectors__ = (implement_interface,)
- accepts_interfaces = (IDownloadable,)
def cell_call(self, row, col, title=None, **kwargs):
entity = self.entity(row, col)
@@ -89,9 +86,8 @@
class IDownloadablePrimaryView(baseviews.PrimaryView):
- __selectors__ = (implement_interface,)
+ __selectors__ = (implements(IDownloadable),)
#skip_attrs = ('eid', 'data',) # XXX
- accepts_interfaces = (IDownloadable,)
def render_entity_title(self, entity):
self.w(u'<h1>%s %s</h1>'
@@ -122,10 +118,7 @@
class IDownloadableLineView(baseviews.OneLineView):
- __selectors__ = (implement_interface,)
- # don't kick default oneline view
- accepts_interfaces = (IDownloadable,)
-
+ __selectors__ = (implements(IDownloadable),)
def cell_call(self, row, col, title=None, **kwargs):
"""the secondary view is a link to download the file"""
@@ -137,11 +130,18 @@
(url, name, durl, self.req._('download')))
+def is_image(entity):
+ mt = entity.download_content_type()
+ if not (mt and mt.startswith('image/')):
+ return 0
+ return 1
+
class ImageView(baseviews.EntityView):
- __selectors__ = (implement_interface, score_entity_selector)
id = 'image'
+ __selectors__ = (implements(IDownloadable),
+ score_entity(is_image))
+
title = _('image')
- accepts_interfaces = (IDownloadable,)
def call(self):
rset = self.rset
@@ -149,13 +149,6 @@
self.w(u'<div class="efile">')
self.wview(self.id, rset, row=i, col=0)
self.w(u'</div>')
-
- @classmethod
- def score_entity(cls, entity):
- mt = entity.download_content_type()
- if not (mt and mt.startswith('image/')):
- return 0
- return 1
def cell_call(self, row, col):
entity = self.entity(row, col)
--- a/web/views/management.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/management.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,12 +11,11 @@
from logilab.common.decorators import cached
+from cubicweb.selectors import (yes, one_line_rset, none_rset,
+ match_user_groups, chainfirst, chainall)
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,
- accept_rset, none_rset,
- chainfirst, chainall)
from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
from cubicweb.web.widgets import StaticComboBoxWidget
from cubicweb.web.form import FormMixIn
@@ -293,11 +292,12 @@
def css_class(someclass):
return someclass and 'class="%s"' % someclass or ''
-class SystemEpropertiesForm(FormMixIn, StartupView):
- controller = 'edit'
+class SystemEPropertiesForm(FormMixIn, StartupView):
id = 'systemepropertiesform'
+ __selectors__ = (none_rset, match_user_groups('managers'),)
+
title = _('site configuration')
- require_groups = ('managers',)
+ controller = 'edit'
category = 'startupview'
def linkable(self):
@@ -461,24 +461,23 @@
w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('edits-pkey', eid), ''))
-class EpropertiesForm(SystemEpropertiesForm):
+
+def is_user_prefs(cls, req, rset, row, col):
+ return req.user.eid == rset[row or 0 ][col or 0]
+
+class EPropertiesForm(SystemEPropertiesForm):
id = 'epropertiesform'
- title = _('preferences')
- require_groups = ('users', 'managers') # we don't want guests to be able to come here
- __selectors__ = chainfirst(none_rset,
- chainall(one_line_rset, accept_rset)),
+ __selectors__ = (
+ # we don't want guests to be able to come here
+ match_user_groups('users', 'managers'),
+ chainfirst(none_rset),
+ chainall(one_line_rset, is_user_prefs),
+ chainall(one_line_rset, match_user_groups('managers'))
+ )
+
accepts = ('EUser',)
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- if row is None:
- row = 0
- score = super(EpropertiesForm, cls).accept_rset(req, rset, row, col)
- # check current user is the rset user or he is in the managers group
- if score and (req.user.eid == rset[row][col or 0]
- or req.user.matching_groups('managers')):
- return score
- return 0
+ title = _('preferences')
@property
def user(self):
@@ -493,7 +492,7 @@
'P for_user U, U eid %(x)s', {'x': self.user.eid})
def form_row_hiddens(self, w, entity, key):
- super(EpropertiesForm, self).form_row_hiddens(w, entity, key)
+ super(EPropertiesForm, self).form_row_hiddens(w, entity, key)
# if user is in the managers group and the property is being created,
# we have to set for_user explicitly
if not entity.has_eid() and self.user.matching_groups('managers'):
@@ -508,8 +507,9 @@
class ProcessInformationView(StartupView):
id = 'info'
+ __selectors__ = (none_rset, match_user_groups('managers'),)
+
title = _('server information')
- require_groups = ('managers',)
def call(self, **kwargs):
"""display server information"""
--- a/web/views/massmailing.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/massmailing.py Mon Feb 16 20:51:41 2009 +0100
@@ -1,7 +1,7 @@
"""Mass mailing form views
:organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -10,17 +10,16 @@
from logilab.mtconverter import html_escape
from cubicweb.interfaces import IEmailable
+from cubicweb.selectors import implements, match_user_groups
from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import implement_interface, match_user_group
-from cubicweb.web.action import EntityAction
+from cubicweb.web.action import Action
from cubicweb.web import stdmsgs
-class SendEmailAction(EntityAction):
+class SendEmailAction(Action):
category = 'mainactions'
- __selectors__ = (implement_interface, match_user_group)
- accepts_interfaces = (IEmailable,) # XXX should check email is set as well
- require_groups = ('managers', 'users')
+ # XXX should check email is set as well
+ __selectors__ = (implements(IEmailable), match_user_groups('managers', 'users'))
id = 'sendemail'
title = _('send email')
@@ -35,10 +34,7 @@
class MassMailingForm(EntityView):
id = 'massmailing'
- __selectors__ = (implement_interface, match_user_group)
- accepts_interfaces = (IEmailable,)
- require_groups = ('managers', 'users')
-
+ __selectors__ = (implements(IEmailable), match_user_groups('managers', 'users'))
form_template = u"""
<div id="compose">
--- a/web/views/schemaentities.py Mon Feb 16 20:42:30 2009 +0100
+++ b/web/views/schemaentities.py Mon Feb 16 20:51:41 2009 +0100
@@ -111,8 +111,10 @@
from cubicweb.web.action import EntityAction
-class ViewWorkflowAction(EntityAction):
+class ViewWorkflowAction(Action):
id = 'workflow'
+ __selectors__ = (implements('EEType'), )
+
category = 'mainactions'
title = _('view workflow')
accepts = ('EEType',)