summaryrefslogtreecommitdiff
path: root/tests/invalid_models_tests/test_relative_fields.py
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/invalid_models_tests/test_relative_fields.py
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/invalid_models_tests/test_relative_fields.py')
-rw-r--r--tests/invalid_models_tests/test_relative_fields.py198
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), [])