Commit 484a3526 authored by Julien Cristau's avatar Julien Cristau
Browse files

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
parent f1773842077d
......@@ -102,6 +102,12 @@ class UniqueTogetherError(RepositoryError):
return None, self.rtypes
class ViolatedConstraint(RepositoryError):
def __init__(self, cnx, cstrname):
self.cnx = cnx
self.cstrname = cstrname
# security exceptions #########################################################
class Unauthorized(SecurityError):
......
......@@ -24,11 +24,13 @@ _ = unicode
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 @@ class IUserFriendlyUniqueTogether(IUserFriendlyError):
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)
......@@ -43,7 +43,7 @@ from rql.utils import rqlvar_maker
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 @@ class Repository(object):
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 @@ class Repository(object):
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()
......
......@@ -46,7 +46,7 @@ from logilab.database import get_db_helper, sqlgen
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 @@ class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
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
......
......@@ -89,7 +89,7 @@ CWUser.get_relations('login').next().fulltextindexed = True
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"')),
......
......@@ -22,6 +22,7 @@ from threading import Thread
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 @@ class PostgresFTITC(CubicWebTC):
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 @@ class PostgresLimitSizeTC(CubicWebTC):
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()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment