Fixed #34586 -- Made QuerySet.create() raise ValueError for reverse one-to-many relations.

This commit is contained in:
Mariana 2023-07-26 23:58:58 +01:00 committed by Mariusz Felisiak
parent aa3cb3f372
commit e02fc58889
3 changed files with 43 additions and 0 deletions

View File

@ -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):
"""

View File

@ -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)

View File

@ -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`