[form] put qualified name on validation error, should fix #784299 stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 26 Mar 2010 08:28:22 +0100
branchstable
changeset 5030 5238d9a8dfee
parent 5029 f7709d28fb79
child 5031 60c4dea96afa
[form] put qualified name on validation error, should fix #784299
entities/schemaobjs.py
entities/test/unittest_wfobjs.py
hooks/integrity.py
hooks/syncsession.py
hooks/test/unittest_hooks.py
hooks/workflow.py
schema.py
server/repository.py
web/formfields.py
web/test/unittest_application.py
web/test/unittest_views_basecontrollers.py
--- a/entities/schemaobjs.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/entities/schemaobjs.py	Fri Mar 26 08:28:22 2010 +0100
@@ -9,6 +9,8 @@
 
 from logilab.common.decorators import cached
 
+from yams.schema import role_name
+
 from cubicweb import ValidationError
 from cubicweb.schema import ERQLExpression, RRQLExpression
 
@@ -64,13 +66,14 @@
         for rdef in self.reverse_relation_type:
             card = rdef.cardinality[0]
             if not card in '?1':
+                qname = role_name('inlined', 'subject')
                 rtype = self.name
                 stype = rdef.stype
                 otype = rdef.otype
                 msg = self._cw._("can't set inlined=%(inlined)s, "
                                  "%(stype)s %(rtype)s %(otype)s "
                                  "has cardinality=%(card)s")
-                raise ValidationError(self.eid, {'inlined': msg % locals()})
+                raise ValidationError(self.eid, {qname: msg % locals()})
 
     def db_key_name(self):
         """XXX goa specific"""
--- a/entities/test/unittest_wfobjs.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/entities/test/unittest_wfobjs.py	Fri Mar 26 08:28:22 2010 +0100
@@ -39,7 +39,7 @@
         self.commit()
         wf.add_state(u'foo')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
         # no pb if not in the same workflow
         wf2 = add_wf(self, 'Company')
         foo = wf2.add_state(u'foo', initial=True)
@@ -49,7 +49,7 @@
         self.commit()
         bar.set_attributes(name=u'foo')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
 
     def test_duplicated_transition(self):
         wf = add_wf(self, 'Company')
@@ -58,7 +58,7 @@
         wf.add_transition(u'baz', (foo,), bar, ('managers',))
         wf.add_transition(u'baz', (bar,), foo)
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
         # no pb if not in the same workflow
         wf2 = add_wf(self, 'Company')
         foo = wf.add_state(u'foo', initial=True)
@@ -70,7 +70,7 @@
         self.commit()
         biz.set_attributes(name=u'baz')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
+        self.assertEquals(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
 
 
 class WorkflowTC(CubicWebTC):
@@ -132,7 +132,7 @@
             ex = self.assertRaises(ValidationError, self.session.execute,
                                'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
                                {'x': self.user().eid, 's': s.eid}, 'x')
-            self.assertEquals(ex.errors, {'in_state': "state doesn't belong to entity's workflow. "
+            self.assertEquals(ex.errors, {'in_state-subject': "state doesn't belong to entity's workflow. "
                                       "You may want to set a custom workflow for this entity first."})
 
     def test_fire_transition(self):
@@ -175,7 +175,7 @@
         member = req.entity_from_eid(self.member.eid)
         ex = self.assertRaises(ValidationError,
                                member.fire_transition, 'deactivate')
-        self.assertEquals(ex.errors, {'by_transition': "transition may not be fired"})
+        self.assertEquals(ex.errors, {'by_transition-subject': "transition may not be fired"})
         cnx.close()
         cnx = self.login('member')
         req = self.request()
@@ -184,7 +184,7 @@
         cnx.commit()
         ex = self.assertRaises(ValidationError,
                                member.fire_transition, 'activate')
-        self.assertEquals(ex.errors, {'by_transition': "transition may not be fired"})
+        self.assertEquals(ex.errors, {'by_transition-subject': "transition may not be fired"})
 
     def test_fire_transition_owned_by(self):
         self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
@@ -257,7 +257,7 @@
         # subworkflow input transition
         ex = self.assertRaises(ValidationError,
                                self.group.change_state, swfstate1, u'gadget')
-        self.assertEquals(ex.errors, {'to_state': "state doesn't belong to entity's workflow"})
+        self.assertEquals(ex.errors, {'to_state-subject': "state doesn't belong to entity's workflow"})
         self.rollback()
         # force back to state1
         self.group.change_state('state1', u'gadget')
@@ -293,7 +293,7 @@
         mwf.add_wftransition(u'swftr1', swf, state1,
                              [(swfstate2, state2), (swfstate2, state3)])
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'subworkflow_exit': u"can't have multiple exits on the same state"})
+        self.assertEquals(ex.errors, {'subworkflow_exit-subject': u"can't have multiple exits on the same state"})
 
     def test_swf_fire_in_a_row(self):
         # sub-workflow
@@ -406,7 +406,7 @@
         self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
                      {'wf': wf.eid, 'x': self.member.eid})
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'custom_workflow': u'workflow has no initial state'})
+        self.assertEquals(ex.errors, {'custom_workflow-subject': u'workflow has no initial state'})
 
     def test_custom_wf_bad_etype(self):
         """try to set a custom workflow which doesn't apply to entity type"""
