--- a/common/appobject.py Tue Feb 17 16:20:53 2009 +0100
+++ b/common/appobject.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/common/entity.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/common/registerers.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/common/selectors.py Tue Feb 17 16:25:20 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/utils.py Tue Feb 17 16:20:53 2009 +0100
+++ b/common/utils.py Tue Feb 17 16:25:20 2009 +0100
@@ -177,7 +177,7 @@
self.post_inlined_scripts.append(self.js_unload_code)
self.pagedata_unload = True
- def getvalue(self):
+ def getvalue(self, skiphead=False):
"""reimplement getvalue to provide a consistent (and somewhat browser
optimzed cf. http://stevesouders.com/cuzillion) order in external
resources declaration
@@ -209,7 +209,10 @@
w(u'<script type="text/javascript">\n')
w(u'\n\n'.join(self.post_inlined_scripts))
w(u'\n</script>\n')
- return u'<head>\n%s</head>\n' % super(HTMLHead, self).getvalue()
+ header = super(HTMLHead, self).getvalue()
+ if skiphead:
+ return header
+ return u'<head>\n%s</head>\n' % header
class HTMLStream(object):
--- a/common/view.py Tue Feb 17 16:20:53 2009 +0100
+++ b/common/view.py Tue Feb 17 16:25:20 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.appobject import AppRsetObject
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
@@ -466,15 +466,3 @@
self._stream.doctype = self.doctype
if not xmldecl:
self._stream.xmldecl = u''
-
-# viewable components base classes ############################################
-
-class VComponent(ComponentMixIn, View):
- """base class for displayable components"""
- property_defs = {
- 'visible': dict(type='Boolean', default=True,
- help=_('display the component or not')),}
-
-class SingletonVComponent(VComponent):
- """base class for displayable unique components"""
- __registerer__ = priority_registerer
--- a/cwvreg.py Tue Feb 17 16:20:53 2009 +0100
+++ b/cwvreg.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/goa/appobjects/dbmgmt.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/goa/appobjects/sessions.py Tue Feb 17 16:25:20 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
--- a/i18n/en.po Tue Feb 17 16:20:53 2009 +0100
+++ b/i18n/en.po Tue Feb 17 16:25:20 2009 +0100
@@ -1816,6 +1816,9 @@
msgid "i18n_login_popup"
msgstr "login"
+msgid "i18n_register_user"
+msgstr "register"
+
msgid "i18nprevnext_next"
msgstr "next"
@@ -2021,6 +2024,9 @@
msgid "login"
msgstr ""
+msgid "login or email"
+msgstr ""
+
msgid "login_action"
msgstr "log in"
--- a/i18n/es.po Tue Feb 17 16:20:53 2009 +0100
+++ b/i18n/es.po Tue Feb 17 16:25:20 2009 +0100
@@ -1899,6 +1899,9 @@
msgid "i18n_login_popup"
msgstr "identificarse"
+msgid "i18n_register_user"
+msgstr "registrarse"
+
msgid "i18nprevnext_next"
msgstr "siguiente"
--- a/i18n/fr.po Tue Feb 17 16:20:53 2009 +0100
+++ b/i18n/fr.po Tue Feb 17 16:25:20 2009 +0100
@@ -1899,6 +1899,9 @@
msgid "i18n_login_popup"
msgstr "s'authentifier"
+msgid "i18n_register_user"
+msgstr "s'enregister"
+
msgid "i18nprevnext_next"
msgstr "suivant"
@@ -2115,6 +2118,9 @@
msgid "login"
msgstr "identifiant"
+msgid "login or email"
+msgstr "identifiant ou email"
+
msgid "login_action"
msgstr "identifiez vous"
--- a/schema.py Tue Feb 17 16:20:53 2009 +0100
+++ b/schema.py Tue Feb 17 16:25:20 2009 +0100
@@ -10,7 +10,7 @@
import re
from logging import getLogger
-from logilab.common.decorators import cached, clear_cache
+from logilab.common.decorators import cached, clear_cache, monkeypatch
from logilab.common.compat import any
from yams import BadSchemaDefinition, buildobjs as ybo
@@ -68,6 +68,41 @@
return (etype,)
ybo.RelationDefinition._actual_types = _actual_types
+
+## cubicweb provides a RichString class for convenience
+class RichString(ybo.String):
+ """Convenience RichString attribute type
+ The follwing declaration::
+
+ class Card(EntityType):
+ content = RichString(fulltextindexed=True, default_format='text/rest')
+
+ is equivalent to::
+
+ class Card(EntityType):
+ content_format = String(meta=True, internationalizable=True,
+ default='text/rest', constraints=[format_constraint])
+ content = String(fulltextindexed=True)
+ """
+ def __init__(self, default_format='text/plain', format_constraints=None, **kwargs):
+ self.default_format = default_format
+ self.format_constraints = format_constraints or [format_constraint]
+ super(RichString, self).__init__(**kwargs)
+
+PyFileReader.context['RichString'] = RichString
+
+## need to monkeypatch yams' _add_relation function to handle RichString
+yams_add_relation = ybo._add_relation
+@monkeypatch(ybo)
+def _add_relation(relations, rdef, name=None, insertidx=None):
+ if isinstance(rdef, RichString):
+ default_format = rdef.default_format
+ format_attrdef = ybo.String(meta=True, internationalizable=True,
+ default=rdef.default_format, maxsize=50,
+ constraints=rdef.format_constraints)
+ yams_add_relation(relations, format_attrdef, name+'_format', insertidx)
+ yams_add_relation(relations, rdef, name, insertidx)
+
def display_name(req, key, form=''):
"""return a internationalized string for the key (schema entity or relation
name) in a given form
@@ -805,7 +840,34 @@
PyFileReader.context['RRQLExpression'] = RRQLExpression
-
+# workflow extensions #########################################################
+
+class workflowable_definition(ybo.metadefinition):
+ """extends default EntityType's metaclass to add workflow relations
+ (i.e. in_state and wf_info_for).
+ This is the default metaclass for WorkflowableEntityType
+ """
+ def __new__(mcs, name, bases, classdict):
+ abstract = classdict.pop('abstract', False)
+ defclass = super(workflowable_definition, mcs).__new__(mcs, name, bases, classdict)
+ if not abstract:
+ existing_rels = set(rdef.name for rdef in defclass.__relations__)
+ if 'in_state' not in existing_rels and 'wf_info_for' not in existing_rels:
+ in_state = ybo.SubjectRelation('State', cardinality='1*',
+ # XXX automatize this
+ constraints=[RQLConstraint('S is ET, O state_of ET')],
+ description=_('account state'))
+ yams_add_relation(defclass.__relations__, in_state, 'in_state')
+ wf_info_for = ybo.ObjectRelation('TrInfo', cardinality='1*', composite='object')
+ yams_add_relation(defclass.__relations__, wf_info_for, 'wf_info_for')
+ return defclass
+
+class WorkflowableEntityType(ybo.EntityType):
+ __metaclass__ = workflowable_definition
+ abstract = True
+
+PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
+
# schema loading ##############################################################
class CubicWebRelationFileReader(RelationFileReader):
@@ -877,6 +939,7 @@
def _load_definition_files(self, cubes):
for filepath in (self.include_schema_files('bootstrap')
+ self.include_schema_files('base')
+ + self.include_schema_files('workflow')
+ self.include_schema_files('Bookmark')
+ self.include_schema_files('Card')):
self.info('loading %s', filepath)
--- a/schemas/Card.py Tue Feb 17 16:20:53 2009 +0100
+++ b/schemas/Card.py Tue Feb 17 16:25:20 2009 +0100
@@ -1,4 +1,4 @@
-from cubicweb.schema import format_constraint
+# from cubicweb.schema import format_constraint
class Card(EntityType):
"""a card is a textual content used as documentation, reference, procedure reminder"""
@@ -12,7 +12,5 @@
title = String(required=True, fulltextindexed=True, maxsize=256)
synopsis = String(fulltextindexed=True, maxsize=512,
description=_("an abstract for this card"))
- content_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- content = String(fulltextindexed=True)
+ content = RichString(fulltextindexed=True, default_format='text/rest')
wikiid = String(maxsize=64, indexed=True)
--- a/schemas/base.py Tue Feb 17 16:20:53 2009 +0100
+++ b/schemas/base.py Tue Feb 17 16:25:20 2009 +0100
@@ -9,8 +9,9 @@
from cubicweb.schema import format_constraint
-class EUser(RestrictedEntityType):
+class EUser(WorkflowableEntityType):
"""define a CubicWeb user"""
+ meta = True # XXX backported from old times, shouldn't be there anymore
permissions = {
'read': ('managers', 'users', ERQLExpression('X identity U')),
'add': ('managers',),
@@ -33,11 +34,6 @@
in_group = SubjectRelation('EGroup', cardinality='+*',
constraints=[RQLConstraint('NOT O name "owners"')],
description=_('groups grant permissions to the user'))
- in_state = SubjectRelation('State', cardinality='1*',
- # XXX automatize this
- constraints=[RQLConstraint('S is ET, O state_of ET')],
- description=_('account state'))
- wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object')
class EmailAddress(MetaEntityType):
@@ -130,112 +126,7 @@
cardinality = '11'
subject = '**'
object = 'Datetime'
-
-
-class State(MetaEntityType):
- """used to associate simple states to an entity type and/or to define
- workflows
- """
- name = String(required=True, indexed=True, internationalizable=True,
- maxsize=256)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- description = String(fulltextindexed=True,
- description=_('semantic description of this state'))
- state_of = SubjectRelation('EEType', cardinality='+*',
- description=_('entity types which may use this state'),
- constraints=[RQLConstraint('O final FALSE')])
- allowed_transition = SubjectRelation('Transition', cardinality='**',
- constraints=[RQLConstraint('S state_of ET, O transition_of ET')],
- description=_('allowed transitions from this state'))
-
- initial_state = ObjectRelation('EEType', cardinality='?*',
- # S initial_state O, O state_of S
- constraints=[RQLConstraint('O state_of S')],
- description=_('initial state for entities of this type'))
-
-
-class Transition(MetaEntityType):
- """use to define a transition from one or multiple states to a destination
- states in workflow's definitions.
- """
- name = String(required=True, indexed=True, internationalizable=True,
- maxsize=256)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- description = String(fulltextindexed=True,
- description=_('semantic description of this transition'))
- condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
- description=_('a RQL expression which should return some results, '
- 'else the transition won\'t be available. '
- 'This query may use X and U variables '
- 'that will respectivly represents '
- 'the current entity and the current user'))
-
- require_group = SubjectRelation('EGroup', cardinality='**',
- description=_('group in which a user should be to be '
- 'allowed to pass this transition'))
- transition_of = SubjectRelation('EEType', cardinality='+*',
- description=_('entity types which may use this transition'),
- constraints=[RQLConstraint('O final FALSE')])
- destination_state = SubjectRelation('State', cardinality='?*',
- constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
- description=_('destination state for this transition'))
-
-
-class TrInfo(MetaEntityType):
- from_state = SubjectRelation('State', cardinality='?*')
- to_state = SubjectRelation('State', cardinality='1*')
- comment_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- comment = String(fulltextindexed=True)
- # get actor and date time using owned_by and creation_date
-
-
-class from_state(MetaRelationType):
- inlined = True
-class to_state(MetaRelationType):
- inlined = True
-class wf_info_for(MetaRelationType):
- """link a transition information to its object"""
- permissions = {
- 'read': ('managers', 'users', 'guests',),# RRQLExpression('U has_read_permission O')),
- 'add': (), # handled automatically, no one should add one explicitly
- 'delete': ('managers',), # RRQLExpression('U has_delete_permission O')
- }
- inlined = True
- composite = 'object'
- fulltext_container = composite
-
-class state_of(MetaRelationType):
- """link a state to one or more entity type"""
-class transition_of(MetaRelationType):
- """link a transition to one or more entity type"""
-
-class initial_state(MetaRelationType):
- """indicate which state should be used by default when an entity using
- states is created
- """
- inlined = True
-
-class destination_state(MetaRelationType):
- """destination state of a transition"""
- inlined = True
-
-class allowed_transition(MetaRelationType):
- """allowed transition from this state"""
-
-class in_state(UserRelationType):
- """indicate the current state of an entity"""
- meta = True
- # not inlined intentionnaly since when using ldap sources, user'state
- # has to be stored outside the EUser table
-
- # add/delete perms given to managers/users, after what most of the job
- # is done by workflow enforcment
-
-
class EProperty(EntityType):
"""used for cubicweb configuration. Once a property has been created you
can't change the key.
--- a/schemas/bootstrap.py Tue Feb 17 16:20:53 2009 +0100
+++ b/schemas/bootstrap.py Tue Feb 17 16:25:20 2009 +0100
@@ -1,7 +1,7 @@
"""core CubicWeb schema necessary for bootstrapping the actual application's schema
: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
"""
@@ -14,10 +14,8 @@
"""define an entity type, used to build the application schema"""
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/plain', constraints=[format_constraint])
- description = String(internationalizable=True,
- description=_('semantic description of this entity type'))
+ description = RichString(internationalizable=True,
+ description=_('semantic description of this entity type'))
meta = Boolean(description=_('is it an application entity type or not ?'))
# necessary to filter using RQL
final = Boolean(description=_('automatic'))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/workflow.py Tue Feb 17 16:25:20 2009 +0100
@@ -0,0 +1,108 @@
+"""workflow related schemas
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+class State(MetaEntityType):
+ """used to associate simple states to an entity type and/or to define
+ workflows
+ """
+ name = String(required=True, indexed=True, internationalizable=True,
+ maxsize=256)
+ description = RichString(fulltextindexed=True, default='text/rest',
+ description=_('semantic description of this state'))
+
+ state_of = SubjectRelation('EEType', cardinality='+*',
+ description=_('entity types which may use this state'),
+ constraints=[RQLConstraint('O final FALSE')])
+ allowed_transition = SubjectRelation('Transition', cardinality='**',
+ constraints=[RQLConstraint('S state_of ET, O transition_of ET')],
+ description=_('allowed transitions from this state'))
+
+ initial_state = ObjectRelation('EEType', cardinality='?*',
+ # S initial_state O, O state_of S
+ constraints=[RQLConstraint('O state_of S')],
+ description=_('initial state for entities of this type'))
+
+
+class Transition(MetaEntityType):
+ """use to define a transition from one or multiple states to a destination
+ states in workflow's definitions.
+ """
+ name = String(required=True, indexed=True, internationalizable=True,
+ maxsize=256)
+ description_format = String(meta=True, internationalizable=True, maxsize=50,
+ default='text/rest', constraints=[format_constraint])
+ description = String(fulltextindexed=True,
+ description=_('semantic description of this transition'))
+ condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
+ description=_('a RQL expression which should return some results, '
+ 'else the transition won\'t be available. '
+ 'This query may use X and U variables '
+ 'that will respectivly represents '
+ 'the current entity and the current user'))
+
+ require_group = SubjectRelation('EGroup', cardinality='**',
+ description=_('group in which a user should be to be '
+ 'allowed to pass this transition'))
+ transition_of = SubjectRelation('EEType', cardinality='+*',
+ description=_('entity types which may use this transition'),
+ constraints=[RQLConstraint('O final FALSE')])
+ destination_state = SubjectRelation('State', cardinality='?*',
+ constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
+ description=_('destination state for this transition'))
+
+
+class TrInfo(MetaEntityType):
+ from_state = SubjectRelation('State', cardinality='?*')
+ to_state = SubjectRelation('State', cardinality='1*')
+ comment_format = String(meta=True, internationalizable=True, maxsize=50,
+ default='text/rest', constraints=[format_constraint])
+ comment = String(fulltextindexed=True)
+ # get actor and date time using owned_by and creation_date
+
+
+class from_state(MetaRelationType):
+ inlined = True
+class to_state(MetaRelationType):
+ inlined = True
+class wf_info_for(MetaRelationType):
+ """link a transition information to its object"""
+ permissions = {
+ 'read': ('managers', 'users', 'guests',),# RRQLExpression('U has_read_permission O')),
+ 'add': (), # handled automatically, no one should add one explicitly
+ 'delete': ('managers',), # RRQLExpression('U has_delete_permission O')
+ }
+ inlined = True
+ composite = 'object'
+ fulltext_container = composite
+
+class state_of(MetaRelationType):
+ """link a state to one or more entity type"""
+class transition_of(MetaRelationType):
+ """link a transition to one or more entity type"""
+
+class initial_state(MetaRelationType):
+ """indicate which state should be used by default when an entity using
+ states is created
+ """
+ inlined = True
+
+class destination_state(MetaRelationType):
+ """destination state of a transition"""
+ inlined = True
+
+class allowed_transition(MetaRelationType):
+ """allowed transition from this state"""
+
+class in_state(UserRelationType):
+ """indicate the current state of an entity"""
+ meta = True
+ # not inlined intentionnaly since when using ldap sources, user'state
+ # has to be stored outside the EUser table
+
+ # add/delete perms given to managers/users, after what most of the job
+ # is done by workflow enforcment
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/selectors.py Tue Feb 17 16:25:20 2009 +0100
@@ -0,0 +1,969 @@
+"""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
+
+
+# abstract selectors ##########################################################
+
+class EClassSelector(Selector):
+ """abstract class for selectors working on the entity classes of the result
+ set. Its __call__ method has the following behaviour:
+
+ * if row is specified, return the score returned by the score_class method
+ called with the entity class found in the specified cell
+ * else return the sum of score returned by the score_class method for each
+ entity type found in the specified column, unless:
+ - `once_is_enough` is True, in which case the first non-zero score is
+ returned
+ - `once_is_enough` is False, in which case if score_class return 0, 0 is
+ returned
+ """
+ def __init__(self, once_is_enough=False):
+ self.once_is_enough = once_is_enough
+
+ @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
+ escore = self.score(cls, req, etype)
+ if not escore and not self.once_is_enough:
+ return 0
+ elif self.once_is_enough:
+ return escore
+ score += escore
+ else:
+ etype = rset.description[row][col]
+ if etype is not None:
+ score = self.score(cls, req, etype)
+ return score and (score + 1)
+
+ def score(self, cls, req, etype):
+ if etype in BASE_TYPES:
+ return 0
+ return self.score_class(cls.vreg.etype_class(etype), req)
+
+ def score_class(self, eclass, req):
+ raise NotImplementedError()
+
+
+class EntitySelector(EClassSelector):
+ """abstract class for selectors working on the entity instances of the
+ result set. Its __call__ method has the following behaviour:
+
+ * if row is specified, return the score returned by the score_entity method
+ called with the entity instance found in the specified cell
+ * else return the sum of score returned by the score_entity method for each
+ entity found in the specified column, unless:
+ - `once_is_enough` is True, in which case the first non-zero score is
+ returned
+ - `once_is_enough` is False, in which case if score_class return 0, 0 is
+ returned
+
+ note: None values (resulting from some outer join in the query) are not
+ considered.
+ """
+
+ @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
+ escore = self.score(req, rset, row, col)
+ if not escore and not self.once_is_enough:
+ return 0
+ elif self.once_is_enough:
+ return escore
+ 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()
+
+
+# very basic selectors ########################################################
+
+def yes(cls, *args, **kwargs):
+ """accept everything"""
+ return 1
+
+@lltrace
+def none_rset(cls, req, rset, *args, **kwargs):
+ """accept no result set (e.g. given rset is None)"""
+ if rset is None:
+ return 1
+ return 0
+
+@lltrace
+def any_rset(cls, req, rset, *args, **kwargs):
+ """accept result set, whatever the number of result it contains"""
+ if rset is not None:
+ return 1
+ return 0
+
+@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
+
+@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
+
+@lltrace
+def one_line_rset(cls, req, rset, row=None, *args, **kwargs):
+ """if row is specified, accept result set with a single line of result,
+ else accepts anyway
+ """
+ if rset is not None and (row is not None or rset.rowcount == 1):
+ return 1
+ return 0
+
+@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
+
+@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 and len(rset.rows[0]) > 1:
+ return 1
+ return 0
+
+@lltrace
+def paginated_rset(cls, req, rset, *args, **kwargs):
+ """accept result set with more lines than the page size.
+
+ Page size is searched in (respecting order):
+ * a page_size argument
+ * a page_size form parameters
+ * the navigation.page-size property
+ """
+ page_size = kwargs.get('page_size')
+ if page_size is None:
+ page_size = req.form.get('page_size')
+ if page_size is None:
+ page_size = req.property_value('navigation.page-size')
+ else:
+ page_size = int(page_size)
+ if rset is None or rset.rowcount <= page_size:
+ return 0
+ return 1
+
+@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
+
+@lltrace
+def one_etype_rset(cls, req, rset, row=None, col=0, *args, **kwargs):
+ """accept result set where entities in the specified column (or 0) are all
+ of the same type
+ """
+ if len(rset.column_types(col)) != 1:
+ return 0
+ return 1
+
+@lltrace
+def two_etypes_rset(cls, req, rset, row=None, col=0, **kwargs):
+ """accept result set where entities in the specified column (or 0) are not
+ of the same type
+ """
+ if rset:
+ etypes = rset.column_types(col)
+ if len(etypes) > 1:
+ return 1
+ return 0
+
+class non_final_entity(EClassSelector):
+ """accept if entity type found in the result set is non final.
+
+ See `EClassSelector` documentation for behaviour when row is not specified.
+ """
+ def score(self, cls, req, etype):
+ if etype in BASE_TYPES:
+ return 0
+ return 1
+
+@lltrace
+def anonymous_user(cls, req, *args, **kwargs):
+ """accept if user is anonymous"""
+ if req.cnx.anonymous_connection:
+ return 1
+ return 0
+
+@lltrace
+def authenticated_user(cls, req, *args, **kwargs):
+ """accept if user is authenticated"""
+ return not anonymous_user(cls, req, *args, **kwargs)
+
+@lltrace
+def primary_view(cls, req, rset, row=None, col=0, view=None, **kwargs):
+ """accept if view given as named argument is a primary view, or if no view
+ is given
+ """
+ if view is not None and not view.is_primary():
+ return 0
+ return 1
+
+@lltrace
+def match_context_prop(cls, req, rset, row=None, col=0, context=None,
+ **kwargs):
+ """accept if:
+ * no context given
+ * context (`basestring`) is matching the context property value for the
+ given cls
+ """
+ propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
+ if not propval:
+ propval = cls.context
+ if context is not None and propval and context != propval:
+ return 0
+ return 1
+
+
+class match_search_state(Selector):
+ """accept if the current request search state is in one of the expected
+ states given to the initializer
+
+ :param expected: either 'normal' or 'linksearch' (eg searching for an
+ object to create a relation with another)
+ """
+ def __init__(self, *expected):
+ self.expected = expected
+
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ try:
+ if not req.search_state[0] in self.expected:
+ return 0
+ except AttributeError:
+ return 1 # class doesn't care about search state, accept it
+ return 1
+
+
+class match_form_params(match_search_state):
+ """accept if parameters specified as initializer arguments are specified
+ in request's form parameters
+
+ :param *expected: parameters (eg `basestring`) which are expected to be
+ found in request's 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):
+ """accept if parameters specified as initializer arguments are specified
+ in named arguments given to the selector
+
+ :param *expected: parameters (eg `basestring`) which are expected to be
+ found in named arguments (kwargs)
+ """
+
+ @lltrace
+ def __call__(self, cls, req, *args, **kwargs):
+ for arg in self.expected:
+ if not arg in kwargs:
+ return 0
+ return len(self.expected)
+
+
+class match_user_groups(Selector):
+ """accept if logged users is in at least one of the given groups. Returned
+ score is the number of groups in which the user is.
+
+ If the special 'owners' group is given:
+ * if row is specified check the entity at the given row/col is owned by the
+ logged user
+ * if row is not specified check all entities in col are owned by the logged
+ user
+
+ :param *required_groups: name of groups (`basestring`) in which the logged
+ user should be
+ """
+
+ 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.required_groups)
+ score = user.matching_groups(self.required_groups)
+ if not score and 'owners' in self.required_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 appobject_selectable(Selector):
+ """accept with another appobject is selectable using selector's input
+ context.
+
+ :param registry: a registry name (`basestring`)
+ :param oid: an object identifier (`basestring`)
+ """
+ def __init__(self, registry, oid):
+ self.registry = registry
+ self.oid = oid
+
+ def __call__(self, cls, req, rset, *args, **kwargs):
+ try:
+ cls.vreg.select_object(self.registry, self.oid, req, rset, *args, **kwargs)
+ return 1
+ except NoSelectableObject:
+ return 0
+
+
+# not so basic selectors ######################################################
+
+class implements(EClassSelector):
+ """accept if entity class found in the result set implements at least one
+ of the interfaces given as argument. Returned score is the number of
+ implemented interfaces.
+
+ See `EClassSelector` documentation for behaviour when row is not specified.
+
+ :param *expected_ifaces: expected interfaces. An interface may be a class
+ or an entity type (e.g. `basestring`) in which case
+ the associated class will be searched in the
+ registry (at selection time)
+
+ note: when 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):
+ """accept if entity class specified using an 'etype' parameters in name
+ argument or request form implements at least one of the interfaces given as
+ argument. Returned score is the number of implemented interfaces.
+
+ :param *expected_ifaces: expected interfaces. An interface may be a class
+ or an entity type (e.g. `basestring`) in which case
+ the associated class will be searched in the
+ registry (at selection time)
+
+ note: when interface is an entity class, the score will reflect class
+ proximity so the most specific object'll be selected
+ """
+
+ @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):
+ """accept if entity class found in the result set support the relation.
+
+ See `EClassSelector` documentation for behaviour when row is not specified.
+
+ :param rtype: a relation type (`basestring`)
+ :param role: the role of the result set entity in the relation. 'subject' or
+ 'object', default to 'subject'.
+ :param target_type: if specified, check the relation's end may be of this
+ target type (`basestring`)
+ :param action: a relation schema action (one of 'read', 'add', 'delete')
+ which must be granted to the logged user, else a 0 score will
+ be returned
+ """
+ def __init__(self, rtype, role='subject', target_etype=None,
+ action='read', once_is_enough=False):
+ super(relation_possible, self).__init__(once_is_enough)
+ self.rtype = rtype
+ self.role = role
+ self.target_etype = target_etype
+ self.action = action
+
+ @lltrace
+ def __call__(self, cls, req, *args, **kwargs):
+ rschema = cls.schema.rschema(self.rtype)
+ if not (rschema.has_perm(req, self.action)
+ or rschema.has_local_role(self.action)):
+ return 0
+ return super(relation_possible, self).__call__(cls, req, *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 has_editable_relation(EntitySelector):
+ """accept if some relations for an entity found in the result set is
+ editable by the logged user.
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+ """
+
+ 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):
+ """accept if the relation can be added to an entity found in the result set
+ by the logged user.
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+
+ :param rtype: a relation type (`basestring`)
+ :param role: the role of the result set entity in the relation. 'subject' or
+ 'object', default to 'subject'.
+ """
+
+ def __init__(self, rtype, role='subject'):
+ 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 0
+ elif not rschema.has_perm(req, 'add', toeid=entity.eid):
+ return 0
+ return 1
+
+
+class has_related_entities(EntitySelector):
+ """accept if entity found in the result set has some linked entities using
+ the specified relation (optionaly filtered according to the specified target
+ type).
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+
+ :param rtype: a relation type (`basestring`)
+ :param role: the role of the result set entity in the relation. 'subject' or
+ 'object', default to 'subject'.
+ :param target_type: if specified, check the relation's end may be of this
+ target type (`basestring`)
+ """
+ def __init__(self, rtype, role='subject', target_etype=None,
+ once_is_enough=False):
+ self.rtype = rtype
+ self.role = role
+ self.target_etype = target_etype
+ self.once_is_enough = once_is_enough
+
+ def score_entity(self, entity):
+ rset = entity.related(self.rtype, self.role)
+ if self.target_etype:
+ return any(x for x, in rset.description if x == self.target_etype)
+ return bool(rset)
+
+
+class has_permission(EntitySelector):
+ """accept if user has the permission to do the requested action on a result
+ set entity.
+
+ * if row is specified, return 1 if user has the permission on the entity
+ instance found in the specified cell
+ * else return a positive score if user has the permission for every entity
+ in the found in the specified column
+
+ note: None values (resulting from some outer join in the query) are not
+ considered.
+
+ :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
+ """
+ def __init__(self, action):
+ self.action = action
+
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ user = req.user
+ action = self.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 += 1
+ 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(req, rset, i, col):
+ return 0
+ score += 1
+ return score
+ return self.score(req, rset, i, col)
+
+ def score_entity(self, entity):
+ if entity.has_perm(self.action):
+ return 1
+ return 0
+
+
+class has_add_permission(EClassSelector):
+ """accept if logged user has the add permission on entity class found in the
+ result set, and class is not a strict subobject.
+
+ See `EClassSelector` documentation for behaviour when row is not specified.
+ """
+ def score(self, cls, req, etype):
+ eschema = cls.schema.eschema(etype)
+ if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
+ and eschema.has_perm(req, 'add'):
+ return 1
+ return 0
+
+
+class rql_condition(EntitySelector):
+ """accept if an arbitrary rql return some results for an eid found in the
+ result set. Returned score is the number of items returned by the rql
+ condition.
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+
+ :param expression: basestring containing an rql expression, which should use
+ X variable to represent the context entity and may use U
+ to represent the logged user
+
+ return the sum of the number of items returned by the rql condition as score
+ or 0 at the first entity scoring to zero.
+ """
+ def __init__(self, expression):
+ if 'U' in frozenset(split_expression(expression)):
+ 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
+
+
+class but_etype(EntitySelector):
+ """accept if the given entity types are not found in the result set.
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+
+ :param *etypes: entity types (`basestring`) which should be refused
+ """
+ def __init__(self, *etypes):
+ self.but_etypes = etypes
+
+ def score(self, req, rset, row, col):
+ if rset.description[row][col] in self.but_etypes:
+ return 0
+ return 1
+
+
+class score_entity(EntitySelector):
+ """accept if some arbitrary function return a positive score for an entity
+ found in the result set.
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+
+ :param scorefunc: callable expected to take an entity as argument and to
+ return a score >= 0
+ """
+ def __init__(self, scorefunc):
+ self.score_entity = scorefunc
+
+
+# XXX DEPRECATED ##############################################################
+
+yes_selector = deprecated_function(yes)
+norset_selector = deprecated_function(none_rset)
+rset_selector = deprecated_function(any_rset)
+anyrset_selector = deprecated_function(nonempty_rset)
+emptyrset_selector = deprecated_function(empty_rset)
+onelinerset_selector = deprecated_function(one_line_rset)
+twolinerset_selector = deprecated_function(two_lines_rset)
+twocolrset_selector = deprecated_function(two_cols_rset)
+largerset_selector = deprecated_function(paginated_rset)
+sortedrset_selector = deprecated_function(sorted_rset)
+oneetyperset_selector = deprecated_function(one_etype_rset)
+multitype_selector = deprecated_function(two_etypes_rset)
+anonymous_selector = deprecated_function(anonymous_user)
+not_anonymous_selector = deprecated_function(authenticated_user)
+primaryview_selector = deprecated_function(primary_view)
+contextprop_selector = deprecated_function(match_context_prop)
+
+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)
+
+
+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'))
+
+def but_etype_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return but_etype(cls.etype)(cls, req, rset, row, col)
+but_etype_selector = deprecated_function(but_etype_selector)
+
+@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
+etype_rtype_selector = deprecated_function(etype_rtype_selector)
+
+#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 = deprecated_function(searchstate_accept)
+searchstate_accept_one = deprecated_function(searchstate_accept_one)
+
+
+def unbind_method(selector):
+ def new_selector(registered):
+ # get the unbound method
+ if hasattr(registered, 'im_func'):
+ registered = registered.im_func
+ # don't rebind since it will be done automatically during
+ # the assignment, inside the destination class body
+ return selector(registered)
+ new_selector.__name__ = selector.__name__
+ return new_selector
+
+
+def deprecate(registered, msg):
+ # get the unbound method
+ if hasattr(registered, 'im_func'):
+ registered = registered.im_func
+ def _deprecate(cls, vreg):
+ warn(msg, DeprecationWarning)
+ return registered(cls, vreg)
+ return _deprecate
+
+@unbind_method
+def require_group_compat(registered):
+ def plug_selector(cls, vreg):
+ cls = registered(cls, vreg)
+ if getattr(cls, 'require_groups', None):
+ warn('use "match_user_groups(group1, group2)" instead of using require_groups',
+ DeprecationWarning)
+ cls.__selectors__ += (match_user_groups(cls.require_groups),)
+ return cls
+ return plug_selector
+
+@unbind_method
+def accepts_compat(registered):
+ def plug_selector(cls, vreg):
+ cls = registered(cls, vreg)
+ if getattr(cls, 'accepts', None):
+ warn('use "match_user_groups(group1, group2)" instead of using require_groups',
+ DeprecationWarning)
+ cls.__selectors__ += (implements(*cls.accepts),)
+ return cls
+ return plug_selector
+
+@unbind_method
+def condition_compat(registered):
+ def plug_selector(cls, vreg):
+ cls = registered(cls, vreg)
+ if getattr(cls, 'condition', None):
+ warn('use "use rql_condition(expression)" instead of using condition',
+ DeprecationWarning)
+ cls.__selectors__ += (rql_condition(cls.condition),)
+ return cls
+ return plug_selector
+
+@unbind_method
+def has_relation_compat(registered):
+ def plug_selector(cls, vreg):
+ cls = registered(cls, vreg)
+ if getattr(cls, 'type', None):
+ warn('use relation_possible selector instead of using etype_rtype',
+ DeprecationWarning)
+ cls.__selectors__ += (relation_possible(cls.rtype, role(cls),
+ getattr(cls, 'etype', None),
+ action=getattr(cls, 'require_permission', 'read')))
+ return cls
+ return plug_selector
--- a/server/repository.py Tue Feb 17 16:20:53 2009 +0100
+++ b/server/repository.py Tue Feb 17 16:25:20 2009 +0100
@@ -491,6 +491,9 @@
try:
if session.execute('EUser X WHERE X login %(login)s', {'login': login}):
return False
+ if session.execute('EUser X WHERE X use_email C, C address %(login)s',
+ {'login': login}):
+ return False
# we have to create the user
user = self.vreg.etype_class('EUser')(session, None)
if isinstance(password, unicode):
@@ -502,6 +505,11 @@
self.glob_add_entity(session, user)
session.execute('SET X in_group G WHERE X eid %(x)s, G name "users"',
{'x': user.eid})
+ # FIXME this does not work yet
+ if '@' in login:
+ session.execute('INSERT EmailAddress X: X address "%(login)s", '
+ 'U primary_email X, U use_email X WHERE U login "%(login)s"',
+ {'login':login})
session.commit()
finally:
session.close()
--- a/server/test/data/schema/Affaire.py Tue Feb 17 16:20:53 2009 +0100
+++ b/server/test/data/schema/Affaire.py Tue Feb 17 16:25:20 2009 +0100
@@ -1,6 +1,6 @@
from cubicweb.schema import format_constraint
-class Affaire(EntityType):
+class Affaire(WorkflowableEntityType):
permissions = {
'read': ('managers',
ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
@@ -13,9 +13,6 @@
constraints=[SizeConstraint(16)])
sujet = String(fulltextindexed=True,
constraints=[SizeConstraint(256)])
- in_state = SubjectRelation('State', cardinality='1*',
- constraints=[RQLConstraint('O state_of ET, ET name "Affaire"')],
- description=_('account state'))
descr_format = String(meta=True, internationalizable=True,
default='text/rest', constraints=[format_constraint])
descr = String(fulltextindexed=True,
@@ -23,8 +20,7 @@
duration = Int()
invoiced = Int()
-
- wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object')
+
depends_on = SubjectRelation('Affaire')
require_permission = SubjectRelation('EPermission')
--- a/sobjects/notification.py Tue Feb 17 16:20:53 2009 +0100
+++ b/sobjects/notification.py Tue Feb 17 16:25:20 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/sobjects/supervising.py Tue Feb 17 16:20:53 2009 +0100
+++ b/sobjects/supervising.py Tue Feb 17 16:25:20 2009 +0100
@@ -2,13 +2,14 @@
: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 UnknownEid
-from cubicweb.common.view import ComponentMixIn, StartupView
+from cubicweb.common.appobject import ComponentMixIn
+from cubicweb.common.view import StartupView
from cubicweb.common.mail import format_mail
from cubicweb.server.hooksmanager import Hook
from cubicweb.server.hookhelper import SendMailOp
--- a/test/unittest_rset.py Tue Feb 17 16:20:53 2009 +0100
+++ b/test/unittest_rset.py Tue Feb 17 16:25:20 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_schema.py Tue Feb 17 16:20:53 2009 +0100
+++ b/test/unittest_schema.py Tue Feb 17 16:25:20 2009 +0100
@@ -134,7 +134,6 @@
self.assertListEquals([basename(f) for f in schema_files], ['Bookmark.py'])
def test_knownValues_load_schema(self):
- """read an url and return a Schema instance"""
schema = loader.load(config)
self.assert_(isinstance(schema, CubicWebSchema))
self.assertEquals(schema.name, 'data')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_selectors.py Tue Feb 17 16:25:20 2009 +0100
@@ -0,0 +1,80 @@
+"""unit tests for selectors mechanism
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.vregistry import Selector, AndSelector, OrSelector
+
+class _1_(Selector):
+ def __call__(self, *args, **kwargs):
+ return 1
+
+class _0_(Selector):
+ def __call__(self, *args, **kwargs):
+ return 0
+
+def _2_(*args, **kwargs):
+ return 2
+
+
+class SelectorsTC(TestCase):
+ def test_basic_and(self):
+ selector = _1_() & _1_()
+ self.assertEquals(selector(None), 2)
+ selector = _1_() & _0_()
+ self.assertEquals(selector(None), 0)
+ selector = _0_() & _1_()
+ self.assertEquals(selector(None), 0)
+
+ def test_basic_or(self):
+ selector = _1_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _1_() | _0_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _0_()
+ self.assertEquals(selector(None), 0)
+
+ def test_selector_and_function(self):
+ selector = _1_() & _2_
+ self.assertEquals(selector(None), 3)
+ selector = _2_ & _1_()
+ self.assertEquals(selector(None), 3)
+
+ def test_three_and(self):
+ selector = _1_() & _1_() & _1_()
+ self.assertEquals(selector(None), 3)
+ selector = _1_() & _0_() & _1_()
+ self.assertEquals(selector(None), 0)
+ selector = _0_() & _1_() & _1_()
+ self.assertEquals(selector(None), 0)
+
+ def test_three_or(self):
+ selector = _1_() | _1_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _1_() | _0_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _1_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _0_() | _0_()
+ self.assertEquals(selector(None), 0)
+
+ def test_composition(self):
+ selector = (_1_() & _1_()) & (_1_() & _1_())
+ self.failUnless(isinstance(selector, AndSelector))
+ self.assertEquals(len(selector.selectors), 4)
+ self.assertEquals(selector(None), 4)
+ selector = (_1_() & _0_()) | (_1_() & _1_())
+ self.failUnless(isinstance(selector, OrSelector))
+ self.assertEquals(len(selector.selectors), 2)
+ self.assertEquals(selector(None), 2)
+
+
+if __name__ == '__main__':
+ unittest_main()
+
--- a/test/unittest_vregistry.py Tue Feb 17 16:20:53 2009 +0100
+++ b/test/unittest_vregistry.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/vregistry.py Tue Feb 17 16:25:20 2009 +0100
@@ -234,7 +234,9 @@
cls = registerer.do_it_yourself(vobjects)
#_kicked |= registerer.kicked
if cls:
- vobject = cls.registered(self)
+ # registered() is technically a classmethod but is not declared
+ # as such because we need to compose registered in some cases
+ vobject = cls.registered.im_func(cls, self)
try:
vname = vobject.__name__
except AttributeError:
@@ -513,7 +515,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 +529,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,5 +545,70 @@
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 ``|``
+
+ This class is only here to give access to binary operators, the
+ selector logic itself should be implemented in the __call__ method
+ """
+ def __init__(self, *selectors):
+ self.selectors = self.merge_selectors(selectors)
+
+ @classmethod
+ def merge_selectors(cls, selectors):
+ """merge selectors when possible :
+
+ AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4))
+ ==> AndSelector(sel1, sel2, sel3, sel4)
+ """
+ merged_selectors = []
+ for selector in selectors:
+ if isinstance(selector, cls):
+ merged_selectors += selector.selectors
+ else:
+ merged_selectors.append(selector)
+ return merged_selectors
+
+ def __and__(self, other):
+ return AndSelector(self, other)
+
+ def __or__(self, other):
+ return OrSelector(self, other)
+
+ __ror__ = __or__ # for cases like (function | selector)
+ __rand__ = __and__ # for cases like (function & selector)
+ # XXX (function | function) or (function & function) not managed yet
+
+ def __call__(self, cls, *args, **kwargs):
+ return NotImplementedError("selector %s must implement its logic "
+ "in its __call__ method" % self.__class__.__name__)
+
+
+class AndSelector(Selector):
+ """and-chained selectors (formerly known as chainall)"""
+ def __call__(self, cls, *args, **kwargs):
+ score = 0
+ for selector in self.selectors:
+ partscore = selector(cls, *args, **kwargs)
+ if not partscore:
+ return 0
+ score += partscore
+ return score
+
+
+class OrSelector(Selector):
+ """or-chained selectors (formerly known as chainfirst)"""
+ def __call__(self, cls, *args, **kwargs):
+ for selector in self.selectors:
+ partscore = selector(cls, *args, **kwargs)
+ if partscore:
+ return partscore
+ return 0
--- a/web/action.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/action.py Tue Feb 17 16:25:20 2009 +0100
@@ -1,17 +1,20 @@
"""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 logilab.common.deprecation import class_moved
+
+from cubicweb import role, target
+from cubicweb.selectors import (relation_possible, match_search_state,
+ one_line_rset, may_add_relation, yes,
+ accepts_compat, condition_compat, deprecate)
from cubicweb.common.appobject import AppRsetObject
from cubicweb.common.registerers import action_registerer
-from cubicweb.common.selectors import add_etype_selector, \
- match_search_state, searchstate_accept_one, \
- searchstate_accept_one_but_etype
-
+
_ = unicode
@@ -21,9 +24,8 @@
"""
__registry__ = 'actions'
__registerer__ = action_registerer
- __selectors__ = (match_search_state,)
- # by default actions don't appear in link search mode
- search_states = ('normal',)
+ __selectors__ = (yes,)
+
property_defs = {
'visible': dict(type='Boolean', default=True,
help=_('display the action or not')),
@@ -37,53 +39,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 +49,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.
@@ -111,111 +67,37 @@
return self._path
-class AddEntityAction(Action):
- """link to the entity creation form. Concrete class must set .etype and
- may override .vid
- """
- __selectors__ = (add_etype_selector, match_search_state)
- vid = 'creation'
- etype = None
-
- def url(self):
- return self.build_url(vid=self.vid, etype=self.etype)
-
-
-class EntityAction(Action):
- """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
-
- @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
- 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
+ def my_selector(cls, req, rset, row=None, col=0, **kwargs):
+ selector = (match_search_state('normal') & one_line_rset
+ & relation_possible(cls.rtype, role(cls), cls.etype,
+ action='add')
+ & may_add_relation(cls.rtype, role(cls)))
+ return selector(cls, req, rset, row, col, **kwargs)
- @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
-
+ __selectors__ = (my_selector,)
+ registered = accepts_compat(Action.registered.im_func)
+
+ category = 'addrelated'
+
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!
__redirectvid=self.req.form.get('__redirectvid', ''))
-
-class LinkToEntityAction2(LinkToEntityAction):
- """LinkToEntity action where the action is not usable on the same
- entity's type as the one refered by the .etype attribute
+class EntityAction(Action):
+ """DEPRECATED / BACKWARD COMPAT
"""
- __selectors__ = (searchstate_accept_one_but_etype,)
+ registered = deprecate(accepts_compat(condition_compat(Action.registered)),
+ msg='EntityAction is deprecated, use Action with '
+ 'appropriate selectors')
--- a/web/box.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/box.py Tue Feb 17 16:25:20 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"
@@ -10,13 +10,13 @@
from logilab.mtconverter import html_escape
from cubicweb import Unauthorized, role as get_role
+from cubicweb.selectors import (one_line_rset, primary_view,
+ match_context_prop, has_related_entities,
+ accepts_compat, condition_compat)
from cubicweb.common.registerers import (
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)
+#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))
context = 'incontext'
- condition = None
def call(self, row=0, col=0, **kwargs):
"""classes inheriting from EntityBoxTemplate should define cell_call"""
--- a/web/component.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/component.py Tue Feb 17 16:25:20 2009 +0100
@@ -1,23 +1,27 @@
"""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"
-from cubicweb.common.appobject import Component, SingletonComponent
+from cubicweb.selectors import (
+ paginated_rset, one_line_rset, primary_view, match_context_prop,
+ condition_compat, accepts_compat, has_relation_compat)
+from cubicweb.common.appobject import Component, SingletonComponent, ComponentMixIn
from cubicweb.common.utils import merge_dicts
-from cubicweb.common.view import VComponent, SingletonVComponent
+from cubicweb.common.view import View
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)
from cubicweb.common.uilib import html_escape
_ = unicode
+class VComponent(ComponentMixIn, View):
+ property_defs = {
+ _('visible'): dict(type='Boolean', default=True,
+ help=_('display the box or not')),
+ }
class EntityVComponent(VComponent):
"""abstract base class for additinal components displayed in content
@@ -32,10 +36,8 @@
__registry__ = 'contentnavigation'
__registerer__ = action_registerer
- __selectors__ = (one_line_rset, primary_view,
- match_context_prop, etype_rtype_selector,
- has_relation, accept,
- rql_condition)
+ __selectors__ = (one_line_rset, primary_view, match_context_prop,)
+ registered = accepts_compat(has_relation_compat(condition_compat(View.registered.im_func)))
property_defs = {
_('visible'): dict(type='Boolean', default=True,
@@ -51,9 +53,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)
@@ -62,10 +62,11 @@
raise NotImplementedError()
-class NavigationComponent(VComponent):
+class NavigationComponent(ComponentMixIn, View):
"""abstract base class for navigation components"""
+ id = 'navigation'
__selectors__ = (paginated_rset,)
- id = 'navigation'
+
page_size_property = 'navigation.page-size'
start_param = '__start'
stop_param = '__stop'
@@ -151,14 +152,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)
vid = 'list'
def rql(self):
- """override this method if you want to use a custom rql query.
- """
+ """override this method if you want to use a custom rql query"""
return None
def cell_call(self, row, col, view=None):
--- a/web/controller.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/controller.py Tue Feb 17 16:25:20 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/data/cubicweb.ajax.js Tue Feb 17 16:20:53 2009 +0100
+++ b/web/data/cubicweb.ajax.js Tue Feb 17 16:25:20 2009 +0100
@@ -9,7 +9,18 @@
var JSON_BASE_URL = baseuri() + 'json?';
-function postAjaxLoad(node) {
+/*
+ * inspect dom response, search for a <div class="ajaxHtmlHead"> node and
+ * put its content into the real document's head.
+ * This enables dynamic css and js loading and is used by replacePageChunk
+ */
+function loadAjaxHtmlHead(node) {
+ jQuery(node).find('div.ajaxHtmlHead').appendTo(jQuery('head'));
+}
+
+function postAjaxLoad(node, req) {
+ // addStylesheets(evalJSON(req.getResponseHeader('X-Cubicweb-Stylesheets') || '[]'));
+ loadAjaxHtmlHead(node);
// find sortable tables if there are some
if (typeof(Sortable) != 'undefined') {
Sortable.sortTables(node);
@@ -28,23 +39,18 @@
// cubicweb loadxhtml plugin to make jquery handle xhtml response
jQuery.fn.loadxhtml = function(url, data, reqtype, mode) {
- var ajax = null;
- if (reqtype == 'post') {
- ajax = jQuery.post;
- } else {
- ajax = jQuery.get;
- }
if (this.size() > 1) {
log('loadxhtml was called with more than one element');
}
+ var node = this.get(0); // only consider the first element
mode = mode || 'replace';
var callback = null;
if (data && data.callback) {
callback = data.callback;
delete data.callback;
}
- var node = this.get(0); // only consider the first element
- ajax(url, data, function(response) {
+ var deferred = loadJSON(url, data, reqtype);
+ deferred.addCallback(function(response, req) {
var domnode = getDomFromResponse(response);
if (mode == 'swap') {
var origId = node.id;
@@ -57,7 +63,7 @@
} else if (mode == 'append') {
jQuery(node).append(domnode);
}
- postAjaxLoad(node);
+ postAjaxLoad(node, req);
while (jQuery.isFunction(callback)) {
callback = callback.apply(this, [domnode]);
}
--- a/web/test/unittest_viewselector.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/test/unittest_viewselector.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/__init__.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/actions.py Tue Feb 17 16:25:20 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/basecomponents.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/basecomponents.py Tue Feb 17 16:25:20 2009 +0100
@@ -5,7 +5,7 @@
* the workflow history section for workflowable objects
: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"
@@ -20,13 +20,13 @@
match_form_params)
from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink
-from cubicweb.web.component import (VComponent, SingletonVComponent, EntityVComponent,
+from cubicweb.web.component import (VComponent, EntityVComponent,
RelatedObjectsVComponent)
_ = unicode
-class RQLInputForm(SingletonVComponent):
+class RQLInputForm(VComponent):
"""build the rql input form, usually displayed in the header"""
id = 'rqlinput'
visible = False
@@ -55,7 +55,7 @@
self.w(u'</form></div>')
-class ApplLogo(SingletonVComponent):
+class ApplLogo(VComponent):
"""build the application logo, usually displayed in the header"""
id = 'logo'
site_wide = True # don't want user to hide this component using an eproperty
@@ -64,7 +64,7 @@
% (self.req.base_url(), self.req.external_resource('LOGO')))
-class ApplHelp(SingletonVComponent):
+class ApplHelp(VComponent):
"""build the help button, usually displayed in the header"""
id = 'help'
def call(self):
@@ -73,7 +73,7 @@
self.req._(u'help'),))
-class UserLink(SingletonVComponent):
+class UserLink(VComponent):
"""if the user is the anonymous user, build a link to login
else a link to the connected user object with a loggout link
"""
@@ -104,13 +104,17 @@
self.w(self.req._('anonymous'))
self.w(u''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
% (self.req._('i18n_login_popup')))
+ # FIXME maybe have an other option to explicitely authorise registration
+ if self.config['anonymous-user']:
+ self.w(u''' [<a class="logout" href="?vid=register">%s</a>]'''
+ % (self.req._('i18n_register_user')))
else:
self.w(self.req._('anonymous'))
self.w(u' [<a class="logout" href="%s">%s</a>]'
% (self.build_url('login'), self.req._('login')))
-class ApplicationMessage(SingletonVComponent):
+class ApplicationMessage(VComponent):
"""display application's messages given using the __message parameter
into a special div section
"""
@@ -165,7 +169,7 @@
displaycols=displaycols, headers=headers)
-class ApplicationName(SingletonVComponent):
+class ApplicationName(VComponent):
"""display the application name"""
id = 'appliname'
@@ -186,7 +190,7 @@
help = _('contentnavigation_seealso_description')
-class EtypeRestrictionComponent(SingletonVComponent):
+class EtypeRestrictionComponent(VComponent):
"""displays the list of entity types contained in the resultset
to be able to filter accordingly.
"""
@@ -238,14 +242,14 @@
class RSSFeedURL(VComponent):
id = 'rss_feed_url'
- __selectors__ = (non_final_entity,)
+ __selectors__ = (non_final_entity(),)
def feed_url(self):
return self.build_url(rql=self.limited_rql(), vid='rss')
class RSSEntityFeedURL(VComponent):
id = 'rss_feed_url'
- __selectors__ = (non_final_entity, one_line_rset)
+ __selectors__ = (non_final_entity(), one_line_rset)
def feed_url(self):
return self.entity(0, 0).rss_feed_url()
--- a/web/views/basecontrollers.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/basecontrollers.py Tue Feb 17 16:25:20 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
@@ -211,14 +211,14 @@
self.req.set_content_type(content_type)
return xmlize(data)
return data
-
+
def html_exec(self, rset=None):
- """html mode: execute query and return the view as HTML"""
+ # XXX try to use the page-content template
req = self.req
rql = req.form.get('rql')
if rset is None and rql:
rset = self._exec(rql)
-
+
vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
try:
view = self.vreg.select_view(vid, req, rset)
@@ -239,6 +239,10 @@
if divid == 'pageContent':
stream.write(u'<div id="contentmain">')
view.dispatch()
+ extresources = req.html_headers.getvalue(skiphead=True)
+ stream.write(u'<div class="ajaxHtmlHead">\n') # XXX use a widget ?
+ stream.write(extresources)
+ stream.write(u'</div>\n')
if req.form.get('paginate') and divid == 'pageContent':
stream.write(u'</div></div>')
source = stream.getvalue()
@@ -462,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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/baseforms.py Tue Feb 17 16:25:20 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/basetemplates.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/basetemplates.py Tue Feb 17 16:25:20 2009 +0100
@@ -19,9 +19,8 @@
# main templates ##############################################################
+class LogInOutTemplate(MainTemplate):
-class LogInOutTemplate(MainTemplate):
-
def call(self):
self.set_request_content_type()
w = self.w
@@ -43,7 +42,7 @@
w(u'\n'.join(additional_headers) + u'\n')
self.template('htmlheader', rset=self.rset)
w(u'<title>%s</title>\n' % html_escape(page_title))
-
+
class LogInTemplate(LogInOutTemplate):
id = 'login'
@@ -51,7 +50,7 @@
def content(self, w):
self.template('logform', rset=self.rset, id='loginBox', klass='')
-
+
class LoggedOutTemplate(LogInOutTemplate):
id = 'loggedout'
@@ -66,10 +65,10 @@
html_escape(indexurl),
self.req._('go back to the index page')))
-
+
class TheMainTemplate(MainTemplate):
"""default main template :
-
+
- call header / footer templates
- build result set
- guess and call an appropriate view through the view manager
@@ -112,7 +111,7 @@
vid = vid_from_rset(req, rset, self.schema)
view = self.vreg.select_view(vid, req, rset)
return view, rset
-
+
def call(self):
view, rset = self._select_view_and_rset()
req = self.req
@@ -123,8 +122,7 @@
req.update_breadcrumbs()
view.set_http_cache_headers()
req.validate_cache()
- with_templates = not view.binary and view.templatable and \
- not req.form.has_key('__notemplate')
+ with_templates = self.with_templates(view)
if not with_templates:
view.set_request_content_type()
self.set_stream(templatable=False)
@@ -132,17 +130,14 @@
self.set_request_content_type()
content_type = self.content_type
self.template_header(content_type, view)
- if view.binary:
- # have to replace our unicode stream using view's binary stream
- view.dispatch()
- assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream
- self._stream = view._stream
- else:
- view.dispatch(w=self.w)
+ self.template('page-content', view=view, rset=rset)
if with_templates:
self.template_footer(view)
-
+ def with_templates(self, view):
+ return (not view.binary and view.templatable and
+ not self.req.form.has_key('__notemplate'))
+
def process_rql(self, rql):
"""execute rql if specified"""
if rql:
@@ -159,17 +154,7 @@
additional_headers = additional_headers or view.html_headers()
self.template_html_header(content_type, page_title, additional_headers)
self.template_body_header(view)
- # display entity type restriction component
- etypefilter = self.vreg.select_component('etypenavigation',
- self.req, self.rset)
- if etypefilter and etypefilter.propval('visible'):
- etypefilter.dispatch(w=self.w)
- self.nav_html = UStringIO()
- self.pagination(self.req, self.rset, self.nav_html.write,
- not (view and view.need_navigation))
- self.w(_(self.nav_html.getvalue()))
- self.w(u'<div id="contentmain">\n')
-
+
def template_html_header(self, content_type, page_title, additional_headers=()):
w = self.whead
lang = self.req.lang
@@ -196,15 +181,8 @@
if msgcomp:
msgcomp.dispatch(w=self.w)
self.content_header(view)
- w(u'<div id="pageContent">\n')
- vtitle = self.req.form.get('vtitle')
- if vtitle:
- w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
-
+
def template_footer(self, view=None):
- self.w(u'</div>\n') # close id=contentmain
- self.w(_(self.nav_html.getvalue()))
- self.w(u'</div>\n') # closes id=pageContent
self.content_footer(view)
self.w(u'</td>\n')
self.nav_column(view, 'right')
@@ -224,18 +202,55 @@
def content_header(self, view=None):
"""by default, display informal messages in content header"""
self.template('contentheader', rset=self.rset, view=view)
-
+
def content_footer(self, view=None):
self.template('contentfooter', rset=self.rset, view=view)
+class PageContentTemplate(TheMainTemplate):
+ id = 'page-content'
+
+ def call(self, view=None, rset=None):
+ self.req.set_header('x-cubicweb-css', 'a.css;b.css')
+ if view is None:
+ view, rset = self._select_view_and_rset()
+ with_templates = self.with_templates(view)
+ w = self.w
+ if with_templates:
+ w(u'<div id="pageContent">\n')
+ vtitle = self.req.form.get('vtitle')
+ if vtitle:
+ w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
+ # display entity type restriction component
+ etypefilter = self.vreg.select_component('etypenavigation',
+ self.req, self.rset)
+ if etypefilter and etypefilter.propval('visible'):
+ etypefilter.dispatch(w=w)
+ self.nav_html = UStringIO()
+ self.pagination(self.req, self.rset, self.nav_html.write,
+ not (view and view.need_navigation))
+ w(_(self.nav_html.getvalue()))
+ w(u'<div id="contentmain">\n')
+ if view.binary:
+ # have to replace our unicode stream using view's binary stream
+ view.dispatch()
+ assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream
+ self._stream = view._stream
+ else:
+ view.dispatch(w=w)
+ if with_templates:
+ w(u'</div>\n') # close id=contentmain
+ w(_(self.nav_html.getvalue()))
+ w(u'</div>\n') # closes id=pageContent
+
+
class ErrorTemplate(TheMainTemplate):
"""fallback template if an internal error occured during displaying the
main template. This template may be called for authentication error,
which means that req.cnx and req.user may not be set.
"""
id = 'error'
-
+
def call(self):
"""display an unexpected error"""
self.set_request_content_type()
@@ -245,7 +260,7 @@
[NOINDEX, NOFOLLOW])
view.dispatch(w=self.w)
self.template_footer(view)
-
+
def template_header(self, content_type, view=None, page_title='', additional_headers=()):
w = self.whead
lang = self.req.lang
@@ -264,7 +279,7 @@
class SimpleMainTemplate(TheMainTemplate):
id = 'main-no-top'
-
+
def template_header(self, content_type, view=None, page_title='', additional_headers=()):
page_title = page_title or view.page_title()
additional_headers = additional_headers or view.html_headers()
@@ -295,7 +310,7 @@
vtitle = self.req.form.get('vtitle')
if vtitle:
w(u'<h1 class="vtitle">%s</h1>' % html_escape(vtitle))
-
+
def topleft_header(self):
self.w(u'<table id="header"><tr>\n')
self.w(u'<td>')
@@ -308,7 +323,7 @@
class HTMLHeader(Template):
"""default html headers"""
id = 'htmlheader'
-
+
def call(self, **kwargs):
self.favicon()
self.stylesheets()
@@ -320,7 +335,7 @@
favicon = self.req.external_resource('FAVICON', None)
if favicon:
self.whead(u'<link rel="shortcut icon" href="%s"/>\n' % favicon)
-
+
def stylesheets(self):
req = self.req
add_css = req.add_css
@@ -330,11 +345,11 @@
add_css(css, u'print', localfile=False)
for css in req.external_resource('IE_STYLESHEETS'):
add_css(css, localfile=False, ieonly=True)
-
+
def javascripts(self):
for jscript in self.req.external_resource('JAVASCRIPTS'):
self.req.add_js(jscript, localfile=False)
-
+
def alternates(self):
urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
if urlgetter is not None:
@@ -352,7 +367,7 @@
class HTMLPageHeader(Template):
"""default html page header"""
id = 'header'
-
+
def call(self, view, **kwargs):
self.main_header(view)
self.w(u'''
@@ -361,7 +376,7 @@
self.w(u'''
</div>
''')
-
+
def main_header(self, view):
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
@@ -392,7 +407,7 @@
self.w(u'</tr></table>\n')
self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
title=False, message=False)
-
+
def state_header(self):
state = self.req.search_state
if state[0] == 'normal':
@@ -402,7 +417,7 @@
msg = ' '.join((_("searching for"),
display_name(self.req, state[1][3]),
_("to associate with"), value,
- _("by relation"), '"',
+ _("by relation"), '"',
display_name(self.req, state[1][2], state[1][0]),
'"'))
return self.w(u'<div class="stateMessage">%s</div>' % msg)
@@ -413,7 +428,7 @@
"""default html page footer: include logo if any, and close the HTML body
"""
id = 'footer'
-
+
def call(self, **kwargs):
req = self.req
self.w(u'<div class="footer">')
@@ -434,7 +449,7 @@
* include selectable content navigation components
"""
id = 'contentheader'
-
+
def call(self, view, **kwargs):
"""by default, display informal messages in content header"""
components = self.vreg.possible_vobjects('contentnavigation',
@@ -452,7 +467,7 @@
components
"""
id = 'contentfooter'
-
+
def call(self, view, **kwargs):
components = self.vreg.possible_vobjects('contentnavigation',
self.req, self.rset,
@@ -474,7 +489,7 @@
if title:
self.w(u'<div id="loginTitle">%s</div>'
% self.req.property_value('ui.site-title'))
- self.w(u'<div id="loginContent">\n')
+ self.w(u'<div id="loginContent">\n')
if message:
self.display_message()
@@ -490,14 +505,14 @@
message = self.req.message
if message:
self.w(u'<div class="simpleMessage">%s</div>\n' % message)
-
+
def login_form(self, id):
_ = self.req._
self.w(u'<form method="post" action="%s" id="login_form">\n'
% html_escape(login_form_url(self.config, self.req)))
self.w(u'<table>\n')
self.w(u'<tr>\n')
- self.w(u'<td><label for="__login">%s</label></td>' % _('login'))
+ self.w(u'<td><label for="__login">%s</label></td>' % _('login or email'))
self.w(u'<td><input name="__login" id="__login" class="data" type="text" /></td>')
self.w(u'</tr><tr>\n')
self.w(u'<td><label for="__password" >%s</label></td>' % _('password'))
@@ -509,7 +524,7 @@
self.w(u'</form>\n')
self.req.html_headers.add_onload('jQuery("#__login:visible").focus()')
-
+
def login_form_url(config, req):
if req.https:
return req.url()
--- a/web/views/baseviews.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/baseviews.py Tue Feb 17 16:25:20 2009 +0100
@@ -22,14 +22,13 @@
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,
- match_form_params, accept_rset)
+from cubicweb.selectors import (yes, empty_rset, nonempty_rset, one_line_rset,
+ non_final_entity, match_search_state, match_form_params)
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 +41,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):
@@ -358,7 +358,7 @@
id = 'text'
title = _('text')
content_type = 'text/plain'
- accepts = 'Any',
+
def call(self, **kwargs):
"""the view is called for an entire result set, by default loop
other rows of the result set and call the same view on the
@@ -383,7 +383,6 @@
class MetaDataView(EntityView):
"""paragraph view of some metadata"""
id = 'metadata'
- accepts = 'Any',
show_eid = True
def cell_call(self, row, col):
@@ -447,13 +446,13 @@
class NotClickableInContextView(EntityView):
id = 'incontext'
- accepts = ('State',)
+
def cell_call(self, row, col):
self.w(html_escape(self.view('textincontext', self.rset, row=row, col=col)))
## class NotClickableOutOfContextView(EntityView):
## id = 'outofcontext'
-## accepts = ('State',)
+
## def cell_call(self, row, col):
## self.w(html_escape(self.view('textoutofcontext', self.rset, row=row)))
@@ -554,7 +553,6 @@
class TreeItemView(ListItemView):
- accepts = ('Any',)
id = 'treeitem'
def cell_call(self, row, col):
@@ -792,10 +790,10 @@
to search for something to link to the edited eid
"""
id = 'search-associate'
+ __selectors__ = (one_line_rset, match_search_state('linksearch'),
+ non_final_entity())
+
title = _('search for association')
- __selectors__ = (one_line_rset, match_search_state, accept)
- accepts = ('Any',)
- search_states = ('linksearch',)
def cell_call(self, row, col):
rset, vid, divid, paginate = self.filter_box_context_info()
@@ -826,7 +824,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'),
@@ -952,7 +950,6 @@
"""
id = 'tsearch'
-
def cell_call(self, row, col, **kwargs):
entity = self.complete_entity(row, col)
self.w(entity.view('incontext'))
@@ -979,22 +976,6 @@
self.w(value.replace('\n', '<br/>'))
-class EntityRelationView(EntityView):
- accepts = ()
- vid = 'list'
-
- def cell_call(self, row, col):
- if self.target == 'object':
- role = 'subject'
- else:
- role = 'object'
- rset = self.rset.get_entity(row, col).related(self.rtype, role)
- self.w(u'<h1>%s</h1>' % self.req._(self.title).capitalize())
- self.w(u'<div class="mainInfo">')
- self.wview(self.vid, rset, 'noresult')
- self.w(u'</div>')
-
-
class TooltipView(OneLineView):
"""A entity view used in a tooltip"""
id = 'tooltip'
--- a/web/views/bookmark.py Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/bookmark.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/boxes.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/debug.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/embedding.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/euser.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/idownloadable.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/management.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/massmailing.py Tue Feb 17 16:25:20 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 Tue Feb 17 16:20:53 2009 +0100
+++ b/web/views/schemaentities.py Tue Feb 17 16:25:20 2009 +0100
@@ -8,6 +8,7 @@
from logilab.mtconverter import html_escape
+from cubicweb.selectors import implements
from cubicweb.schemaviewer import SchemaViewer
from cubicweb.common.uilib import ureport_as_html
from cubicweb.common.view import EntityView
@@ -109,10 +110,12 @@
self.w(u'</em>')
-from cubicweb.web.action import EntityAction
+from cubicweb.web.action import Action
-class ViewWorkflowAction(EntityAction):
+class ViewWorkflowAction(Action):
id = 'workflow'
+ __selectors__ = (implements('EEType'), )
+
category = 'mainactions'
title = _('view workflow')
accepts = ('EEType',)