Opened 3 hours ago

Last modified 35 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 2

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

  • 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 (2)

comment:1 by Jacob Walls, 2 hours 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

comment:2 by Jacob Walls, 107 minutes ago

Description: modified (diff)

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.

Note: See TracTickets for help on using tickets.
Back to Top