@@ -415,7 +415,7 @@
         self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
                      {'wf': wf.eid, 'x': self.member.eid}, 'x')
         ex = self.assertRaises(ValidationError, self.commit)
-        self.assertEquals(ex.errors, {'custom_workflow': 'workflow isn\'t a workflow for this type'})
+        self.assertEquals(ex.errors, {'custom_workflow-subject': 'workflow isn\'t a workflow for this type'})
 
     def test_del_custom_wf(self):
         """member in some state shared by the new workflow, nothing has to be
@@ -535,7 +535,7 @@
         user = cnx.user(self.session)
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'activate')
-        self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+        self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
                           u"transition isn't allowed from")
         cnx.close()
 
@@ -544,7 +544,7 @@
         user = cnx.user(self.session)
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'dummy')
-        self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+        self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
                           u"transition isn't allowed from")
         cnx.close()
 
@@ -557,7 +557,7 @@
         session.set_pool()
         ex = self.assertRaises(ValidationError,
                                user.fire_transition, 'deactivate')
-        self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+        self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
                                             u"transition isn't allowed from")
         # get back now
         user.fire_transition('activate')
--- a/hooks/integrity.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/hooks/integrity.py	Fri Mar 26 08:28:22 2010 +0100
@@ -10,6 +10,8 @@
 
 from threading import Lock
 
+from yams.schema import role_name
+
 from cubicweb import ValidationError
 from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
 from cubicweb.selectors import implements
@@ -73,7 +75,8 @@
             _ = self.session._
             msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
             msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
-            raise ValidationError(self.eid, {self.rtype: msg})
+            qname = role_name(self.rtype, self.role)
+            raise ValidationError(self.eid, {qname: msg})
 
     def commit_event(self):
         pass
@@ -84,12 +87,14 @@
 
 class _CheckSRelationOp(_CheckRequiredRelationOperation):
     """check required subject relation"""
+    role = 'subject'
     def _rql(self):
         return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
 
 
 class _CheckORelationOp(_CheckRequiredRelationOperation):
     """check required object relation"""
+    role = 'object'
     def _rql(self):
         return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
 
@@ -225,7 +230,8 @@
                 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')
-                    raise ValidationError(entity.eid, {attr: msg % val})
+                    qname = role_name(attr, 'subject')
+                    raise ValidationError(entity.eid, {qname: msg % val})
 
 
 class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -237,12 +243,16 @@
 
     def __call__(self):
         if self.event == 'before_delete_entity' and self.entity.name == 'owners':
-            raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
-        elif self.event == 'before_update_entity' and 'name' in self.entity.edited_attributes:
+            msg = self._cw._('can\'t be deleted')
+            raise ValidationError(self.entity.eid, {None: msg})
+        elif self.event == 'before_update_entity' and \
+                 'name' in self.entity.edited_attributes:
             newname = self.entity.pop('name')
             oldname = self.entity.name
             if oldname == 'owners' and newname != oldname:
-                raise ValidationError(self.entity.eid, {'name': self._cw._('can\'t be changed')})
+                qname = role_name('name', 'subject')
+                msg = self._cw._('can\'t be changed')
+                raise ValidationError(self.entity.eid, {qname: msg})
             self.entity['name'] = newname
 
 
--- a/hooks/syncsession.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/hooks/syncsession.py	Fri Mar 26 08:28:22 2010 +0100
@@ -7,6 +7,7 @@
 """
 __docformat__ = "restructuredtext en"
 
