# HG changeset patch # User Sylvain Thénault # Date 1347896935 -7200 # Node ID bbe0d6985e5959bfe92f3e83fba3d4b72a592a7a # Parent c747242d22a683527938857c3882d75443e70ca4 [validation error] refactor validation error handling so translation is done on the web side Users should now use cubicweb.validation_error helper function that will activate the feature with other handy behaviours. Also test testing for message in errors should call exception.tr(unicode) before comparing. Using bare ValidationError keep backward compat. diff -r c747242d22a6 -r bbe0d6985e59 __init__.py --- a/__init__.py Tue Sep 11 22:32:01 2012 +0200 +++ b/__init__.py Mon Sep 17 17:48:55 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) diff -r c747242d22a6 -r bbe0d6985e59 _exceptions.py --- a/_exceptions.py Tue Sep 11 22:32:01 2012 +0200 +++ b/_exceptions.py Mon Sep 17 17:48:55 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 ######################################################### diff -r c747242d22a6 -r bbe0d6985e59 hooks/integrity.py --- a/hooks/integrity.py Tue Sep 11 22:32:01 2012 +0200 +++ b/hooks/integrity.py Mon Sep 17 17:48:55 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): diff -r c747242d22a6 -r bbe0d6985e59 hooks/syncschema.py --- a/hooks/syncschema.py Tue Sep 11 22:32:01 2012 +0200 +++ b/hooks/syncschema.py Mon Sep 17 17:48:55 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}) diff -r c747242d22a6 -r bbe0d6985e59 hooks/syncsession.py --- a/hooks/syncsession.py Tue Sep 11 22:32:01 2012 +0200 +++ b/hooks/syncsession.py Mon Sep 17 17:48:55 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) diff -r c747242d22a6 -r bbe0d6985e59 hooks/syncsources.py --- a/hooks/syncsources.py Tue Sep 11 22:32:01 2012 +0200 +++ b/hooks/syncsources.py Mon Sep 17 17:48:55 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 . """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,9 +47,8 @@ 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}) + msg = _('unknown source type') + raise validation_error(self.entity, {('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) @@ -65,7 +65,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) @@ -154,8 +155,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): diff -r c747242d22a6 -r bbe0d6985e59 hooks/test/unittest_hooks.py --- a/hooks/test/unittest_hooks.py Tue Sep 11 22:32:01 2012 +0200 +++ b/hooks/test/unittest_hooks.py Mon Sep 17 17:48:55 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'}) diff -r c747242d22a6 -r bbe0d6985e59 hooks/test/unittest_syncsession.py --- a/hooks/test/unittest_syncsession.py Tue Sep 11 22:32:01 2012 +0200 +++ b/hooks/test/unittest_syncsession.py Mon Sep 17 17:48:55 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): diff -r c747242d22a6 -r bbe0d6985e59 hooks/workflow.py --- a/hooks/workflow.py Tue Sep 11 22:32:01 2012 +0200 +++ b/hooks/workflow.py Mon Sep 17 17:48:55 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): diff -r c747242d22a6 -r bbe0d6985e59 server/edition.py --- a/server/edition.py Tue Sep 11 22:32:01 2012 +0200 +++ b/server/edition.py Mon Sep 17 17:48:55 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 diff -r c747242d22a6 -r bbe0d6985e59 server/test/unittest_undo.py --- a/server/test/unittest_undo.py Tue Sep 11 22:32:01 2012 +0200 +++ b/server/test/unittest_undo.py Mon Sep 17 17:48:55 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 ' diff -r c747242d22a6 -r bbe0d6985e59 web/application.py --- a/web/application.py Tue Sep 11 22:32:01 2012 +0200 +++ b/web/application.py Mon Sep 17 17:48:55 2012 +0200 @@ -511,7 +511,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, diff -r c747242d22a6 -r bbe0d6985e59 web/test/unittest_views_basecontrollers.py --- a/web/test/unittest_views_basecontrollers.py Tue Sep 11 22:32:01 2012 +0200 +++ b/web/test/unittest_views_basecontrollers.py Mon Sep 17 17:48:55 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'], diff -r c747242d22a6 -r bbe0d6985e59 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Tue Sep 11 22:32:01 2012 +0200 +++ b/web/views/basecontrollers.py Mon Sep 17 17:48:55 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) diff -r c747242d22a6 -r bbe0d6985e59 web/views/editcontroller.py --- a/web/views/editcontroller.py Tue Sep 11 22:32:01 2012 +0200 +++ b/web/views/editcontroller.py Mon Sep 17 17:48:55 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.