diff options
| author | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2025-10-18 15:03:50 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-18 15:03:50 +0200 |
| commit | 0c487aa3a7b2417481bf48c1e5355c855873e210 (patch) | |
| tree | 33b92a3bdf11f66d0f67fe4b4338084c7028c709 /tests/delete | |
| parent | b1e0262c9f9d11eae6230b51c5aa5d71122d5f05 (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.py | 49 | ||||
| -rw-r--r-- | tests/delete/tests.py | 51 |
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 = [] |