+from yams.schema import role_name
 from cubicweb import UnknownProperty, ValidationError, BadConnectionId
 from cubicweb.selectors import implements
 from cubicweb.server import hook
@@ -147,11 +148,13 @@
         try:
             value = session.vreg.typed_value(key, value)
         except UnknownProperty:
+            qname = role_name('pkey', 'subject')
             raise ValidationError(self.entity.eid,
-                                  {'pkey': session._('unknown property key')})
+                                  {qname: session._('unknown property key')})
         except ValueError, ex:
+            qname = role_name('value', 'subject')
             raise ValidationError(self.entity.eid,
-                                  {'value': session._(str(ex))})
+                                  {qname: session._(str(ex))})
         if not session.user.matching_groups('managers'):
             session.add_relation(self.entity.eid, 'for_user', session.user.eid)
         else:
@@ -174,7 +177,8 @@
         except UnknownProperty:
             return
         except ValueError, ex:
-            raise ValidationError(entity.eid, {'value': session._(str(ex))})
+            qname = role_name('value', 'subject')
+            raise ValidationError(entity.eid, {qname: session._(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,
@@ -214,8 +218,10 @@
         key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
                                      {'x': eidfrom}, 'x')[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,
-                                  {'for_user': session._("site-wide property can't be set for user")})
+                                  {qname: msg})
         for session_ in get_user_sessions(session.repo, self.eidto):
             _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
                               key=key, value=value)
--- a/hooks/test/unittest_hooks.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/hooks/test/unittest_hooks.py	Fri Mar 26 08:28:22 2010 +0100
@@ -102,9 +102,9 @@
                               'WHERE FE name "CWUser", RT name "in_group", TE name "String"')[0][0]
         self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
                      {'x': releid}, 'x')
-        ex = self.assertRaises(ValidationError,
-                               self.commit)
-        self.assertEquals(ex.errors, {'to_entity': 'RQLConstraint O final FALSE failed'})
+        ex = self.assertRaises(ValidationError, self.commit)
+        self.assertEquals(ex.errors,
+                          {'to_entity-object': 'RQLConstraint O final FALSE failed'})
 
     def test_html_tidy_hook(self):
         req = self.request()
