Re: [Django] #37238: 6.1: save() falls back to python default for primary key that should come from related instance instead

0 views
Skip to first unread message

Django

unread,
Jul 28, 2026, 4:51:24 PM (22 hours ago) Jul 28
to django-...@googlegroups.com
#37238: 6.1: save() falls back to python default for primary key that should come
from related instance instead
-------------------------------------+-------------------------------------
Reporter: Jacob Walls | Owner: Jacob
| Walls
Type: Bug | Status: assigned
Component: Database layer | Version: 6.1
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Old description:

> When saving an object whose primary key is a `OneToOneField`, and when
> that value is meant to come from an unsaved related object, and when that
> related object uses `db_default`,
> a2348c85fc6c20087935c74cd99340dd4ef2dcdc has the unintentional effect of
> falling back to the field's `default` instead of getting it from the
> related object once saved.
>
> To demonstrate, adjust a test being drafted in this
> [https://github.com/django/django/pull/21677 other PR] (where the
> `bulk_create()` case was considered and handled) to use `save()` instead:
>
> Passes before a2348c85fc6c20087935c74cd99340dd4ef2dcdc on SQLite. Other
> databases have other behaviors, like raising integrity errors, so we
> might need some test skips here.
>
> {{{#!diff
> diff --git a/tests/bulk_create/models.py b/tests/bulk_create/models.py
> index bc9beba990..34ea0bf3f8 100644
> --- a/tests/bulk_create/models.py
> +++ b/tests/bulk_create/models.py
> @@ -157,3 +157,22 @@ class DbDefaultPrimaryKey(models.Model):
>
> class Meta:
> required_db_features = {"supports_expression_defaults"}
> +
> +
> +class IntegerDbDefaultPrimaryKey(models.Model):
> + id = models.IntegerField(primary_key=True, db_default=42)
> +
> + class Meta:
> + required_db_features = {"supports_expression_defaults"}
> +
> +
> +class RelatedToDbDefaultPrimaryKey(models.Model):
> + related = models.OneToOneField(
> + IntegerDbDefaultPrimaryKey,
> + on_delete=models.CASCADE,
> + primary_key=True,
> + default=1,
> + )
> +
> + class Meta:
> + required_db_features = {"supports_expression_defaults"}
> diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py
> index 397fcb9186..2692d62fb1 100644
> --- a/tests/bulk_create/tests.py
> +++ b/tests/bulk_create/tests.py
> @@ -27,6 +27,7 @@ from .models import (
> DbDefaultModel,
> DbDefaultPrimaryKey,
> FieldsWithDbColumns,
> + IntegerDbDefaultPrimaryKey,
> NoFields,
> NullableFields,
> Pizzeria,
> @@ -35,6 +36,7 @@ from .models import (
> ProxyMultiProxyCountry,
> ProxyProxyCountry,
> RelatedModel,
> + RelatedToDbDefaultPrimaryKey,
> Restaurant,
> SmallAutoFieldModel,
> State,
> @@ -439,6 +441,19 @@ class BulkCreateTests(TestCase):
> child = NullableFields.objects.get(integer_field=88)
> self.assertEqual(child.auto_field, parent)
>
> + @skipUnlessDBFeature(
> + "can_return_columns_from_insert", "supports_expression_defaults"
> + )
> + def
> test_pk_from_related_instance_saved_after_init_with_defaults(self):
> + # Ensure that the related field's Python default references a
> + # different valid object.
> + IntegerDbDefaultPrimaryKey.objects.create(pk=1)
> + related_object = IntegerDbDefaultPrimaryKey()
> + obj = RelatedToDbDefaultPrimaryKey(related=related_object)
> + related_object.save()
> + obj.save()
> + self.assertEqual(obj.pk, related_object.pk)
> +
> def test_unsaved_parent(self):
> parent = NoFields()
> msg = (
> }}}
>
> {{{#!py
> ======================================================================
> FAIL: test_pk_from_related_instance_saved_after_init_with_defaults
> (bulk_create.tests.BulkCreateTests.test_pk_from_related_instance_saved_after_init_with_defaults)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
> File "/Users/jwalls/django/tests/bulk_create/tests.py", line 455, in
> test_pk_from_related_instance_saved_after_init_with_defaults
> self.assertEqual(obj.pk, related_object.pk)
> ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
> AssertionError: 1 != 42
>
> ----------------------------------------------------------------------
> Ran 1 test in 0.007s
>
> FAILED (failures=1)
> }}}

New description:

When saving an object whose primary key is a `OneToOneField`, and when
that value is meant to come from an unsaved related object, and when that
related object uses `db_default`, a2348c85fc6c20087935c74cd99340dd4ef2dcdc
has the unintentional effect of falling back to the field's `default`
instead of getting it from the related object once saved. EDIT: before
6.1, the fallback was to the related object's db_default expression, which
isn't quite right either, so my current proposal is to just match the 6.0
behavior and then bounce the rest to a follow-up issue.

To demonstrate, adjust a test being drafted in this
[https://github.com/django/django/pull/21677 other PR] (where the
`bulk_create()` case was considered and handled) to use `save()` instead:

Passes before a2348c85fc6c20087935c74cd99340dd4ef2dcdc on SQLite. Other
databases have other behaviors, like raising integrity errors, so we might
need some test skips here. This should be rewritten to use Now() instead
of a constant value; I found the constant value was easier to debug.

{{{#!diff
diff --git a/tests/bulk_create/models.py b/tests/bulk_create/models.py
index bc9beba990..34ea0bf3f8 100644
--- a/tests/bulk_create/models.py
+++ b/tests/bulk_create/models.py
@@ -157,3 +157,22 @@ class DbDefaultPrimaryKey(models.Model):

class Meta:
required_db_features = {"supports_expression_defaults"}
+
+
+class IntegerDbDefaultPrimaryKey(models.Model):
+ id = models.IntegerField(primary_key=True, db_default=42)
+
+ class Meta:
+ required_db_features = {"supports_expression_defaults"}
+
+
+class RelatedToDbDefaultPrimaryKey(models.Model):
+ related = models.OneToOneField(
+ IntegerDbDefaultPrimaryKey,
+ on_delete=models.CASCADE,
+ primary_key=True,
+ default=1,
+ )
+
+ class Meta:
+ required_db_features = {"supports_expression_defaults"}
diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py
index 397fcb9186..2692d62fb1 100644
--- a/tests/bulk_create/tests.py
+++ b/tests/bulk_create/tests.py
@@ -27,6 +27,7 @@ from .models import (
DbDefaultModel,
DbDefaultPrimaryKey,
FieldsWithDbColumns,
+ IntegerDbDefaultPrimaryKey,
NoFields,
NullableFields,
Pizzeria,
@@ -35,6 +36,7 @@ from .models import (
ProxyMultiProxyCountry,
ProxyProxyCountry,
RelatedModel,
+ RelatedToDbDefaultPrimaryKey,
Restaurant,
SmallAutoFieldModel,
State,
@@ -439,6 +441,19 @@ class BulkCreateTests(TestCase):
child = NullableFields.objects.get(integer_field=88)
self.assertEqual(child.auto_field, parent)

+ @skipUnlessDBFeature(
+ "can_return_columns_from_insert", "supports_expression_defaults"
+ )
+ def
test_pk_from_related_instance_saved_after_init_with_defaults(self):
+ # Ensure that the related field's Python default references a
+ # different valid object.
+ IntegerDbDefaultPrimaryKey.objects.create(pk=1)
+ related_object = IntegerDbDefaultPrimaryKey()
+ obj = RelatedToDbDefaultPrimaryKey(related=related_object)
+ related_object.save()
+ obj.save()
+ self.assertEqual(obj.pk, related_object.pk)
+
def test_unsaved_parent(self):
parent = NoFields()
msg = (
}}}

{{{#!py
======================================================================
FAIL: test_pk_from_related_instance_saved_after_init_with_defaults
(bulk_create.tests.BulkCreateTests.test_pk_from_related_instance_saved_after_init_with_defaults)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/jwalls/django/tests/bulk_create/tests.py", line 455, in
test_pk_from_related_instance_saved_after_init_with_defaults
self.assertEqual(obj.pk, related_object.pk)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 != 42

----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (failures=1)
}}}

--
Comment (by Jacob Walls):

Ah, in no recent versions does the primary key get inserted with the value
from the related object. (It comes from the related object's db_default
expression, which can be a separate non-release-blocker ticket.) But it at
least shouldn't start coming from the python default in 6.1, I gather.
--
Ticket URL: <https://code.djangoproject.com/ticket/37238#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jul 28, 2026, 6:03:00 PM (21 hours ago) Jul 28
to django-...@googlegroups.com
#37238: 6.1: save() falls back to python default for primary key that should come
from related instance instead
-------------------------------------+-------------------------------------
Reporter: Jacob Walls | Owner: Jacob
| Walls
Type: Bug | Status: assigned
Component: Database layer | Version: 6.1
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* has_patch: 0 => 1

Comment:

[https://github.com/django/django/pull/21693 PR] with a more
understandable test 😅
--
Ticket URL: <https://code.djangoproject.com/ticket/37238#comment:3>

Django

unread,
5:55 AM (9 hours ago) 5:55 AM
to django-...@googlegroups.com
#37238: 6.1: save() falls back to python default for primary key that should come
from related instance instead
-------------------------------------+-------------------------------------
Reporter: Jacob Walls | Owner: Jacob
| Walls
Type: Bug | Status: closed
Component: Database layer | Version: 6.1
(models, ORM) |
Severity: Release blocker | Resolution: duplicate
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce):

* resolution: => duplicate
* status: assigned => closed

Comment:

The patch for #37239 appears to resolve this ticket, and the issue sounds
similar, so I am going to mark as a duplicate
If there are test cases where the fix for #37239 wouldn't fix this but
demonstrate this issue, we can reopen
--
Ticket URL: <https://code.djangoproject.com/ticket/37238#comment:4>

Django

unread,
1:24 PM (2 hours ago) 1:24 PM
to django-...@googlegroups.com
#37238: 6.1: save() falls back to python default for primary key that should come
from related instance instead
-------------------------------------+-------------------------------------
Reporter: Jacob Walls | Owner: Jacob
| Walls
Type: Bug | Status: new
Component: Database layer | Version: 6.1
(models, ORM) |
Severity: Release blocker | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* resolution: duplicate =>
* status: closed => new

Comment:

Good spot, added a more distinct test case. There's also the issue of
whether the other issue will be backported (probably not), but this one
should be since it's a 6.1 regression.
--
Ticket URL: <https://code.djangoproject.com/ticket/37238#comment:5>
Reply all
Reply to author
Forward
0 new messages