django/tests/force_insert_update/tests.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

184 lines
7.5 KiB
Python
Raw Normal View History

from django.db import DatabaseError, IntegrityError, models, transaction
from django.test import TestCase
from .models import (
Counter,
DiamondSubSubCounter,
InheritedCounter,
OtherSubCounter,
ProxyCounter,
SubCounter,
SubSubCounter,
WithCustomPK,
)
class ForceTests(TestCase):
def test_force_update(self):
c = Counter.objects.create(name="one", value=1)
# The normal case
c.value = 2
c.save()
# Same thing, via an update
c.value = 3
c.save(force_update=True)
# Won't work because force_update and force_insert are mutually
# exclusive
c.value = 4
msg = "Cannot force both insert and updating in model saving."
with self.assertRaisesMessage(ValueError, msg):
Fixed #21134 -- Prevented queries in broken transactions. Squashed commit of the following: commit 63ddb271a44df389b2c302e421fc17b7f0529755 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 29 22:51:00 2013 +0200 Clarified interactions between atomic and exceptions. commit 2899ec299228217c876ba3aa4024e523a41c8504 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:45:32 2013 +0200 Fixed TransactionManagementError in tests. Previous commit introduced an additional check to prevent running queries in transactions that will be rolled back, which triggered a few failures in the tests. In practice using transaction.atomic instead of the low-level savepoint APIs was enough to fix the problems. commit 4a639b059ea80aeb78f7f160a7d4b9f609b9c238 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Tue Sep 24 22:24:17 2013 +0200 Allowed nesting constraint_checks_disabled inside atomic. Since MySQL handles transactions loosely, this isn't a problem. commit 2a4ab1cb6e83391ff7e25d08479e230ca564bfef Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sat Sep 21 18:43:12 2013 +0200 Prevented running queries in transactions that will be rolled back. This avoids a counter-intuitive behavior in an edge case on databases with non-atomic transaction semantics. It prevents using savepoint_rollback() inside an atomic block without calling set_rollback(False) first, which is backwards-incompatible in tests. Refs #21134. commit 8e3db393853c7ac64a445b66e57f3620a3fde7b0 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:14:17 2013 +0200 Replaced manual savepoints by atomic blocks. This ensures the rollback flag is handled consistently in internal APIs.
2013-09-22 20:14:17 +00:00
c.save(force_insert=True, force_update=True)
# Try to update something that doesn't have a primary key in the first
# place.
c1 = Counter(name="two", value=2)
msg = "Cannot force an update in save() with no primary key."
with self.assertRaisesMessage(ValueError, msg):
Fixed #21134 -- Prevented queries in broken transactions. Squashed commit of the following: commit 63ddb271a44df389b2c302e421fc17b7f0529755 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 29 22:51:00 2013 +0200 Clarified interactions between atomic and exceptions. commit 2899ec299228217c876ba3aa4024e523a41c8504 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:45:32 2013 +0200 Fixed TransactionManagementError in tests. Previous commit introduced an additional check to prevent running queries in transactions that will be rolled back, which triggered a few failures in the tests. In practice using transaction.atomic instead of the low-level savepoint APIs was enough to fix the problems. commit 4a639b059ea80aeb78f7f160a7d4b9f609b9c238 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Tue Sep 24 22:24:17 2013 +0200 Allowed nesting constraint_checks_disabled inside atomic. Since MySQL handles transactions loosely, this isn't a problem. commit 2a4ab1cb6e83391ff7e25d08479e230ca564bfef Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sat Sep 21 18:43:12 2013 +0200 Prevented running queries in transactions that will be rolled back. This avoids a counter-intuitive behavior in an edge case on databases with non-atomic transaction semantics. It prevents using savepoint_rollback() inside an atomic block without calling set_rollback(False) first, which is backwards-incompatible in tests. Refs #21134. commit 8e3db393853c7ac64a445b66e57f3620a3fde7b0 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:14:17 2013 +0200 Replaced manual savepoints by atomic blocks. This ensures the rollback flag is handled consistently in internal APIs.
2013-09-22 20:14:17 +00:00
with transaction.atomic():
c1.save(force_update=True)
c1.save(force_insert=True)
# Won't work because we can't insert a pk of the same value.
c.value = 5
Fixed #21134 -- Prevented queries in broken transactions. Squashed commit of the following: commit 63ddb271a44df389b2c302e421fc17b7f0529755 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 29 22:51:00 2013 +0200 Clarified interactions between atomic and exceptions. commit 2899ec299228217c876ba3aa4024e523a41c8504 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:45:32 2013 +0200 Fixed TransactionManagementError in tests. Previous commit introduced an additional check to prevent running queries in transactions that will be rolled back, which triggered a few failures in the tests. In practice using transaction.atomic instead of the low-level savepoint APIs was enough to fix the problems. commit 4a639b059ea80aeb78f7f160a7d4b9f609b9c238 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Tue Sep 24 22:24:17 2013 +0200 Allowed nesting constraint_checks_disabled inside atomic. Since MySQL handles transactions loosely, this isn't a problem. commit 2a4ab1cb6e83391ff7e25d08479e230ca564bfef Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sat Sep 21 18:43:12 2013 +0200 Prevented running queries in transactions that will be rolled back. This avoids a counter-intuitive behavior in an edge case on databases with non-atomic transaction semantics. It prevents using savepoint_rollback() inside an atomic block without calling set_rollback(False) first, which is backwards-incompatible in tests. Refs #21134. commit 8e3db393853c7ac64a445b66e57f3620a3fde7b0 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:14:17 2013 +0200 Replaced manual savepoints by atomic blocks. This ensures the rollback flag is handled consistently in internal APIs.
2013-09-22 20:14:17 +00:00
with self.assertRaises(IntegrityError):
with transaction.atomic():
c.save(force_insert=True)
# Trying to update should still fail, even with manual primary keys, if
# the data isn't in the database already.
obj = WithCustomPK(name=1, value=1)
msg = "Forced update did not affect any rows."
with self.assertRaisesMessage(DatabaseError, msg):
Fixed #21134 -- Prevented queries in broken transactions. Squashed commit of the following: commit 63ddb271a44df389b2c302e421fc17b7f0529755 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 29 22:51:00 2013 +0200 Clarified interactions between atomic and exceptions. commit 2899ec299228217c876ba3aa4024e523a41c8504 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:45:32 2013 +0200 Fixed TransactionManagementError in tests. Previous commit introduced an additional check to prevent running queries in transactions that will be rolled back, which triggered a few failures in the tests. In practice using transaction.atomic instead of the low-level savepoint APIs was enough to fix the problems. commit 4a639b059ea80aeb78f7f160a7d4b9f609b9c238 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Tue Sep 24 22:24:17 2013 +0200 Allowed nesting constraint_checks_disabled inside atomic. Since MySQL handles transactions loosely, this isn't a problem. commit 2a4ab1cb6e83391ff7e25d08479e230ca564bfef Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sat Sep 21 18:43:12 2013 +0200 Prevented running queries in transactions that will be rolled back. This avoids a counter-intuitive behavior in an edge case on databases with non-atomic transaction semantics. It prevents using savepoint_rollback() inside an atomic block without calling set_rollback(False) first, which is backwards-incompatible in tests. Refs #21134. commit 8e3db393853c7ac64a445b66e57f3620a3fde7b0 Author: Aymeric Augustin <aymeric.augustin@m4x.org> Date: Sun Sep 22 22:14:17 2013 +0200 Replaced manual savepoints by atomic blocks. This ensures the rollback flag is handled consistently in internal APIs.
2013-09-22 20:14:17 +00:00
with transaction.atomic():
obj.save(force_update=True)
class InheritanceTests(TestCase):
def test_force_update_on_inherited_model(self):
a = InheritedCounter(name="count", value=1, tag="spam")
a.save()
a.save(force_update=True)
def test_force_update_on_proxy_model(self):
a = ProxyCounter(name="count", value=1)
a.save()
a.save(force_update=True)
def test_force_update_on_inherited_model_without_fields(self):
"""
Issue 13864: force_update fails on subclassed models, if they don't
specify custom fields.
"""
a = SubCounter(name="count", value=1)
a.save()
a.value = 2
a.save(force_update=True)
class ForceInsertInheritanceTests(TestCase):
def test_force_insert_not_bool_or_tuple(self):
msg = "force_insert must be a bool or tuple."
with self.assertRaisesMessage(TypeError, msg), transaction.atomic():
Counter().save(force_insert=1)
with self.assertRaisesMessage(TypeError, msg), transaction.atomic():
Counter().save(force_insert="test")
with self.assertRaisesMessage(TypeError, msg), transaction.atomic():
Counter().save(force_insert=[])
def test_force_insert_not_model(self):
msg = f"Invalid force_insert member. {object!r} must be a model subclass."
with self.assertRaisesMessage(TypeError, msg), transaction.atomic():
Counter().save(force_insert=(object,))
instance = Counter()
msg = f"Invalid force_insert member. {instance!r} must be a model subclass."
with self.assertRaisesMessage(TypeError, msg), transaction.atomic():
Counter().save(force_insert=(instance,))
def test_force_insert_not_base(self):
msg = "Invalid force_insert member. SubCounter must be a base of Counter."
with self.assertRaisesMessage(TypeError, msg):
Counter().save(force_insert=(SubCounter,))
def test_force_insert_false(self):
with self.assertNumQueries(3):
obj = SubCounter.objects.create(pk=1, value=0)
with self.assertNumQueries(2):
SubCounter(pk=obj.pk, value=1).save()
obj.refresh_from_db()
self.assertEqual(obj.value, 1)
with self.assertNumQueries(2):
SubCounter(pk=obj.pk, value=2).save(force_insert=False)
obj.refresh_from_db()
self.assertEqual(obj.value, 2)
with self.assertNumQueries(2):
SubCounter(pk=obj.pk, value=3).save(force_insert=())
obj.refresh_from_db()
self.assertEqual(obj.value, 3)
def test_force_insert_false_with_existing_parent(self):
parent = Counter.objects.create(pk=1, value=1)
with self.assertNumQueries(2):
SubCounter.objects.create(pk=parent.pk, value=2)
def test_force_insert_parent(self):
with self.assertNumQueries(3):
SubCounter(pk=1, value=1).save(force_insert=True)
# Force insert a new parent and don't UPDATE first.
with self.assertNumQueries(2):
SubCounter(pk=2, value=1).save(force_insert=(Counter,))
with self.assertNumQueries(2):
SubCounter(pk=3, value=1).save(force_insert=(models.Model,))
def test_force_insert_with_grandparent(self):
with self.assertNumQueries(4):
SubSubCounter(pk=1, value=1).save(force_insert=True)
# Force insert parents on all levels and don't UPDATE first.
with self.assertNumQueries(3):
SubSubCounter(pk=2, value=1).save(force_insert=(models.Model,))
with self.assertNumQueries(3):
SubSubCounter(pk=3, value=1).save(force_insert=(Counter,))
# Force insert only the last parent.
with self.assertNumQueries(4):
SubSubCounter(pk=4, value=1).save(force_insert=(SubCounter,))
def test_force_insert_with_existing_grandparent(self):
# Force insert only the last child.
grandparent = Counter.objects.create(pk=1, value=1)
with self.assertNumQueries(4):
SubSubCounter(pk=grandparent.pk, value=1).save(force_insert=True)
# Force insert a parent, and don't force insert a grandparent.
grandparent = Counter.objects.create(pk=2, value=1)
with self.assertNumQueries(3):
SubSubCounter(pk=grandparent.pk, value=1).save(force_insert=(SubCounter,))
# Force insert parents on all levels, grandparent conflicts.
grandparent = Counter.objects.create(pk=3, value=1)
with self.assertRaises(IntegrityError), transaction.atomic():
SubSubCounter(pk=grandparent.pk, value=1).save(force_insert=(Counter,))
def test_force_insert_diamond_mti(self):
# Force insert all parents.
with self.assertNumQueries(4):
DiamondSubSubCounter(pk=1, value=1).save(
force_insert=(Counter, SubCounter, OtherSubCounter)
)
with self.assertNumQueries(4):
DiamondSubSubCounter(pk=2, value=1).save(force_insert=(models.Model,))
# Force insert parents, and don't force insert a common grandparent.
with self.assertNumQueries(5):
DiamondSubSubCounter(pk=3, value=1).save(
force_insert=(SubCounter, OtherSubCounter)
)
grandparent = Counter.objects.create(pk=4, value=1)
with self.assertNumQueries(4):
DiamondSubSubCounter(pk=grandparent.pk, value=1).save(
force_insert=(SubCounter, OtherSubCounter),
)
# Force insert all parents, grandparent conflicts.
grandparent = Counter.objects.create(pk=5, value=1)
with self.assertRaises(IntegrityError), transaction.atomic():
DiamondSubSubCounter(pk=grandparent.pk, value=1).save(
force_insert=(models.Model,)
)