diff --git a/AUTHORS b/AUTHORS index 37170ffa..f64093d8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -223,3 +223,4 @@ that much better: * Kiryl Yermakou (https://github.com/rma4ok) * Matthieu Rigal (https://github.com/MRigal) * Charanpal Dhanjal (https://github.com/charanpald) + * Emmanuel Leblond (https://github.com/touilleMan) diff --git a/docs/changelog.rst b/docs/changelog.rst index 42d98597..65e236ba 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -20,6 +20,7 @@ Changes in 0.9.X - DEV - Support for PyMongo 3+ #946 - Fix for issue where FileField deletion did not free space in GridFS. - No_dereference() not respected on embedded docs containing reference. #517 +- Document save raise an exception if save_condition fails #1005 Changes in 0.9.0 ================ diff --git a/mongoengine/document.py b/mongoengine/document.py index 060919ca..eedd01d2 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -266,7 +266,8 @@ class Document(BaseDocument): to cascading saves. Implies ``cascade=True``. :param _refs: A list of processed references used in cascading saves :param save_condition: only perform save if matching record in db - satisfies condition(s) (e.g., version number) + satisfies condition(s) (e.g. version number). + Raises :class:`OperationError` if the conditions are not satisfied .. versionchanged:: 0.5 In existing documents it only saves changed fields using @@ -284,6 +285,8 @@ class Document(BaseDocument): .. versionchanged:: 0.8.5 Optional save_condition that only overwrites existing documents if the condition is satisfied in the current db record. + .. versionchanged:: 0.10 + :class:`OperationError` exception raised if save_condition fails. """ signals.pre_save.send(self.__class__, document=self) @@ -348,6 +351,9 @@ class Document(BaseDocument): upsert = save_condition is None last_error = collection.update(select_dict, update_query, upsert=upsert, **write_concern) + if not upsert and last_error['nModified'] == 0: + raise OperationError('Race condition preventing' + ' document update detected') created = is_new_object(last_error) if cascade is None: diff --git a/tests/document/instance.py b/tests/document/instance.py index 2cfdef65..e1710b9f 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -954,11 +954,12 @@ class InstanceTest(unittest.TestCase): self.assertEqual(w1.save_id, UUID(1)) self.assertEqual(w1.count, 0) - # mismatch in save_condition prevents save + # mismatch in save_condition prevents save and raise exception flip(w1) self.assertTrue(w1.toggle) self.assertEqual(w1.count, 1) - w1.save(save_condition={'save_id': UUID(42)}) + self.assertRaises(OperationError, + w1.save, save_condition={'save_id': UUID(42)}) w1.reload() self.assertFalse(w1.toggle) self.assertEqual(w1.count, 0) @@ -986,7 +987,8 @@ class InstanceTest(unittest.TestCase): self.assertEqual(w1.count, 2) flip(w2) flip(w2) - w2.save(save_condition={'save_id': old_id}) + self.assertRaises(OperationError, + w2.save, save_condition={'save_id': old_id}) w2.reload() self.assertFalse(w2.toggle) self.assertEqual(w2.count, 2) @@ -998,7 +1000,8 @@ class InstanceTest(unittest.TestCase): self.assertTrue(w1.toggle) self.assertEqual(w1.count, 3) flip(w1) - w1.save(save_condition={'count__gte': w1.count}) + self.assertRaises(OperationError, + w1.save, save_condition={'count__gte': w1.count}) w1.reload() self.assertTrue(w1.toggle) self.assertEqual(w1.count, 3)