@@ -217,23 +217,23 @@
     def test_unexistant_eproperty(self):
         ex = self.assertRaises(ValidationError,
                           self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+        self.assertEquals(ex.errors, {'pkey-subject': 'unknown property key'})
         ex = self.assertRaises(ValidationError,
                           self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
-        self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+        self.assertEquals(ex.errors, {'pkey-subject': 'unknown property key'})
 
     def test_site_wide_eproperty(self):
         ex = self.assertRaises(ValidationError,
                                self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
+        self.assertEquals(ex.errors, {'for_user-subject': "site-wide property can't be set for user"})
 
     def test_bad_type_eproperty(self):
         ex = self.assertRaises(ValidationError,
                                self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
-        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+        self.assertEquals(ex.errors, {'value-subject': u'unauthorized value'})
         ex = self.assertRaises(ValidationError,
                           self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
-        self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+        self.assertEquals(ex.errors, {'value-subject': u'unauthorized value'})
 
 
 class SchemaHooksTC(CubicWebTC):
@@ -253,7 +253,7 @@
             self.execute('INSERT CWUser X: X login "admin"')
         except ValidationError, ex:
             self.assertIsInstance(ex.entity, int)
-            self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+            self.assertEquals(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
 
 if __name__ == '__main__':
--- a/hooks/workflow.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/hooks/workflow.py	Fri Mar 26 08:28:22 2010 +0100
@@ -9,6 +9,8 @@
 
 from datetime import datetime
 
+from yams.schema import role_name
+
 from cubicweb import RepositoryError, ValidationError
 from cubicweb.interfaces import IWorkflowable
 from cubicweb.selectors import implements
@@ -73,8 +75,9 @@
         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, {'custom_workflow': msg})
+                raise ValidationError(entity.eid, {qname: msg})
             if mainwf.state_by_eid(entity.current_state.eid):
                 # nothing to do
                 return
@@ -97,8 +100,9 @@
         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, {'subworkflow_exit': msg})
+                raise ValidationError(self.treid, {qname: msg})
             outputs.add(ep.subwf_state.eid)
 
 
@@ -112,6 +116,7 @@
         wftr = forentity.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})
         tostate = wftr.get_exit_point(forentity, trinfo['to_state'])
@@ -166,8 +171,9 @@
         try:
             foreid = entity['wf_info_for']
         except KeyError:
+            qname = role_name('wf_info_for', 'subject')
             msg = session._('mandatory relation')
-            raise ValidationError(entity.eid, {'wf_info_for': msg})
+            raise ValidationError(entity.eid, {qname: msg})
         forentity = session.entity_from_eid(foreid)
         # then check it has a workflow set, unless we're in the process of changing
         # entity's workflow
@@ -195,41 +201,47 @@
             # 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, {'by_transition': msg})
+                raise ValidationError(entity.eid, {qname: msg})
             deststateeid = entity.get('to_state')
             if not deststateeid:
+                qname = role_name('by_transition', 'subject')
                 msg = session._('mandatory relation')
-                raise ValidationError(entity.eid, {'by_transition': msg})
+                raise ValidationError(entity.eid, {qname: 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, {'to_state': msg})
+                raise ValidationError(entity.eid, {qname: 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')
                 if tr is None:
                     msg = session._("transition doesn't belong to entity's workflow")
-                    raise ValidationError(entity.eid, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {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, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {qname: msg})
                 if not tr.may_be_fired(foreid):
                     msg = session._("transition may not be fired")
-                    raise ValidationError(entity.eid, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {qname: msg})
             if entity.get('to_state'):
                 deststateeid = entity['to_state']
                 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, {'by_transition': msg})
+                    raise ValidationError(entity.eid, {qname: 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, {'to_state': msg})
+                        raise ValidationError(entity.eid, {qname: msg})
             else:
                 deststateeid = tr.destination(forentity).eid
         # everything is ok, add missing information on the trinfo entity
@@ -279,12 +291,14 @@
             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, {'in_state': msg})
+            raise ValidationError(self.eidfrom, {qname: msg})
         if entity.current_workflow and wf.eid != entity.current_workflow.eid:
+            qname = role_name('in_state', 'subject')
             msg = session._("state doesn't belong to entity's current workflow")
-            raise ValidationError(self.eidfrom, {'in_state': msg})
+            raise ValidationError(self.eidfrom, {qname: msg})
 
 
 class SetModificationDateOnStateChange(WorkflowHook):
--- a/schema.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/schema.py	Fri Mar 26 08:28:22 2010 +0100
@@ -21,7 +21,7 @@
 
 from yams import BadSchemaDefinition, buildobjs as ybo
 from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
-     RelationDefinitionSchema, PermissionMixIn
+     RelationDefinitionSchema, PermissionMixIn, role_name
 from yams.constraints import BaseConstraint, FormatConstraint
 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
                          obsolete as yobsolete, cleanup_sys_modules)
@@ -682,17 +682,22 @@
             # XXX at this point if both or neither of S and O are in mainvar we
             # dunno if the validation error `occured` on eidfrom or eidto (from
             # user interface point of view)
+            #
+            # possible enhancement: check entity being created, it's probably
+            # the main eid unless this is a composite relation
             if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
                 maineid = eidfrom
+                qname = role_name(rtype, 'subject')
             else:
                 maineid = eidto
+                qname = role_name(rtype, 'object')
             if self.msg:
                 msg = session._(self.msg)
             else:
                 msg = '%(constraint)s %(restriction)s failed' % {
                     'constraint':  session._(self.type()),
                     'restriction': self.restriction}
-            raise ValidationError(maineid, {rtype: msg})
+            raise ValidationError(maineid, {qname: msg})
 
     def exec_query(self, session, eidfrom, eidto):
         if eidto is None:
--- a/server/repository.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/server/repository.py	Fri Mar 26 08:28:22 2010 +0100
@@ -31,6 +31,7 @@
 from logilab.common import flatten
 
 from yams import BadSchemaDefinition
+from yams.schema import role_name
 from rql import RQLSyntaxError
 
 from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP,
@@ -519,7 +520,8 @@
             if (session.execute('CWUser X WHERE X login %(login)s', {'login': login})
                 or session.execute('CWUser X WHERE X use_email C, C address %(login)s',
                                    {'login': login})):
-                raise ValidationError(None, {'login': errmsg % login})
+                qname = role_name('login', 'subject')
+                raise ValidationError(None, {qname: errmsg % login})
             # we have to create the user
             user = self.vreg['etypes'].etype_class('CWUser')(session, None)
             if isinstance(password, unicode):
@@ -534,7 +536,8 @@
             if email or '@' in login:
                 d = {'login': login, 'email': email or login}
                 if session.execute('EmailAddress X WHERE X address %(email)s', d):
-                    raise ValidationError(None, {'address': errmsg % d['email']})
+                    qname = role_name('address', 'subject')
+                    raise ValidationError(None, {qname: errmsg % d['email']})
                 session.execute('INSERT EmailAddress X: X address %(email)s, '
                                 'U primary_email X, U use_email X WHERE U login %(login)s', d)
             session.commit()
--- a/web/formfields.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/web/formfields.py	Fri Mar 26 08:28:22 2010 +0100
@@ -15,7 +15,7 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.date import ustrftime
 
-from yams.schema import KNOWN_METAATTRIBUTES
+from yams.schema import KNOWN_METAATTRIBUTES, role_name
 from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
                               FormatConstraint)
 
@@ -214,7 +214,7 @@
     def role_name(self):
         """return <field.name>-<field.role> if role is specified, else field.name"""
         if self.role is not None:
-            return '%s-%s' % (self.name, self.role)
+            return role_name(self.name, self.role)
         return self.name
 
     def dom_id(self, form, suffix=None):
--- a/web/test/unittest_application.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/web/test/unittest_application.py	Fri Mar 26 08:28:22 2010 +0100
@@ -186,7 +186,7 @@
         self.assertEquals(values['eid'], eid)
         error = forminfo['error']
         self.assertEquals(error.entity, user.eid)
-        self.assertEquals(error.errors['login'], 'required attribute')
+        self.assertEquals(error.errors['login-subject'], 'required attribute')
 
 
     def test_validation_error_dont_loose_subentity_data(self):
@@ -213,8 +213,9 @@
         forminfo = req.get_session_data('view?vid=edition...')
         self.assertEquals(set(forminfo['eidmap']), set('XY'))
         self.assertEquals(forminfo['error'].entity, forminfo['eidmap']['X'])
-        self.assertEquals(forminfo['error'].errors, {'login': 'required attribute',
-                                                     'upassword': 'required attribute'})
+        self.assertEquals(forminfo['error'].errors,
+                          {'login-subject': 'required attribute',
+                           'upassword-subject': 'required attribute'})
         self.assertEquals(forminfo['values'], form)
 
     def _test_cleaned(self, kwargs, injected, cleaned):
--- a/web/test/unittest_views_basecontrollers.py	Fri Mar 26 06:57:52 2010 +0100
+++ b/web/test/unittest_views_basecontrollers.py	Fri Mar 26 08:28:22 2010 +0100
@@ -51,7 +51,7 @@
                     'upassword-subject-confirm:X': u'toto',
                     }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+        self.assertEquals(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
 
     def test_user_editing_itself(self):
         """checking that a manager user can edit itself
@@ -219,7 +219,7 @@
                     'described_by_test-subject:X': u(feid),
                 }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value -10'})
+        self.assertEquals(ex.errors, {'amount-subject': 'value [0;100] constraint failed for value -10'})
         req = self.request()
         req.form = {'eid': ['X'],
                     '__type:X': 'Salesterm',
@@ -228,7 +228,7 @@
                     'described_by_test-subject:X': u(feid),
                     }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value 110'})
+        self.assertEquals(ex.errors, {'amount-subject': 'value [0;100] constraint failed for value 110'})
         req = self.request()
         req.form = {'eid': ['X'],
                     '__type:X': 'Salesterm',
@@ -430,7 +430,7 @@
                     'use_email-object:Y': 'X',
                     }
         ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
-        self.assertEquals(ex.errors, {'address': u'required attribute'})
+        self.assertEquals(ex.errors, {'address-subject': u'required attribute'})
 
     def test_nonregr_copy(self):
         user = self.user()