--- a/__init__.py Tue Sep 11 22:32:01 2012 +0200
+++ b/__init__.py Fri Oct 12 15:38:58 2012 +0200
@@ -199,3 +199,26 @@
CW_EVENT_MANAGER.bind(event, func, *args, **kwargs)
return func
return _decorator
+
+
+from yams.schema import role_name as rname
+
+def validation_error(entity, errors, substitutions=None, i18nvalues=None):
+ """easy way to retrieve a :class:`cubicweb.ValidationError` for an entity or eid.
+
+ You may also have 2-tuple as error keys, :func:`yams.role_name` will be
+ called automatically for them.
+
+ Messages in errors **should not be translated yet**, though marked for
+ internationalization. You may give an additional substition dictionary that
+ will be used for interpolation after the translation.
+ """
+ if substitutions is None:
+ # set empty dict else translation won't be done for backward
+ # compatibility reason (see ValidationError.tr method)
+ substitutions = {}
+ for key in errors.keys():
+ if isinstance(key, tuple):
+ errors[rname(*key)] = errors.pop(key)
+ return ValidationError(getattr(entity, 'eid', entity), errors,
+ substitutions, i18nvalues)
--- a/_exceptions.py Tue Sep 11 22:32:01 2012 +0200
+++ b/_exceptions.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -19,7 +19,7 @@
__docformat__ = "restructuredtext en"
-from yams import ValidationError
+from yams import ValidationError as ValidationError
# abstract exceptions #########################################################
--- a/devtools/fake.py Tue Sep 11 22:32:01 2012 +0200
+++ b/devtools/fake.py Fri Oct 12 15:38:58 2012 +0200
@@ -155,6 +155,12 @@
def set_entity_cache(self, entity):
pass
+ def security_enabled(self, read=False, write=False):
+ class FakeCM(object):
+ def __enter__(self): pass
+ def __exit__(self, exctype, exc, traceback): pass
+ return FakeCM()
+
# for use with enabled_security context manager
read_security = write_security = True
def init_security(self, *args):
--- a/devtools/testlib.py Tue Sep 11 22:32:01 2012 +0200
+++ b/devtools/testlib.py Fri Oct 12 15:38:58 2012 +0200
@@ -47,7 +47,7 @@
from cubicweb import cwconfig, dbapi, devtools, web, server
from cubicweb.sobjects import notification
from cubicweb.web import Redirect, application
-from cubicweb.server.session import Session, security_enabled
+from cubicweb.server.session import Session
from cubicweb.server.hook import SendMailOp
from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
from cubicweb.devtools import BASE_URL, fake, htmlparser, DEFAULT_EMPTY_DB_ID
@@ -1050,7 +1050,7 @@
"""this method populates the database with `how_many` entities
of each possible type. It also inserts random relations between them
"""
- with security_enabled(self.session, read=False, write=False):
+ with self.session.security_enabled(read=False, write=False):
self._auto_populate(how_many)
def _auto_populate(self, how_many):
--- a/entities/test/unittest_wfobjs.py Tue Sep 11 22:32:01 2012 +0200
+++ b/entities/test/unittest_wfobjs.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -20,7 +20,6 @@
from cubicweb import ValidationError
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import security_enabled
def add_wf(self, etype, name=None, default=False):
@@ -126,8 +125,9 @@
self.assertEqual(trs[0].destination(None).name, u'deactivated')
# test a std user get no possible transition
cnx = self.login('member')
+ req = self.request()
# fetch the entity using the new session
- trs = list(cnx.user().cw_adapt_to('IWorkflowable').possible_transitions())
+ trs = list(req.user.cw_adapt_to('IWorkflowable').possible_transitions())
self.assertEqual(len(trs), 0)
cnx.close()
@@ -154,7 +154,7 @@
wf = add_wf(self, 'CWUser')
s = wf.add_state(u'foo', initial=True)
self.commit()
- with security_enabled(self.session, write=False):
+ with self.session.security_enabled(write=False):
with self.assertRaises(ValidationError) as cm:
self.session.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
{'x': self.user().eid, 's': s.eid})
@@ -173,7 +173,7 @@
def test_goback_transition(self):
req = self.request()
- wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
+ wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
asleep = wf.add_state('asleep')
wf.add_transition('rest', (wf.state_by_name('activated'),
wf.state_by_name('deactivated')),
@@ -212,7 +212,7 @@
req = self.request()
iworkflowable = req.entity_from_eid(self.member.eid).cw_adapt_to('IWorkflowable')
iworkflowable.fire_transition('deactivate')
- cnx.commit()
+ req.cu.commit()
with self.assertRaises(ValidationError) as cm:
iworkflowable.fire_transition('activate')
self.assertEqual(cm.exception.errors, {'by_transition-subject': "transition may not be fired"})
@@ -556,13 +556,12 @@
def setUp(self):
CubicWebTC.setUp(self)
- self.wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
- self.session.set_cnxset()
+ req = self.request()
+ self.wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
self.s_activated = self.wf.state_by_name('activated').eid
self.s_deactivated = self.wf.state_by_name('deactivated').eid
self.s_dummy = self.wf.add_state(u'dummy').eid
self.wf.add_transition(u'dummy', (self.s_deactivated,), self.s_dummy)
- req = self.request()
ueid = self.create_user(req, 'stduser', commit=False).eid
# test initial state is set
rset = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s',
--- a/hooks/integrity.py Tue Sep 11 22:32:01 2012 +0200
+++ b/hooks/integrity.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -20,12 +20,11 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from threading import Lock
-from yams.schema import role_name
-
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
RQLConstraint, RQLUniqueConstraint)
from cubicweb.predicates import is_instance
@@ -87,11 +86,11 @@
continue
if not session.execute(self.base_rql % rtype, {'x': eid}):
etype = session.describe(eid)[0]
- _ = session._
msg = _('at least one relation %(rtype)s is required on '
'%(etype)s (%(eid)s)')
- msg %= {'rtype': _(rtype), 'etype': _(etype), 'eid': eid}
- raise ValidationError(eid, {role_name(rtype, self.role): msg})
+ raise validation_error(eid, {(rtype, self.role): msg},
+ {'rtype': rtype, 'etype': etype, 'eid': eid},
+ ['rtype', 'etype'])
class _CheckSRelationOp(_CheckRequiredRelationOperation):
@@ -231,9 +230,9 @@
rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
rset = self._cw.execute(rql, {'val': val})
if rset and rset[0][0] != entity.eid:
- msg = self._cw._('the value "%s" is already used, use another one')
- qname = role_name(attr, 'subject')
- raise ValidationError(entity.eid, {qname: msg % val})
+ msg = _('the value "%s" is already used, use another one')
+ raise validation_error(entity, {(attr, 'subject'): msg},
+ (val,))
class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -246,15 +245,12 @@
def __call__(self):
entity = self.entity
if self.event == 'before_delete_entity' and entity.name == 'owners':
- msg = self._cw._('can\'t be deleted')
- raise ValidationError(entity.eid, {None: msg})
+ raise validation_error(entity, {None: _("can't be deleted")})
elif self.event == 'before_update_entity' \
and 'name' in entity.cw_edited:
oldname, newname = entity.cw_edited.oldnewvalue('name')
if oldname == 'owners' and newname != oldname:
- qname = role_name('name', 'subject')
- msg = self._cw._('can\'t be changed')
- raise ValidationError(entity.eid, {qname: msg})
+ raise validation_error(entity, {('name', 'subject'): _("can't be changed")})
class TidyHtmlFields(IntegrityHook):
--- a/hooks/syncschema.py Tue Sep 11 22:32:01 2012 +0200
+++ b/hooks/syncschema.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -24,6 +24,7 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from copy import copy
from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
@@ -31,7 +32,7 @@
from logilab.common.decorators import clear_cache
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.predicates import is_instance
from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
CONSTRAINTS, ETYPE_NAME_MAP, display_name)
@@ -127,10 +128,9 @@
if attr in ro_attrs:
origval, newval = entity.cw_edited.oldnewvalue(attr)
if newval != origval:
- errors[attr] = session._("can't change the %s attribute") % \
- display_name(session, attr)
+ errors[attr] = _("can't change this attribute")
if errors:
- raise ValidationError(entity.eid, errors)
+ raise validation_error(entity, errors)
class _MockEntity(object): # XXX use a named tuple with python 2.6
@@ -913,7 +913,7 @@
# final entities can't be deleted, don't care about that
name = self.entity.name
if name in CORE_TYPES:
- raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ raise validation_error(self.entity, {None: _("can't be deleted")})
# delete every entities of this type
if name not in ETYPE_NAME_MAP:
self._cw.execute('DELETE %s X' % name)
@@ -983,7 +983,7 @@
def __call__(self):
name = self.entity.name
if name in CORE_TYPES:
- raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ raise validation_error(self.entity, {None: _("can't be deleted")})
# delete relation definitions using this relation type
self._cw.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
{'x': self.entity.eid})
--- a/hooks/syncsession.py Tue Sep 11 22:32:01 2012 +0200
+++ b/hooks/syncsession.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,9 +18,9 @@
"""Core hooks: synchronize living session on persistent data changes"""
__docformat__ = "restructuredtext en"
+_ = unicode
-from yams.schema import role_name
-from cubicweb import UnknownProperty, ValidationError, BadConnectionId
+from cubicweb import UnknownProperty, BadConnectionId, validation_error
from cubicweb.predicates import is_instance
from cubicweb.server import hook
@@ -165,13 +165,11 @@
try:
value = session.vreg.typed_value(key, value)
except UnknownProperty:
- qname = role_name('pkey', 'subject')
- msg = session._('unknown property key %s') % key
- raise ValidationError(self.entity.eid, {qname: msg})
+ msg = _('unknown property key %s')
+ raise validation_error(self.entity, {('pkey', 'subject'): msg}, (key,))
except ValueError, ex:
- qname = role_name('value', 'subject')
- raise ValidationError(self.entity.eid,
- {qname: session._(str(ex))})
+ raise validation_error(self.entity,
+ {('value', 'subject'): str(ex)})
if not session.user.matching_groups('managers'):
session.add_relation(self.entity.eid, 'for_user', session.user.eid)
else:
@@ -196,8 +194,7 @@
except UnknownProperty:
return
except ValueError, ex:
- qname = role_name('value', 'subject')
- raise ValidationError(entity.eid, {qname: session._(str(ex))})
+ raise validation_error(entity, {('value', 'subject'): str(ex)})
if entity.for_user:
for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
_ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
@@ -237,10 +234,8 @@
key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
{'x': eidfrom})[0]
if session.vreg.property_info(key)['sitewide']:
- qname = role_name('for_user', 'subject')
- msg = session._("site-wide property can't be set for user")
- raise ValidationError(eidfrom,
- {qname: msg})
+ msg = _("site-wide property can't be set for user")
+ raise validation_error(eidfrom, {('for_user', 'subject'): msg})
for session_ in get_user_sessions(session.repo, self.eidto):
_ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
key=key, value=value)
--- a/hooks/syncsources.py Tue Sep 11 22:32:01 2012 +0200
+++ b/hooks/syncsources.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -17,12 +17,13 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""hooks for repository sources synchronization"""
+_ = unicode
+
from socket import gethostname
from logilab.common.decorators import clear_cache
-from yams.schema import role_name
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.predicates import is_instance
from cubicweb.server import SOURCE_TYPES, hook
@@ -46,12 +47,15 @@
try:
sourcecls = SOURCE_TYPES[self.entity.type]
except KeyError:
- msg = self._cw._('unknown source type')
- raise ValidationError(self.entity.eid,
- {role_name('type', 'subject'): msg})
- sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
- fail_if_unknown=not self._cw.vreg.config.repairing)
- SourceAddedOp(self._cw, entity=self.entity)
+ msg = _('Unknown source type')
+ raise validation_error(self.entity, {('type', 'subject'): msg})
+ # ignore creation of the system source done during database
+ # initialisation, as config for this source is in a file and handling
+ # is done separatly (no need for the operation either)
+ if self.entity.name != 'system':
+ sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
+ fail_if_unknown=not self._cw.vreg.config.repairing)
+ SourceAddedOp(self._cw, entity=self.entity)
class SourceRemovedOp(hook.Operation):
@@ -65,7 +69,8 @@
events = ('before_delete_entity',)
def __call__(self):
if self.entity.name == 'system':
- raise ValidationError(self.entity.eid, {None: 'cant remove system source'})
+ msg = _("You cannot remove the system source")
+ raise validation_error(self.entity, {None: msg})
SourceRemovedOp(self._cw, uri=self.entity.name)
@@ -116,11 +121,18 @@
__select__ = SourceHook.__select__ & is_instance('CWSource')
events = ('before_update_entity',)
def __call__(self):
- if 'config' in self.entity.cw_edited:
- SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
if 'name' in self.entity.cw_edited:
oldname, newname = self.entity.cw_edited.oldnewvalue('name')
+ if oldname == 'system':
+ msg = _("You cannot rename the system source")
+ raise validation_error(self.entity, {('name', 'subject'): msg})
SourceRenamedOp(self._cw, oldname=oldname, newname=newname)
+ if 'config' in self.entity.cw_edited:
+ if self.entity.name == 'system' and self.entity.config:
+ msg = _("Configuration of the system source goes to "
+ "the 'sources' file, not in the database")
+ raise validation_error(self.entity, {('config', 'subject'): msg})
+ SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
class SourceHostConfigUpdatedHook(SourceHook):
@@ -154,8 +166,8 @@
events = ('before_add_relation',)
def __call__(self):
if not self._cw.added_in_transaction(self.eidfrom):
- msg = self._cw._("can't change this relation")
- raise ValidationError(self.eidfrom, {self.rtype: msg})
+ msg = _("You can't change this relation")
+ raise validation_error(self.eidfrom, {self.rtype: msg})
class SourceMappingChangedOp(hook.DataOperationMixIn, hook.Operation):
--- a/hooks/test/unittest_hooks.py Tue Sep 11 22:32:01 2012 +0200
+++ b/hooks/test/unittest_hooks.py Fri Oct 12 15:38:58 2012 +0200
@@ -170,6 +170,7 @@
try:
self.execute('INSERT CWUser X: X login "admin"')
except ValidationError, ex:
+ ex.tr(unicode)
self.assertIsInstance(ex.entity, int)
self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
--- a/hooks/test/unittest_syncsession.py Tue Sep 11 22:32:01 2012 +0200
+++ b/hooks/test/unittest_syncsession.py Fri Oct 12 15:38:58 2012 +0200
@@ -31,9 +31,11 @@
def test_unexistant_cwproperty(self):
with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+ cm.exception.tr(unicode)
self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+ cm.exception.tr(unicode)
self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
def test_site_wide_cwproperty(self):
--- a/hooks/workflow.py Tue Sep 11 22:32:01 2012 +0200
+++ b/hooks/workflow.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,12 +18,12 @@
"""Core hooks: workflow related hooks"""
__docformat__ = "restructuredtext en"
+_ = unicode
from datetime import datetime
-from yams.schema import role_name
-from cubicweb import RepositoryError, ValidationError
+from cubicweb import RepositoryError, validation_error
from cubicweb.predicates import is_instance, adaptable
from cubicweb.server import hook
@@ -92,9 +92,8 @@
if mainwf.eid == self.wfeid:
deststate = mainwf.initial
if not deststate:
- qname = role_name('custom_workflow', 'subject')
- msg = session._('workflow has no initial state')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('workflow has no initial state')
+ raise validation_error(entity, {('custom_workflow', 'subject'): msg})
if mainwf.state_by_eid(iworkflowable.current_state.eid):
# nothing to do
return
@@ -119,9 +118,8 @@
outputs = set()
for ep in tr.subworkflow_exit:
if ep.subwf_state.eid in outputs:
- qname = role_name('subworkflow_exit', 'subject')
- msg = self.session._("can't have multiple exits on the same state")
- raise ValidationError(self.treid, {qname: msg})
+ msg = _("can't have multiple exits on the same state")
+ raise validation_error(self.treid, {('subworkflow_exit', 'subject'): msg})
outputs.add(ep.subwf_state.eid)
@@ -137,13 +135,12 @@
wftr = iworkflowable.subworkflow_input_transition()
if wftr is None:
# inconsistency detected
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.trinfo.eid, {'to_state': msg})
+ msg = _("state doesn't belong to entity's current workflow")
+ raise validation_error(self.trinfo, {('to_state', 'subject'): msg})
tostate = wftr.get_exit_point(forentity, trinfo.cw_attr_cache['to_state'])
if tostate is not None:
# reached an exit point
- msg = session._('exiting from subworkflow %s')
+ msg = _('exiting from subworkflow %s')
msg %= session._(iworkflowable.current_workflow.name)
session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
iworkflowable.change_state(tostate, msg, u'text/plain', tr=wftr)
@@ -186,9 +183,8 @@
try:
foreid = entity.cw_attr_cache['wf_info_for']
except KeyError:
- qname = role_name('wf_info_for', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('wf_info_for', 'subject'): msg})
forentity = session.entity_from_eid(foreid)
# see comment in the TrInfo entity definition
entity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for)
@@ -201,13 +197,13 @@
else:
wf = iworkflowable.current_workflow
if wf is None:
- msg = session._('related entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('related entity has no workflow set')
+ raise validation_error(entity, {None: msg})
# then check it has a state set
fromstate = iworkflowable.current_state
if fromstate is None:
- msg = session._('related entity has no state')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('related entity has no state')
+ raise validation_error(entity, {None: msg})
# True if we are coming back from subworkflow
swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
cowpowers = (session.user.is_in_group('managers')
@@ -219,47 +215,42 @@
# no transition set, check user is a manager and destination state
# is specified (and valid)
if not cowpowers:
- qname = role_name('by_transition', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
deststateeid = entity.cw_attr_cache.get('to_state')
if not deststateeid:
- qname = role_name('by_transition', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
deststate = wf.state_by_eid(deststateeid)
if deststate is None:
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow")
+ raise validation_error(entity, {('to_state', 'subject'): msg})
else:
# check transition is valid and allowed, unless we're coming back
# from subworkflow
tr = session.entity_from_eid(treid)
if swtr is None:
- qname = role_name('by_transition', 'subject')
+ qname = ('by_transition', 'subject')
if tr is None:
- msg = session._("transition doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition doesn't belong to entity's workflow")
+ raise validation_error(entity, {qname: msg})
if not tr.has_input_state(fromstate):
- msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
- 'tr': session._(tr.name), 'st': session._(fromstate.name)}
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition %(tr)s isn't allowed from %(st)s")
+ raise validation_error(entity, {qname: msg}, {
+ 'tr': tr.name, 'st': fromstate.name}, ['tr', 'st'])
if not tr.may_be_fired(foreid):
- msg = session._("transition may not be fired")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition may not be fired")
+ raise validation_error(entity, {qname: msg})
deststateeid = entity.cw_attr_cache.get('to_state')
if deststateeid is not None:
if not cowpowers and deststateeid != tr.destination(forentity).eid:
- qname = role_name('by_transition', 'subject')
- msg = session._("transition isn't allowed")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition isn't allowed")
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
if swtr is None:
deststate = session.entity_from_eid(deststateeid)
if not cowpowers and deststate is None:
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow")
+ raise validation_error(entity, {('to_state', 'subject'): msg})
else:
deststateeid = tr.destination(forentity).eid
# everything is ok, add missing information on the trinfo entity
@@ -307,20 +298,18 @@
iworkflowable = entity.cw_adapt_to('IWorkflowable')
mainwf = iworkflowable.main_workflow
if mainwf is None:
- msg = session._('entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('entity has no workflow set')
+ raise validation_error(entity, {None: msg})
for wf in mainwf.iter_workflows():
if wf.state_by_eid(self.eidto):
break
else:
- qname = role_name('in_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow. You may "
- "want to set a custom workflow for this entity first.")
- raise ValidationError(self.eidfrom, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow. You may "
+ "want to set a custom workflow for this entity first.")
+ raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
if iworkflowable.current_workflow and wf.eid != iworkflowable.current_workflow.eid:
- qname = role_name('in_state', 'subject')
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.eidfrom, {qname: msg})
+ msg = _("state doesn't belong to entity's current workflow")
+ raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
class SetModificationDateOnStateChange(WorkflowHook):
--- a/i18n/de.po Tue Sep 11 22:32:01 2012 +0200
+++ b/i18n/de.po Fri Oct 12 15:38:58 2012 +0200
@@ -2561,6 +2561,9 @@
msgid "foaf"
msgstr "FOAF"
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr "dem Link folgen"
--- a/i18n/en.po Tue Sep 11 22:32:01 2012 +0200
+++ b/i18n/en.po Fri Oct 12 15:38:58 2012 +0200
@@ -2508,6 +2508,9 @@
msgid "foaf"
msgstr ""
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr ""
--- a/i18n/es.po Tue Sep 11 22:32:01 2012 +0200
+++ b/i18n/es.po Fri Oct 12 15:38:58 2012 +0200
@@ -2603,6 +2603,9 @@
msgid "foaf"
msgstr "Amigo de un Amigo, FOAF"
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr "Seguir la liga"
--- a/i18n/fr.po Tue Sep 11 22:32:01 2012 +0200
+++ b/i18n/fr.po Fri Oct 12 15:38:58 2012 +0200
@@ -2611,6 +2611,9 @@
msgid "foaf"
msgstr "foaf"
+msgid "focus on this selection"
+msgstr "afficher cette sélection"
+
msgid "follow"
msgstr "suivre le lien"
@@ -4137,10 +4140,6 @@
msgid "there is no transaction #%s"
msgstr "Il n'y a pas de transaction #%s"
-#, python-format
-msgid "there is no transaction #%s"
-msgstr ""
-
msgid "this action is not reversible!"
msgstr ""
"Attention ! Cette opération va détruire les données de façon irréversible."
@@ -4696,6 +4695,9 @@
#~ msgid "day"
#~ msgstr "jour"
+#~ msgid "jump to selection"
+#~ msgstr "afficher cette sélection"
+
#~ msgid "log out first"
#~ msgstr "déconnecter vous d'abord"
--- a/server/__init__.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/__init__.py Fri Oct 12 15:38:58 2012 +0200
@@ -247,15 +247,13 @@
def initialize_schema(config, schema, mhandler, event='create'):
from cubicweb.server.schemaserial import serialize_schema
- from cubicweb.server.session import hooks_control
session = mhandler.session
cubes = config.cubes()
# deactivate every hooks but those responsible to set metadata
# so, NO INTEGRITY CHECKS are done, to have quicker db creation.
# Active integrity is kept else we may pb such as two default
# workflows for one entity type.
- with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata',
- 'activeintegrity'):
+ with session.deny_all_hooks_but('metadata', 'activeintegrity'):
# execute cubicweb's pre<event> script
mhandler.cmd_exec_event_script('pre%s' % event)
# execute cubes pre<event> script if any
--- a/server/checkintegrity.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/checkintegrity.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -32,7 +32,6 @@
from cubicweb.schema import PURE_VIRTUAL_RTYPES, VIRTUAL_RTYPES
from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.session import security_enabled
def notify_fixed(fix):
if fix:
@@ -394,7 +393,7 @@
# yo, launch checks
if checks:
eids_cache = {}
- with security_enabled(session, read=False, write=False): # ensure no read security
+ with session.security_enabled(read=False, write=False): # ensure no read security
for check in checks:
check_func = globals()['check_%s' % check]
check_func(repo.schema, session, eids_cache, fix=fix)
--- a/server/edition.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/edition.py Fri Oct 12 15:38:58 2012 +0200
@@ -143,8 +143,7 @@
for rtype in self]
try:
entity.e_schema.check(dict_protocol_catcher(entity),
- creation=creation, _=entity._cw._,
- relations=relations)
+ creation=creation, relations=relations)
except ValidationError, ex:
ex.entity = self.entity
raise
--- a/server/hook.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/hook.py Fri Oct 12 15:38:58 2012 +0200
@@ -197,14 +197,12 @@
~~~~~~~~~~~~~
It is sometimes convenient to explicitly enable or disable some hooks. For
-instance if you want to disable some integrity checking hook. This can be
+instance if you want to disable some integrity checking hook. This can be
controlled more finely through the `category` class attribute, which is a string
giving a category name. One can then uses the
-:class:`~cubicweb.server.session.hooks_control` context manager to explicitly
-enable or disable some categories.
-
-.. autoclass:: cubicweb.server.session.hooks_control
-
+:meth:`~cubicweb.server.session.Session.deny_all_hooks_but` and
+:meth:`~cubicweb.server.session.Session.allow_all_hooks_but` context managers to
+explicitly enable or disable some categories.
The existing categories are:
@@ -230,10 +228,8 @@
* ``bookmark``, bookmark entities handling hooks
-Nothing precludes one to invent new categories and use the
-:class:`~cubicweb.server.session.hooks_control` context manager to
-filter them in or out. Note that ending the transaction with commit()
-or rollback() will restore the hooks.
+Nothing precludes one to invent new categories and use existing mechanisms to
+filter them in or out.
Hooks specific predicates
@@ -268,7 +264,6 @@
from cubicweb.cwvreg import CWRegistry, CWRegistryStore
from cubicweb.predicates import ExpectedValuePredicate, is_instance
from cubicweb.appobject import AppObject
-from cubicweb.server.session import security_enabled
ENTITIES_HOOKS = set(('before_add_entity', 'after_add_entity',
'before_update_entity', 'after_update_entity',
@@ -326,11 +321,11 @@
pruned = self.get_pruned_hooks(session, event,
entities, eids_from_to, kwargs)
# by default, hooks are executed with security turned off
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
for _kwargs in _iter_kwargs(entities, eids_from_to, kwargs):
hooks = sorted(self.filtered_possible_objects(pruned, session, **_kwargs),
key=lambda x: x.order)
- with security_enabled(session, write=False):
+ with session.security_enabled(write=False):
for hook in hooks:
hook()
--- a/server/querier.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/querier.py Fri Oct 12 15:38:58 2012 +0200
@@ -42,7 +42,6 @@
from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
from cubicweb.server.edition import EditedEntity
-from cubicweb.server.session import security_enabled
ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
@@ -262,7 +261,7 @@
cached = True
else:
noinvariant = set()
- with security_enabled(self.session, read=False):
+ with self.session.security_enabled(read=False):
self._insert_security(union, noinvariant)
if key is not None:
self.session.transaction_data[key] = (union, self.args)
--- a/server/repository.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/repository.py Fri Oct 12 15:38:58 2012 +0200
@@ -56,8 +56,7 @@
RepositoryError, UniqueTogetherError, typed_eid, onevent)
from cubicweb import cwvreg, schema, server
from cubicweb.server import ShuttingDown, utils, hook, pool, querier, sources
-from cubicweb.server.session import Session, InternalSession, InternalManager, \
- security_enabled
+from cubicweb.server.session import Session, InternalSession, InternalManager
from cubicweb.server.ssplanner import EditedEntity
NO_CACHE_RELATIONS = set( [('owned_by', 'object'),
@@ -109,12 +108,12 @@
# * we don't want read permissions to be applied but we want delete
# permission to be checked
if card[0] in '1?':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
'NOT Y eid %%(y)s' % rtype,
{'x': eidfrom, 'y': eidto})
if card[1] in '1?':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
'NOT X eid %%(x)s' % rtype,
{'x': eidfrom, 'y': eidto})
@@ -1200,7 +1199,7 @@
source = self.sources_by_eid[scleanup]
# delete remaining relations: if user can delete the entity, he can
# delete all its relations without security checking
- with security_enabled(session, read=False, write=False):
+ with session.security_enabled(read=False, write=False):
eid = entity.eid
for rschema, _, role in entity.e_schema.relation_definitions():
rtype = rschema.type
@@ -1242,7 +1241,7 @@
source = self.sources_by_eid[scleanup]
# delete remaining relations: if user can delete the entity, he can
# delete all its relations without security checking
- with security_enabled(session, read=False, write=False):
+ with session.security_enabled(read=False, write=False):
in_eids = ','.join([str(_e.eid) for _e in entities])
for rschema, _, role in entities[0].e_schema.relation_definitions():
rtype = rschema.type
@@ -1355,7 +1354,7 @@
session.update_rel_cache_add(entity.eid, attr, value)
rdef = session.rtype_eids_rdef(attr, entity.eid, value)
if rdef.cardinality[1] in '1?' and activeintegrity:
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr,
{'x': entity.eid, 'y': value})
edited.set_defaults()
@@ -1541,7 +1540,7 @@
rdef = session.rtype_eids_rdef(rtype, subjeid, objeid)
card = rdef.cardinality
if card[0] in '?1':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
'NOT Y eid %%(y)s' % rtype,
{'x': subjeid, 'y': objeid})
@@ -1552,7 +1551,7 @@
continue
subjects[subjeid] = len(relations_by_rtype[rtype]) - 1
if card[1] in '?1':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
'NOT X eid %%(x)s' % rtype,
{'x': subjeid, 'y': objeid})
--- a/server/session.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/session.py Fri Oct 12 15:38:58 2012 +0200
@@ -108,6 +108,11 @@
with hooks_control(self.session, self.session.HOOKS_DENY_ALL, 'integrity'):
# ... do stuff with none but 'integrity' hooks activated
+
+ This is an internal api, you should rather use
+ :meth:`~cubicweb.server.session.Session.deny_all_hooks_but` or
+ :meth:`~cubicweb.server.session.Session.allow_all_hooks_but` session
+ methods.
"""
def __init__(self, session, mode, *categories):
self.session = session
@@ -217,6 +222,9 @@
:attr:`running_dbapi_query`, boolean flag telling if the executing query
is coming from a dbapi connection or is a query from within the repository
+
+ .. automethod:: cubicweb.server.session.deny_all_hooks_but
+ .. automethod:: cubicweb.server.session.all_all_hooks_but
"""
is_request = False
is_internal_session = False
@@ -462,7 +470,7 @@
DEFAULT_SECURITY = object() # evaluated to true by design
- def security_enabled(self, read=False, write=False):
+ def security_enabled(self, read=None, write=None):
return security_enabled(self, read=read, write=write)
def init_security(self, read, write):
@@ -1205,6 +1213,9 @@
return 'en'
return None
+ def prefered_language(self, language=None):
+ # mock CWUser.prefered_language, mainly for testing purpose
+ return self.property_value('ui.language')
from logging import getLogger
from cubicweb import set_log_methods
--- a/server/ssplanner.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/ssplanner.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -27,7 +27,6 @@
from cubicweb import QueryError, typed_eid
from cubicweb.schema import VIRTUAL_RTYPES
from cubicweb.rqlrewrite import add_types_restriction
-from cubicweb.server.session import security_enabled
from cubicweb.server.edition import EditedEntity
READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
@@ -87,7 +86,7 @@
# the generated select substep if not emited (eg nothing
# to be selected)
if checkread and eid not in neweids:
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
eschema(session.describe(eid)[0]).check_perm(
session, 'read', eid=eid)
eidconsts[lhs.variable] = eid
--- a/server/test/unittest_session.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/test/unittest_session.py Fri Oct 12 15:38:58 2012 +0200
@@ -18,7 +18,6 @@
from __future__ import with_statement
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import hooks_control
class InternalSessionTC(CubicWebTC):
@@ -36,7 +35,7 @@
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set())
self.assertEqual(len(session._tx_data), 1)
- with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata'):
+ with session.deny_all_hooks_but('metadata'):
self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
@@ -48,7 +47,7 @@
self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
- with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
+ with session.allow_all_hooks_but('integrity'):
self.assertEqual(session.hooks_mode, session.HOOKS_ALLOW_ALL)
self.assertEqual(session.disabled_hook_categories, set(('integrity',)))
self.assertEqual(session.enabled_hook_categories, set(('metadata',))) # not changed in such case
@@ -65,4 +64,5 @@
if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
unittest_main()
--- a/server/test/unittest_undo.py Tue Sep 11 22:32:01 2012 +0200
+++ b/server/test/unittest_undo.py Fri Oct 12 15:38:58 2012 +0200
@@ -228,6 +228,7 @@
"%s doesn't exist anymore." % g.eid])
with self.assertRaises(ValidationError) as cm:
self.commit()
+ cm.exception.tr(unicode)
self.assertEqual(cm.exception.entity, self.toto.eid)
self.assertEqual(cm.exception.errors,
{'in_group-subject': u'at least one relation in_group is '
--- a/test/unittest_migration.py Tue Sep 11 22:32:01 2012 +0200
+++ b/test/unittest_migration.py Fri Oct 12 15:38:58 2012 +0200
@@ -108,7 +108,13 @@
self.assertEqual(source['db-driver'], 'sqlite')
handler = get_test_db_handler(config)
handler.init_test_database()
-
+ handler.build_db_cache()
+ repo, cnx = handler.get_repo_and_cnx()
+ cu = cnx.cursor()
+ self.assertEqual(cu.execute('Any SN WHERE X is CWUser, X login "admin", X in_state S, S name SN').rows,
+ [['activated']])
+ cnx.close()
+ repo.shutdown()
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_schema.py Tue Sep 11 22:32:01 2012 +0200
+++ b/test/unittest_schema.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -350,8 +350,8 @@
class WorkflowShemaTC(CubicWebTC):
def test_trinfo_default_format(self):
- tr = self.session.user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
- self.assertEqual(tr.comment_format, 'text/plain')
+ tr = self.request().user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
+ self.assertEqual(tr.comment_format, 'text/plain')
if __name__ == '__main__':
unittest_main()
--- a/web/application.py Tue Sep 11 22:32:01 2012 +0200
+++ b/web/application.py Fri Oct 12 15:38:58 2012 +0200
@@ -461,7 +461,6 @@
result = self.notfound_content(req)
req.status_out = ex.status
except ValidationError, ex:
- req.status_out = httplib.CONFLICT
result = self.validation_error_handler(req, ex)
except RemoteCallFailed, ex:
result = self.ajax_error_handler(req, ex)
@@ -480,7 +479,7 @@
except (AuthenticationError, LogOut):
# the rollback is handled in the finally
raise
- ### Last defence line
+ ### Last defense line
except BaseException, ex:
result = self.error_handler(req, ex, tb=True)
finally:
@@ -511,7 +510,7 @@
return ''
def validation_error_handler(self, req, ex):
- ex.errors = dict((k, v) for k, v in ex.errors.items())
+ ex.tr(req._) # translate messages using ui language
if '__errorurl' in req.form:
forminfo = {'error': ex,
'values': req.form,
@@ -526,6 +525,7 @@
req.headers_out.setHeader('location', str(location))
req.status_out = httplib.SEE_OTHER
return ''
+ req.status_out = httplib.CONFLICT
return self.error_handler(req, ex, tb=False)
def error_handler(self, req, ex, tb=False):
--- a/web/data/cubicweb.ajax.js Tue Sep 11 22:32:01 2012 +0200
+++ b/web/data/cubicweb.ajax.js Fri Oct 12 15:38:58 2012 +0200
@@ -704,7 +704,7 @@
var ajaxArgs = ['render', formparams, registry, compid];
ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4));
var params = ajaxFuncArgs.apply(null, ajaxArgs);
- return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap');
+ return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true);
}
/* ajax tabs ******************************************************************/
--- a/web/data/cubicweb.facets.js Tue Sep 11 22:32:01 2012 +0200
+++ b/web/data/cubicweb.facets.js Fri Oct 12 15:38:58 2012 +0200
@@ -68,6 +68,14 @@
var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + encodeURIComponent(bkPath);
$bkLink.attr('href', bkUrl);
}
+ var $focusLink = jQuery('#focusLink');
+ if ($focusLink.length) {
+ var url = baseuri()+ 'view?rql=' + encodeURIComponent(rql);
+ if (vid) {
+ url += '&vid=' + encodeURIComponent(vid);
+ }
+ $focusLink.attr('href', url);
+ }
var toupdate = result[1];
var extraparams = vidargs;
if (paginate) { extraparams['paginate'] = '1'; } // XXX in vidargs
--- a/web/test/unittest_views_basecontrollers.py Tue Sep 11 22:32:01 2012 +0200
+++ b/web/test/unittest_views_basecontrollers.py Fri Oct 12 15:38:58 2012 +0200
@@ -77,6 +77,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.tr(unicode)
self.assertEqual(cm.exception.errors, {'login-subject': 'the value "admin" is already used, use another one'})
def test_user_editing_itself(self):
@@ -249,6 +250,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.tr(unicode)
self.assertEqual(cm.exception.errors, {'amount-subject': 'value -10 must be >= 0'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
@@ -259,6 +261,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.tr(unicode)
self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
--- a/web/views/basecontrollers.py Tue Sep 11 22:32:01 2012 +0200
+++ b/web/views/basecontrollers.py Fri Oct 12 15:38:58 2012 +0200
@@ -190,6 +190,7 @@
def _validation_error(req, ex):
req.cnx.rollback()
+ ex.tr(req._) # translate messages using ui language
# XXX necessary to remove existant validation error?
# imo (syt), it's not necessary
req.session.data.pop(req.form.get('__errorurl'), None)
--- a/web/views/editcontroller.py Tue Sep 11 22:32:01 2012 +0200
+++ b/web/views/editcontroller.py Fri Oct 12 15:38:58 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/views/facets.py Tue Sep 11 22:32:01 2012 +0200
+++ b/web/views/facets.py Fri Oct 12 15:38:58 2012 +0200
@@ -26,6 +26,7 @@
from logilab.common.decorators import cachedproperty
from logilab.common.registry import objectify_predicate, yes
+from cubicweb import tags
from cubicweb.predicates import (non_final_entity, multi_lines_rset,
match_context_prop, relation_possible)
from cubicweb.utils import json_dumps
@@ -234,6 +235,7 @@
vid = req.form.get('vid')
if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
w(self.bookmark_link(rset))
+ w(self.focus_link(rset))
hiddens = {}
for param in ('subvid', 'vtitle'):
if param in req.form:
@@ -269,6 +271,9 @@
req._('bookmark this search'))
return self.bk_linkbox_template % bk_link
+ def focus_link(self, rset):
+ return self.bk_linkbox_template % tags.a(self._cw._('focus on this selection'),
+ href=self._cw.url(), id='focusLink')
class FilterTable(FacetFilterMixIn, AnyRsetView):
__regid__ = 'facet.filtertable'