diff --git a/django/db/models/base.py b/django/db/models/base.py index 7b7a8833cc6f..61a5ee91856a 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -28,7 +28,14 @@ router, transaction, ) -from django.db.models import NOT_PROVIDED, ExpressionWrapper, IntegerField, Max, Value +from django.db.models import ( + NOT_PROVIDED, + Expression, + ExpressionWrapper, + IntegerField, + Max, + Value, +) from django.db.models.constants import LOOKUP_SEP from django.db.models.deletion import CASCADE, DO_NOTHING, Collector, DatabaseOnDelete from django.db.models.expressions import DatabaseDefault @@ -1295,7 +1302,9 @@ def _prepare_related_fields_for_save(self, operation_name, fields=None): "%s() prohibited to prevent data loss due to unsaved " "related object '%s'." % (operation_name, field.name) ) - elif getattr(self, field.attname) in field.empty_values: + if ( + field_val := getattr(self, field.attname) + ) in field.empty_values or isinstance(field_val, Expression): # Set related object if it has been saved after an # assignment. setattr(self, field.name, obj) diff --git a/tests/field_defaults/models.py b/tests/field_defaults/models.py index 4275564113c2..e2b2ac487032 100644 --- a/tests/field_defaults/models.py +++ b/tests/field_defaults/models.py @@ -13,7 +13,7 @@ from decimal import Decimal from django.db import models -from django.db.models.functions import Coalesce, ExtractYear, Now, Pi +from django.db.models.functions import UUID4, Coalesce, ExtractYear, Now, Pi from django.db.models.lookups import GreaterThan @@ -78,3 +78,23 @@ class DBDefaultsOneToOnePK(models.Model): db_default="kr", on_delete=models.CASCADE, ) + + +class DBDefaultsFunctionPK(models.Model): + uuid = models.UUIDField(primary_key=True, db_default=UUID4()) + + class Meta: + required_db_features = { + "supports_uuid4_function_in_default", + "supports_expression_defaults", + } + + +class DBDefaultsFunctionFK(models.Model): + parent = models.ForeignKey(DBDefaultsFunctionPK, models.CASCADE) + + class Meta: + required_db_features = { + "supports_uuid4_function_in_default", + "supports_expression_defaults", + } diff --git a/tests/field_defaults/tests.py b/tests/field_defaults/tests.py index dd92b9ad447f..7343015ea08b 100644 --- a/tests/field_defaults/tests.py +++ b/tests/field_defaults/tests.py @@ -13,7 +13,7 @@ OrderByList, RawSQL, ) -from django.db.models.functions import Collate +from django.db.models.functions import UUID4, Collate from django.db.models.lookups import GreaterThan from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature @@ -23,6 +23,8 @@ DBDefaults, DBDefaultsFK, DBDefaultsFunction, + DBDefaultsFunctionFK, + DBDefaultsFunctionPK, DBDefaultsOneToOnePK, DBDefaultsPK, ) @@ -155,6 +157,18 @@ def test_primary_key_assigned_db_default_expression(self): # DatabaseDefault is used, but this behavior varies across databases. self.assertNotEqual(obj.language_code_id, "tr") + @skipUnlessDBFeature( + "can_return_columns_from_insert", + "supports_expression_defaults", + "supports_uuid4_function_in_default", + ) + def test_foreign_key_to_parent_with_expression_pk(self): + parent = DBDefaultsFunctionPK(pk=UUID4()) + obj = DBDefaultsFunctionFK(parent=parent) + parent.save() + obj.save() + self.assertEqual(obj.parent_id, parent.pk) + @skipUnlessDBFeature("supports_expression_defaults") def test_case_when_db_default_returning(self): m = DBDefaultsFunction.objects.create()