summaryrefslogtreecommitdiff
path: root/tests/delete
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2025-10-18 15:03:50 +0200
committerGitHub <noreply@github.com>2025-10-18 15:03:50 +0200
commit0c487aa3a7b2417481bf48c1e5355c855873e210 (patch)
tree33b92a3bdf11f66d0f67fe4b4338084c7028c709 /tests/delete
parentb1e0262c9f9d11eae6230b51c5aa5d71122d5f05 (diff)
Fixed #21961 -- Added support for database-level delete options for ForeignKey.
Thanks Simon Charette for pair programming. Co-authored-by: Nick Stefan <NickStefan12@gmail.com> Co-authored-by: Akash Kumar Sen <71623442+Akash-Kumar-Sen@users.noreply.github.com> Co-authored-by: Simon Charette <charette.s@gmail.com>
Diffstat (limited to 'tests/delete')
-rw-r--r--tests/delete/models.py49
-rw-r--r--tests/delete/tests.py51
2 files changed, 100 insertions, 0 deletions
diff --git a/tests/delete/models.py b/tests/delete/models.py
index 7f123b3396..bd9caf42a7 100644
--- a/tests/delete/models.py
+++ b/tests/delete/models.py
@@ -41,6 +41,46 @@ class RChildChild(RChild):
pass
+class RelatedDbOptionGrandParent(models.Model):
+ pass
+
+
+class RelatedDbOptionParent(models.Model):
+ p = models.ForeignKey(RelatedDbOptionGrandParent, models.DB_CASCADE, null=True)
+
+
+class RelatedDbOption(models.Model):
+ name = models.CharField(max_length=30)
+ db_setnull = models.ForeignKey(
+ RelatedDbOptionParent,
+ models.DB_SET_NULL,
+ null=True,
+ related_name="db_setnull_set",
+ )
+ db_cascade = models.ForeignKey(
+ RelatedDbOptionParent, models.DB_CASCADE, related_name="db_cascade_set"
+ )
+
+
+class SetDefaultDbModel(models.Model):
+ db_setdefault = models.ForeignKey(
+ RelatedDbOptionParent,
+ models.DB_SET_DEFAULT,
+ db_default=models.Value(1),
+ related_name="db_setdefault_set",
+ )
+ db_setdefault_none = models.ForeignKey(
+ RelatedDbOptionParent,
+ models.DB_SET_DEFAULT,
+ db_default=None,
+ null=True,
+ related_name="db_setnull_nullable_set",
+ )
+
+ class Meta:
+ required_db_features = {"supports_on_delete_db_default"}
+
+
class A(models.Model):
name = models.CharField(max_length=30)
@@ -119,6 +159,15 @@ def create_a(name):
return a
+def create_related_db_option(name):
+ a = RelatedDbOption(name=name)
+ for name in ["db_setnull", "db_cascade"]:
+ r = RelatedDbOptionParent.objects.create()
+ setattr(a, name, r)
+ a.save()
+ return a
+
+
class M(models.Model):
m2m = models.ManyToManyField(R, related_name="m_set")
m2m_through = models.ManyToManyField(R, through="MR", related_name="m_through_set")
diff --git a/tests/delete/tests.py b/tests/delete/tests.py
index 59140b5c62..8d525d1e5f 100644
--- a/tests/delete/tests.py
+++ b/tests/delete/tests.py
@@ -34,11 +34,16 @@ from .models import (
RChild,
RChildChild,
Referrer,
+ RelatedDbOption,
+ RelatedDbOptionGrandParent,
+ RelatedDbOptionParent,
RProxy,
S,
+ SetDefaultDbModel,
T,
User,
create_a,
+ create_related_db_option,
get_default_r,
)
@@ -76,18 +81,48 @@ class OnDeleteTests(TestCase):
a = A.objects.get(pk=a.pk)
self.assertIsNone(a.setnull)
+ def test_db_setnull(self):
+ a = create_related_db_option("db_setnull")
+ a.db_setnull.delete()
+ a = RelatedDbOption.objects.get(pk=a.pk)
+ self.assertIsNone(a.db_setnull)
+
def test_setdefault(self):
a = create_a("setdefault")
a.setdefault.delete()
a = A.objects.get(pk=a.pk)
self.assertEqual(self.DEFAULT, a.setdefault.pk)
+ @skipUnlessDBFeature("supports_on_delete_db_default")
+ def test_db_setdefault(self):
+ # Object cannot be created on the module initialization, use hardcoded
+ # PKs instead.
+ r = RelatedDbOptionParent.objects.create(pk=2)
+ default_r = RelatedDbOptionParent.objects.create(pk=1)
+ set_default_db_obj = SetDefaultDbModel.objects.create(db_setdefault=r)
+ set_default_db_obj.db_setdefault.delete()
+ set_default_db_obj = SetDefaultDbModel.objects.get(pk=set_default_db_obj.pk)
+ self.assertEqual(set_default_db_obj.db_setdefault, default_r)
+
def test_setdefault_none(self):
a = create_a("setdefault_none")
a.setdefault_none.delete()
a = A.objects.get(pk=a.pk)
self.assertIsNone(a.setdefault_none)
+ @skipUnlessDBFeature("supports_on_delete_db_default")
+ def test_db_setdefault_none(self):
+ # Object cannot be created on the module initialization, use hardcoded
+ # PKs instead.
+ r = RelatedDbOptionParent.objects.create(pk=2)
+ default_r = RelatedDbOptionParent.objects.create(pk=1)
+ set_default_db_obj = SetDefaultDbModel.objects.create(
+ db_setdefault_none=r, db_setdefault=default_r
+ )
+ set_default_db_obj.db_setdefault_none.delete()
+ set_default_db_obj = SetDefaultDbModel.objects.get(pk=set_default_db_obj.pk)
+ self.assertIsNone(set_default_db_obj.db_setdefault_none)
+
def test_cascade(self):
a = create_a("cascade")
a.cascade.delete()
@@ -359,6 +394,22 @@ class DeletionTests(TestCase):
self.assertNumQueries(5, s.delete)
self.assertFalse(S.objects.exists())
+ def test_db_cascade(self):
+ related_db_op = RelatedDbOptionParent.objects.create(
+ p=RelatedDbOptionGrandParent.objects.create()
+ )
+ RelatedDbOption.objects.bulk_create(
+ [
+ RelatedDbOption(db_cascade=related_db_op)
+ for _ in range(2 * GET_ITERATOR_CHUNK_SIZE)
+ ]
+ )
+ with self.assertNumQueries(1):
+ results = related_db_op.delete()
+ self.assertEqual(results, (1, {"delete.RelatedDbOptionParent": 1}))
+ self.assertFalse(RelatedDbOption.objects.exists())
+ self.assertFalse(RelatedDbOptionParent.objects.exists())
+
def test_instance_update(self):
deleted = []
related_setnull_sets = []