summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariana <mmariana.pereira.20@gmail.com>2023-07-26 23:58:58 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-08-09 06:59:40 +0200
commite02fc588893485e5f5e509cdb67c63d8e1a45b31 (patch)
tree30de5cf24d654b6d807096474548e0162e71ad43
parentaa3cb3f37265be37d892e2b391ff023e9caee2a4 (diff)
Fixed #34586 -- Made QuerySet.create() raise ValueError for reverse one-to-many relations.
-rw-r--r--django/db/models/options.py11
-rw-r--r--django/db/models/query.py9
-rw-r--r--tests/one_to_one/tests.py23
3 files changed, 43 insertions, 0 deletions
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 64e5ff53ea..76017fc8b5 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -90,6 +90,7 @@ class Options:
"concrete_fields",
"local_concrete_fields",
"_non_pk_concrete_field_names",
+ "_reverse_one_to_one_field_names",
"_forward_fields_map",
"managers",
"managers_map",
@@ -993,6 +994,16 @@ class Options:
return frozenset(names)
@cached_property
+ def _reverse_one_to_one_field_names(self):
+ """
+ Return a set of reverse one to one field names pointing to the current
+ model.
+ """
+ return frozenset(
+ field.name for field in self.related_objects if field.one_to_one
+ )
+
+ @cached_property
def db_returning_fields(self):
"""
Private API intended only to be used by Django itself.
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 95323b6f9b..fcc725b3fc 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -662,6 +662,15 @@ class QuerySet(AltersData):
Create a new object with the given kwargs, saving it to the database
and returning the created object.
"""
+ reverse_one_to_one_fields = frozenset(kwargs).intersection(
+ self.model._meta._reverse_one_to_one_field_names
+ )
+ if reverse_one_to_one_fields:
+ raise ValueError(
+ "The following fields do not exist in this model: %s"
+ % ", ".join(reverse_one_to_one_fields)
+ )
+
obj = self.model(**kwargs)
self._for_write = True
obj.save(force_insert=True, using=self.db)
diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py
index 3c92445af6..65efee6074 100644
--- a/tests/one_to_one/tests.py
+++ b/tests/one_to_one/tests.py
@@ -524,6 +524,29 @@ class OneToOneTests(TestCase):
Director._meta.base_manager_name = None
Director._meta._expire_cache()
+ def test_create_reverse_o2o_error(self):
+ msg = "The following fields do not exist in this model: restaurant"
+ with self.assertRaisesMessage(ValueError, msg):
+ Place.objects.create(restaurant=self.r1)
+
+ def test_get_or_create_reverse_o2o_error(self):
+ msg = "The following fields do not exist in this model: restaurant"
+ r2 = Restaurant.objects.create(
+ place=self.p2, serves_hot_dogs=True, serves_pizza=False
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ Place.objects.get_or_create(name="nonexistent", defaults={"restaurant": r2})
+
+ def test_update_or_create_reverse_o2o_error(self):
+ msg = "The following fields do not exist in this model: restaurant"
+ r2 = Restaurant.objects.create(
+ place=self.p2, serves_hot_dogs=True, serves_pizza=False
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ Place.objects.update_or_create(
+ name="nonexistent", defaults={"restaurant": r2}
+ )
+
def test_hasattr_related_object(self):
# The exception raised on attribute access when a related object
# doesn't exist should be an instance of a subclass of `AttributeError`