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/invalid_models_tests/test_relative_fields.py | |
| 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/invalid_models_tests/test_relative_fields.py')
| -rw-r--r-- | tests/invalid_models_tests/test_relative_fields.py | 198 |
1 files changed, 197 insertions, 1 deletions
diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index ed6d39f7c6..e73f22ab41 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -3,7 +3,8 @@ from unittest import mock from django.core.checks import Error from django.core.checks import Warning as DjangoWarning from django.db import connection, models -from django.test.testcases import SimpleTestCase +from django.test import skipUnlessDBFeature +from django.test.testcases import SimpleTestCase, TestCase from django.test.utils import isolate_apps, modify_settings, override_settings @@ -751,6 +752,29 @@ class RelativeFieldTests(SimpleTestCase): ], ) + def test_on_delete_db_set_null_on_non_nullable_field(self): + class Person(models.Model): + pass + + class Model(models.Model): + foreign_key = models.ForeignKey("Person", models.DB_SET_NULL) + + field = Model._meta.get_field("foreign_key") + self.assertEqual( + field.check(), + [ + Error( + "Field specifies on_delete=DB_SET_NULL, but cannot be null.", + hint=( + "Set null=True argument on the field, or change the on_delete " + "rule." + ), + obj=field, + id="fields.E320", + ), + ], + ) + def test_on_delete_set_default_without_default_value(self): class Person(models.Model): pass @@ -2259,3 +2283,175 @@ class M2mThroughFieldsTests(SimpleTestCase): ), ], ) + + +@isolate_apps("invalid_models_tests") +class DatabaseLevelOnDeleteTests(TestCase): + + def test_db_set_default_support(self): + class Parent(models.Model): + pass + + class Child(models.Model): + parent = models.ForeignKey( + Parent, models.DB_SET_DEFAULT, db_default=models.Value(1) + ) + + field = Child._meta.get_field("parent") + expected = ( + [] + if connection.features.supports_on_delete_db_default + else [ + Error( + f"{connection.display_name} does not support a DB_SET_DEFAULT.", + hint="Change the on_delete rule to SET_DEFAULT.", + obj=field, + id="fields.E324", + ) + ] + ) + self.assertEqual(field.check(databases=self.databases), expected) + + def test_db_set_default_required_db_features(self): + class Parent(models.Model): + pass + + class Child(models.Model): + parent = models.ForeignKey( + Parent, models.DB_SET_DEFAULT, db_default=models.Value(1) + ) + + class Meta: + required_db_features = {"supports_on_delete_db_default"} + + field = Child._meta.get_field("parent") + self.assertEqual(field.check(databases=self.databases), []) + + @skipUnlessDBFeature("supports_on_delete_db_default") + def test_db_set_default_no_db_default(self): + class Parent(models.Model): + pass + + class Child(models.Model): + parent = models.ForeignKey(Parent, models.DB_SET_DEFAULT) + + field = Child._meta.get_field("parent") + self.assertEqual( + field.check(databases=self.databases), + [ + Error( + "Field specifies on_delete=DB_SET_DEFAULT, but has no db_default " + "value.", + hint="Set a db_default value, or change the on_delete rule.", + obj=field, + id="fields.E322", + ) + ], + ) + + def test_python_db_chain(self): + class GrandParent(models.Model): + pass + + class Parent(models.Model): + grand_parent = models.ForeignKey(GrandParent, models.DB_CASCADE) + + class Child(models.Model): + parent = models.ForeignKey(Parent, models.RESTRICT) + + field = Child._meta.get_field("parent") + self.assertEqual( + field.check(databases=self.databases), + [ + Error( + "Field specifies Python-level on_delete variant, but referenced " + "model uses database-level variant.", + hint=( + "Use either database or Python on_delete variants uniformly in " + "the references chain." + ), + obj=field, + id="fields.E323", + ) + ], + ) + + def test_db_python_chain(self): + class GrandParent(models.Model): + pass + + class Parent(models.Model): + grand_parent = models.ForeignKey(GrandParent, models.CASCADE) + + class Child(models.Model): + parent = models.ForeignKey(Parent, models.DB_SET_NULL, null=True) + + field = Child._meta.get_field("parent") + self.assertEqual( + field.check(databases=self.databases), + [ + Error( + "Field specifies database-level on_delete variant, but referenced " + "model uses Python-level variant.", + hint=( + "Use either database or Python on_delete variants uniformly in " + "the references chain." + ), + obj=field, + id="fields.E323", + ) + ], + ) + + def test_db_python_chain_auto_created(self): + class GrandParent(models.Model): + pass + + class Parent(GrandParent): + pass + + class Child(models.Model): + parent = models.ForeignKey(Parent, on_delete=models.DB_CASCADE) + + field = Child._meta.get_field("parent") + self.assertEqual( + field.check(databases=self.databases), + [ + Error( + "Field specifies database-level on_delete variant, but referenced " + "model uses Python-level variant.", + hint=( + "Use either database or Python on_delete variants uniformly in " + "the references chain." + ), + obj=field, + id="fields.E323", + ) + ], + ) + + def test_db_do_nothing_chain(self): + class GrandParent(models.Model): + pass + + class Parent(models.Model): + grand_parent = models.ForeignKey(GrandParent, models.DO_NOTHING) + + class Child(models.Model): + parent = models.ForeignKey(Parent, models.DB_SET_NULL, null=True) + + field = Child._meta.get_field("parent") + self.assertEqual(field.check(databases=self.databases), []) + + def test_do_nothing_db_chain(self): + class GrandParent(models.Model): + pass + + class Parent(models.Model): + grand_parent = models.ForeignKey(GrandParent, models.DB_SET_NULL, null=True) + + class Child(models.Model): + parent = models.ForeignKey(Parent, models.DO_NOTHING) + + field = Child._meta.get_field("parent") + self.assertEqual(field.check(databases=self.databases), []) |
