save() falls back to python default even when DatabaseDefault instance is assigned
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 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 --git a/tests/bulk_create/models.py b/tests/bulk_create/models.py
index bc9beba990..34ea0bf3f8 100644
|
a
|
b
|
class DbDefaultPrimaryKey(models.Model):
|
| 157 | 157 | |
| 158 | 158 | class Meta: |
| 159 | 159 | required_db_features = {"supports_expression_defaults"} |
| | 160 | |
| | 161 | |
| | 162 | class IntegerDbDefaultPrimaryKey(models.Model): |
| | 163 | id = models.IntegerField(primary_key=True, db_default=42) |
| | 164 | |
| | 165 | class Meta: |
| | 166 | required_db_features = {"supports_expression_defaults"} |
| | 167 | |
| | 168 | |
| | 169 | class RelatedToDbDefaultPrimaryKey(models.Model): |
| | 170 | related = models.OneToOneField( |
| | 171 | IntegerDbDefaultPrimaryKey, |
| | 172 | on_delete=models.CASCADE, |
| | 173 | primary_key=True, |
| | 174 | default=1, |
| | 175 | ) |
| | 176 | |
| | 177 | class Meta: |
| | 178 | 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
|
b
|
from .models import (
|
| 27 | 27 | DbDefaultModel, |
| 28 | 28 | DbDefaultPrimaryKey, |
| 29 | 29 | FieldsWithDbColumns, |
| | 30 | IntegerDbDefaultPrimaryKey, |
| 30 | 31 | NoFields, |
| 31 | 32 | NullableFields, |
| 32 | 33 | Pizzeria, |
| … |
… |
from .models import (
|
| 35 | 36 | ProxyMultiProxyCountry, |
| 36 | 37 | ProxyProxyCountry, |
| 37 | 38 | RelatedModel, |
| | 39 | RelatedToDbDefaultPrimaryKey, |
| 38 | 40 | Restaurant, |
| 39 | 41 | SmallAutoFieldModel, |
| 40 | 42 | State, |
| … |
… |
class BulkCreateTests(TestCase):
|
| 439 | 441 | child = NullableFields.objects.get(integer_field=88) |
| 440 | 442 | self.assertEqual(child.auto_field, parent) |
| 441 | 443 | |
| | 444 | @skipUnlessDBFeature( |
| | 445 | "can_return_columns_from_insert", "supports_expression_defaults" |
| | 446 | ) |
| | 447 | def test_pk_from_related_instance_saved_after_init_with_defaults(self): |
| | 448 | # Ensure that the related field's Python default references a |
| | 449 | # different valid object. |
| | 450 | IntegerDbDefaultPrimaryKey.objects.create(pk=1) |
| | 451 | related_object = IntegerDbDefaultPrimaryKey() |
| | 452 | obj = RelatedToDbDefaultPrimaryKey(related=related_object) |
| | 453 | related_object.save() |
| | 454 | obj.save() |
| | 455 | self.assertEqual(obj.pk, related_object.pk) |
| | 456 | |
| 442 | 457 | def test_unsaved_parent(self): |
| 443 | 458 | parent = NoFields() |
| 444 | 459 | msg = ( |
======================================================================
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)
Change History
(11)
| Description: |
modified (diff)
|
| Summary: |
6.1: save() falls back to db_default for primary key that should come from related instance instead → 6.1: save() falls back to python default for primary key that should come from related instance instead
|
| Description: |
modified (diff)
|
| Resolution: |
→ duplicate
|
| Status: |
assigned → closed
|
| Resolution: |
duplicate
|
| Status: |
closed → new
|
| Summary: |
6.1: save() falls back to python default for primary key that should come from related instance instead → save() falls back to python default even when DatabaseDefault instance is assigned
|
| Needs documentation: |
set
|
| Status: |
new → assigned
|
| Triage Stage: |
Unreviewed → Accepted
|
| Needs documentation: |
unset
|
| Triage Stage: |
Accepted → Ready for checkin
|
| Resolution: |
→ fixed
|
| Status: |
assigned → closed
|
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.