# HG changeset patch # User Julien Cristau # Date 1427063233 -3600 # Node ID 1e6655cff5ab6fa1a47e66e745cca703d4b515f6 # Parent f1773842077d0647c374c4671902200b0a7b53ba 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 diff -r f1773842077d -r 1e6655cff5ab _exceptions.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): diff -r f1773842077d -r 1e6655cff5ab entities/adapters.py --- 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) diff -r f1773842077d -r 1e6655cff5ab server/repository.py --- 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() diff -r f1773842077d -r 1e6655cff5ab server/sources/native.py --- 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 diff -r f1773842077d -r 1e6655cff5ab server/test/data/schema.py --- 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"')), diff -r f1773842077d -r 1e6655cff5ab server/test/unittest_postgres.py --- 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('a>b', 'text/html', 2)"), \ 'a>...' + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main()