add IUserFriendlyError adapter for violation of check constraints
This way we get back the same error messages we get from the python
check.
Related to #5154406
--- a/_exceptions.py Mon May 18 19:47:34 2015 +0200
+++ b/_exceptions.py Sun Mar 22 23:27:13 2015 +0100
@@ -102,6 +102,12 @@
return None, self.rtypes
+class ViolatedConstraint(RepositoryError):
+ def __init__(self, cnx, cstrname):
+ self.cnx = cnx
+ self.cstrname = cstrname
+
+
# security exceptions #########################################################
class Unauthorized(SecurityError):
--- a/entities/adapters.py Mon May 18 19:47:34 2015 +0200
+++ b/entities/adapters.py Sun Mar 22 23:27:13 2015 +0100
@@ -24,11 +24,13 @@
from itertools import chain
from warnings import warn
+from hashlib import md5
from logilab.mtconverter import TransformError
from logilab.common.decorators import cached
-from cubicweb import ValidationError, view
+from cubicweb import ValidationError, view, ViolatedConstraint
+from cubicweb.schema import CONSTRAINTS
from cubicweb.predicates import is_instance, relation_possible, match_exception
@@ -372,3 +374,25 @@
i18nvalues.append(rtype + '-rtype')
errors[''] = _('some relations violate a unicity constraint')
raise ValidationError(self.entity.eid, errors, msgargs=msgargs, i18nvalues=i18nvalues)
+
+
+class IUserFriendlyCheckConstraint(IUserFriendlyError):
+ __select__ = match_exception(ViolatedConstraint)
+
+ def raise_user_exception(self):
+ _ = self._cw._
+ cstrname = self.exc.cstrname
+ eschema = self.entity.e_schema
+ for rschema, attrschema in eschema.attribute_definitions():
+ rdef = rschema.rdef(eschema, attrschema)
+ for constraint in rdef.constraints:
+ if cstrname == 'cstr' + md5(eschema.type + rschema.type + constraint.type() + (constraint.serialize() or '')).hexdigest():
+ break
+ else:
+ continue
+ break
+ else:
+ assert 0
+ key = rschema.type + '-subject'
+ msg, args = constraint.failed_message(key, self.entity.cw_edited[rschema.type])
+ raise ValidationError(self.entity.eid, {key: msg}, args)
--- a/server/repository.py Mon May 18 19:47:34 2015 +0200
+++ b/server/repository.py Sun Mar 22 23:27:13 2015 +0100
@@ -43,7 +43,7 @@
from cubicweb import (CW_MIGRATION_MAP, QueryError,
UnknownEid, AuthenticationError, ExecutionError,
BadConnectionId, ValidationError,
- UniqueTogetherError, onevent)
+ UniqueTogetherError, onevent, ViolatedConstraint)
from cubicweb import cwvreg, schema, server
from cubicweb.server import ShuttingDown, utils, hook, querier, sources
from cubicweb.server.session import Session, InternalManager
@@ -927,7 +927,7 @@
self.add_info(cnx, entity, source, extid)
try:
source.add_entity(cnx, entity)
- except UniqueTogetherError as exc:
+ except (UniqueTogetherError, ViolatedConstraint) as exc:
userhdlr = cnx.vreg['adapters'].select(
'IUserFriendlyError', cnx, entity=entity, exc=exc)
userhdlr.raise_user_exception()
@@ -990,7 +990,7 @@
try:
source.update_entity(cnx, entity)
edited.saved = True
- except UniqueTogetherError as exc:
+ except (UniqueTogetherError, ViolatedConstraint) as exc:
userhdlr = cnx.vreg['adapters'].select(
'IUserFriendlyError', cnx, entity=entity, exc=exc)
userhdlr.raise_user_exception()
--- a/server/sources/native.py Mon May 18 19:47:34 2015 +0200
+++ b/server/sources/native.py Sun Mar 22 23:27:13 2015 +0100
@@ -46,7 +46,7 @@
from yams.schema import role_name
from cubicweb import (UnknownEid, AuthenticationError, ValidationError, Binary,
- UniqueTogetherError, UndoTransactionException)
+ UniqueTogetherError, UndoTransactionException, ViolatedConstraint)
from cubicweb import transaction as tx, server, neg_role
from cubicweb.utils import QueryCache
from cubicweb.schema import VIRTUAL_RTYPES
@@ -731,6 +731,18 @@
columns = arg.split(':', 1)[1].split(',')
rtypes = [c.split('.', 1)[1].strip()[3:] for c in columns]
raise UniqueTogetherError(cnx, rtypes=rtypes)
+
+ mo = re.search('"cstr[a-f0-9]{32}"', arg)
+ if mo is not None:
+ # postgresql
+ raise ViolatedConstraint(cnx, cstrname=mo.group(0)[1:-1])
+ if arg.startswith('CHECK constraint failed:'):
+ # sqlite3 (new)
+ raise ViolatedConstraint(cnx, cstrname=arg.split(':', 1)[1].strip())
+ mo = re.match('^constraint (cstr.*) failed$', arg)
+ if mo is not None:
+ # sqlite3 (old)
+ raise ViolatedConstraint(cnx, cstrname=mo.group(1))
raise
return cursor
--- a/server/test/data/schema.py Mon May 18 19:47:34 2015 +0200
+++ b/server/test/data/schema.py Sun Mar 22 23:27:13 2015 +0100
@@ -89,7 +89,7 @@
class Note(WorkflowableEntityType):
date = String(maxsize=10)
- type = String(maxsize=6)
+ type = String(vocabulary=[u'todo', u'a', u'b', u'T', u'lalala'])
para = String(maxsize=512,
__permissions__ = {
'add': ('managers', ERQLExpression('X in_state S, S name "todo"')),
--- a/server/test/unittest_postgres.py Mon May 18 19:47:34 2015 +0200
+++ b/server/test/unittest_postgres.py Sun Mar 22 23:27:13 2015 +0100
@@ -22,6 +22,7 @@
from logilab.common.testlib import SkipTest
+from cubicweb import ValidationError
from cubicweb.devtools import PostgresApptestConfiguration, startpgcluster, stoppgcluster
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.predicates import is_instance
@@ -123,6 +124,18 @@
self.assertEqual(datenaiss.tzinfo, None)
self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 2, 0))
+ def test_constraint_validationerror(self):
+ with self.admin_access.repo_cnx() as cnx:
+ with cnx.allow_all_hooks_but('integrity'):
+ with self.assertRaises(ValidationError) as cm:
+ cnx.execute("INSERT Note N: N type 'nogood'")
+ self.assertEqual(cm.exception.errors,
+ {'type-subject': u'invalid value %(KEY-value)s, it must be one of %(KEY-choices)s'})
+ self.assertEqual(cm.exception.msgargs,
+ {'type-subject-value': u'"nogood"',
+ 'type-subject-choices': u'"todo", "a", "b", "T", "lalala"'})
+
+
class PostgresLimitSizeTC(CubicWebTC):
configcls = PostgresApptestConfiguration
@@ -141,6 +154,7 @@
yield self.assertEqual, sql("SELECT limit_size('<span>a>b</span>', 'text/html', 2)"), \
'a>...'
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()