summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJozef Knaperek <jknaperek@gmail.com>2014-07-11 22:11:04 +0200
committerAnssi Kääriäinen <akaariai@gmail.com>2014-11-12 12:53:30 +0200
commitc56c42b5c01ed774cfa8e044cd372a984608536c (patch)
treef04a635b41051a6d6c1f423a954538f4834bce64
parentd647764a53a77be89c8d7239d70adcf8343d9f3a (diff)
Fixed #22967 -- Made Model._do_update consistent
Made _do_update behave more strictly according to its docs, including a corner case when specific concurent updates are executed and select_on_save is set.
-rw-r--r--django/db/models/base.py10
-rw-r--r--tests/basic/tests.py2
2 files changed, 9 insertions, 3 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index d416b65ccf..f2404d40a5 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -757,8 +757,14 @@ class Model(six.with_metaclass(ModelBase)):
return update_fields is not None or filtered.exists()
if self._meta.select_on_save and not forced_update:
if filtered.exists():
- filtered._update(values)
- return True
+ # It may happen that the object is deleted from the DB right after
+ # this check, causing the subsequent UPDATE to return zero matching
+ # rows. The same result can occur in some rare cases when the
+ # database returns zero despite the UPDATE being executed
+ # successfully (a row is matched and updated). In order to
+ # distinguish these two cases, the object's existence in the
+ # database is again checked for if the UPDATE query returns 0.
+ return filtered._update(values) > 0 or filtered.exists()
else:
return False
return filtered._update(values) > 0
diff --git a/tests/basic/tests.py b/tests/basic/tests.py
index 4f3e8a958f..a10e38f2a4 100644
--- a/tests/basic/tests.py
+++ b/tests/basic/tests.py
@@ -701,7 +701,7 @@ class SelectOnSaveTests(TestCase):
try:
Article._base_manager.__class__ = FakeManager
asos = ArticleSelectOnSave.objects.create(pub_date=datetime.now())
- with self.assertNumQueries(2):
+ with self.assertNumQueries(3):
asos.save()
self.assertTrue(FakeQuerySet.called)
# This is not wanted behavior, but this is how Django has always