add IUserFriendlyError adapter for violation of check constraints
authorJulien Cristau <julien.cristau@logilab.fr>
Sun, 22 Mar 2015 23:27:13 +0100
changeset 10446 1e6655cff5ab
parent 10445 f1773842077d
child 10447 d309c020d9fb
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
_exceptions.py
entities/adapters.py
server/repository.py
server/sources/native.py
server/test/data/schema.py
server/test/unittest_postgres.py
--- 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()