From e02fc588893485e5f5e509cdb67c63d8e1a45b31 Mon Sep 17 00:00:00 2001 From: Mariana Date: Wed, 26 Jul 2023 23:58:58 +0100 Subject: [PATCH] Fixed #34586 -- Made QuerySet.create() raise ValueError for reverse one-to-many relations. --- django/db/models/options.py | 11 +++++++++++ django/db/models/query.py | 9 +++++++++ tests/one_to_one/tests.py | 23 +++++++++++++++++++++++ 3 files changed, 43 insertions(+) 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", @@ -992,6 +993,16 @@ class Options: names.append(field.attname) 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): """ 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`