mirror of
https://github.com/django/django.git
synced 2025-10-27 15:46:10 +00:00
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>
This commit is contained in:
@@ -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), [])
|
||||
|
||||
Reference in New Issue
Block a user