Opened 2 hours ago

Last modified 64 minutes ago

#37238 assigned Bug

6.1: save() falls back to python default for primary key that should come from related instance instead — at Version 1

Reported by: Jacob Walls Owned by: Jacob Walls
Component: Database layer (models, ORM) Version: 6.1
Severity: Release blocker Keywords:
Cc: Mariusz Felisiak Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Jacob Walls)

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 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.

  • tests/bulk_create/models.py

    diff --git a/tests/bulk_create/models.py b/tests/bulk_create/models.py
    index bc9beba990..34ea0bf3f8 100644
    a b class DbDefaultPrimaryKey(models.Model):  
    157157
    158158    class Meta:
    159159        required_db_features = {"supports_expression_defaults"}
     160
     161
     162class 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
     169class 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"}
  • tests/bulk_create/tests.py

    diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py
    index 397fcb9186..2692d62fb1 100644
    a b from .models import (  
    2727    DbDefaultModel,
    2828    DbDefaultPrimaryKey,
    2929    FieldsWithDbColumns,
     30    IntegerDbDefaultPrimaryKey,
    3031    NoFields,
    3132    NullableFields,
    3233    Pizzeria,
    from .models import (  
    3536    ProxyMultiProxyCountry,
    3637    ProxyProxyCountry,
    3738    RelatedModel,
     39    RelatedToDbDefaultPrimaryKey,
    3840    Restaurant,
    3941    SmallAutoFieldModel,
    4042    State,
    class BulkCreateTests(TestCase):  
    439441        child = NullableFields.objects.get(integer_field=88)
    440442        self.assertEqual(child.auto_field, parent)
    441443
     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
    442457    def test_unsaved_parent(self):
    443458        parent = NoFields()
    444459        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 (1)

comment:1 by Jacob Walls, 104 minutes ago

Description: modified (diff)
Summary: 6.1: save() falls back to db_default for primary key that should come from related instance instead6.1: save() falls back to python default for primary key that should come from related instance instead
Note: See TracTickets for help on using tickets.
Back to Top