[repo] normalize ValidationError on edited entity (closes #2509729) stable
authorDavid Douard <david.douard@logilab.fr>
Thu, 25 Jul 2013 08:52:15 +0200
branchstable
changeset 9184 b982e88e4836
parent 9183 95e69c2d52a9
child 9187 8406bef5d5f2
[repo] normalize ValidationError on edited entity (closes #2509729) In CubicWeb, ValidationError.entity MUST be the eid of the involved entity, not the entity iteself (as done by yams).
doc/book/en/devrepo/devcore/dbapi.rst
doc/book/en/devrepo/repo/hooks.rst
server/edition.py
server/repository.py
web/test/unittest_views_basecontrollers.py
--- a/doc/book/en/devrepo/devcore/dbapi.rst	Wed Jul 24 13:59:08 2013 +0200
+++ b/doc/book/en/devrepo/devcore/dbapi.rst	Thu Jul 25 08:52:15 2013 +0200
@@ -29,6 +29,11 @@
 
   Also, a rollback is automatically done if an error occurs during commit.
 
+.. note::
+
+   A :exc:`ValidationError` has a `entity` attribute. In CubicWeb,
+   this atttribute is set to the entity's eid (not a reference to the
+   entity itself).
 
 Executing RQL queries from a view or a hook
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- a/doc/book/en/devrepo/repo/hooks.rst	Wed Jul 24 13:59:08 2013 +0200
+++ b/doc/book/en/devrepo/repo/hooks.rst	Thu Jul 25 08:52:15 2013 +0200
@@ -237,7 +237,7 @@
 interface. Hence its constructor is different from the default Exception
 constructor. It accepts, positionally:
 
-* an entity eid,
+* an entity eid (**not the entity itself**),
 
 * a dict whose keys represent attribute (or relation) names and values
   an end-user facing message (hence properly translated) relating the
--- a/server/edition.py	Wed Jul 24 13:59:08 2013 +0200
+++ b/server/edition.py	Thu Jul 25 08:52:15 2013 +0200
@@ -145,7 +145,7 @@
             entity.e_schema.check(dict_protocol_catcher(entity),
                                   creation=creation, relations=relations)
         except ValidationError as ex:
-            ex.entity = self.entity
+            ex.entity = self.entity.eid
             raise
 
     def clone(self):
--- a/server/repository.py	Wed Jul 24 13:59:08 2013 +0200
+++ b/server/repository.py	Thu Jul 25 08:52:15 2013 +0200
@@ -793,16 +793,7 @@
                 #       Zeroed to avoid useless overhead with pyro
                 rset._rqlst = None
                 return rset
-            except (Unauthorized, RQLSyntaxError):
-                raise
-            except ValidationError as ex:
-                # need ValidationError normalization here so error may pass
-                # through pyro
-                if hasattr(ex.entity, 'eid'):
-                    ex.entity = ex.entity.eid # error raised by yams
-                    args = list(ex.args)
-                    args[0] = ex.entity
-                    ex.args = tuple(args)
+            except (ValidationError, Unauthorized, RQLSyntaxError):
                 raise
             except Exception:
                 # FIXME: check error to catch internal errors
--- a/web/test/unittest_views_basecontrollers.py	Wed Jul 24 13:59:08 2013 +0200
+++ b/web/test/unittest_views_basecontrollers.py	Thu Jul 25 08:52:15 2013 +0200
@@ -39,6 +39,8 @@
 from cubicweb.web.views.basecontrollers import JSonController, xhtmlize, jsonize
 from cubicweb.web.views.ajaxcontroller import ajaxfunc, AjaxFunction
 import cubicweb.transaction as tx
+from cubicweb.server.hook import Hook, Operation
+from cubicweb.predicates import is_instance
 
 u = unicode
 
@@ -287,6 +289,7 @@
             self.ctrl_publish(req)
         cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
+
         req = self.request(rollbackfirst=True)
         req.form = {'eid': ['X'],
                     '__type:X': 'Salesterm',
@@ -300,6 +303,67 @@
         e = self.execute('Salesterm X').get_entity(0, 0)
         self.assertEqual(e.amount, 10)
 
+    def test_interval_bound_constraint_validateform(self):
+        """Test the FormValidatorController controller on entity with
+        constrained attributes"""
+        feid = self.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
+                            {'data': Binary('yo')})[0][0]
+        seid = self.request().create_entity('Salesterm', amount=0, described_by_test=feid).eid
+        self.commit()
+
+        # ensure a value that violate a constraint is properly detected
+        req = self.request(rollbackfirst=True)
+        req.form = {'eid': [unicode(seid)],
+                    '__type:%s'%seid: 'Salesterm',
+                    '_cw_entity_fields:%s'%seid: 'amount-subject',
+                    'amount-subject:%s'%seid: u'-10',
+                }
+        self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [false, [%s, {"amount-subject": "value -10 must be >= 0"}], null], null);
+</script>'''%seid, self.ctrl_publish(req, 'validateform'))
+
+        # ensure a value that comply a constraint is properly processed
+        req = self.request(rollbackfirst=True)
+        req.form = {'eid': [unicode(seid)],
+                    '__type:%s'%seid: 'Salesterm',
+                    '_cw_entity_fields:%s'%seid: 'amount-subject',
+                    'amount-subject:%s'%seid: u'20',
+                }
+        self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
+</script>''', self.ctrl_publish(req, 'validateform'))
+        self.assertEqual(20, self.execute('Any V WHERE X amount V, X eid %(eid)s', {'eid': seid})[0][0])
+
+        req = self.request(rollbackfirst=True)
+        req.form = {'eid': ['X'],
+                    '__type:X': 'Salesterm',
+                    '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
+                    'amount-subject:X': u'0',
+                    'described_by_test-subject:X': u(feid),
+                }
+
+        # ensure a value that is modified in an operation on a modify
+        # hook works as it should (see
+        # https://www.cubicweb.org/ticket/2509729 )
+        class MyOperation(Operation):
+            def precommit_event(self):
+                self.entity.cw_set(amount=-10)
+        class ValidationErrorInOpAfterHook(Hook):
+            __regid__ = 'valerror-op-after-hook'
+            __select__ = Hook.__select__ & is_instance('Salesterm')
+            events = ('after_add_entity',)
+            def __call__(self):
+                MyOperation(self._cw, entity=self.entity)
+
+        with self.temporary_appobjects(ValidationErrorInOpAfterHook):
+            self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [false, ["X", {"amount-subject": "value -10 must be >= 0"}], null], null);
+</script>''', self.ctrl_publish(req, 'validateform'))
+
+        self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
+</script>''', self.ctrl_publish(req, 'validateform'))
+
     def test_req_pending_insert(self):
         """make sure req's pending insertions are taken into account"""
         tmpgroup = self.request().create_entity('CWGroup', name=u"test")
@@ -312,7 +376,6 @@
         self.assertItemsEqual(usergroups, ['managers', 'test'])
         self.assertEqual(get_pending_inserts(req), [])
 
-
     def test_req_pending_delete(self):
         """make sure req's pending deletions are taken into account"""
         user = self.user()