#36259: `_is_pk_set()` is incorrectly used to check for unsaved models.
-------------------------------------+-------------------------------------
     Reporter:  Clifford Gama        |                    Owner:  Clifford
                                     |  Gama
         Type:  Bug                  |                   Status:  assigned
    Component:  Database layer       |                  Version:  dev
  (models, ORM)                      |
     Severity:  Release blocker      |               Resolution:
     Keywords:  _is_pk_set           |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by Clifford Gama:

Old description:

> Since models defined with custom primary keys can have `pk` set before
> being saved to the database, this means they bypass this as if they were
> saved. Here is an example:
> {{{#!diff
> diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py
> index 451e97c274..67b1c32e90 100644
> --- a/tests/one_to_one/tests.py
> +++ b/tests/one_to_one/tests.py
> @@ -170,6 +170,16 @@ class OneToOneTests(TestCase):
>          with self.assertRaises(Restaurant.DoesNotExist):
>              place.restaurant
>
> +    def test_unsaved_object_with_supplied_primary_key(self):
> +        link1 = Place.objects.create(name="Ushaka", address="Ethekwini")
> +        link2 = ManualPrimaryKey(primary_key="YTREWQ", name="moonwalk")
> +        msg = (
> +            "save() prohibited to prevent data loss due to unsaved
> related object "
> +            "'link2'."
> +        )
> +        with self.assertRaisesMessage(ValueError, msg):
> +            MultiModel.objects.create(link1=link1, link2=link2,
> name="Unsavable")
> +
>      def test_reverse_relationship_cache_cascade(self):
>          """
>          Regression test for #9023: accessing the reverse relationship
> shouldn't
> }}}
>
> which results in
> {{{#!python
> FE
> ======================================================================
> ERROR: test_unsaved_object_with_supplied_primary_key
> (one_to_one.tests.OneToOneTests.test_unsaved_object_with_supplied_primary_key)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File "/home/clifford/code/django/django/db/backends/sqlite3/base.py",
> line 304, in check_constraints
>     raise IntegrityError(
> django.db.utils.IntegrityError: The row in table 'one_to_one_multimodel'
> with primary key '1' has an invalid foreign key:
> one_to_one_multimodel.link2_id contains a value 'YTREWQ' that does not
> have a corresponding value in one_to_one_manualprimarykey.primary_key.
>
> ======================================================================
> FAIL: test_unsaved_object_with_supplied_primary_key
> (one_to_one.tests.OneToOneTests.test_unsaved_object_with_supplied_primary_key)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File "/home/clifford/code/django/tests/one_to_one/tests.py", line 180,
> in test_unsaved_object_with_supplied_primary_key
>     with self.assertRaisesMessage(ValueError, msg):
>   File "/usr/lib/python3.12/contextlib.py", line 144, in __exit__
>     next(self.gen)
> AssertionError: ValueError not raised
>
> ----------------------------------------------------------------------
> Ran 1 test in 0.004s
>
> FAILED (failures=1, errors=1)
> }}}
> And here is the culprit
> [https://github.com/django/django/blob/ef6a83789b310a441237a190a493c9586a4cb260/django/db/models/base.py#L1220C17-L1224C22
> code]:
> This is done in a few other places. My proposal is to replace such uses
> cases of `_is_pk_set` with `instance._state.added` checks.

New description:

 Since models defined with custom primary keys can have `pk` set before
 being saved to the database, this means they bypass this as if they were
 saved. Here is an example:
 {{{#!diff
 diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py
 index 451e97c274..67b1c32e90 100644
 --- a/tests/one_to_one/tests.py
 +++ b/tests/one_to_one/tests.py
 @@ -170,6 +170,16 @@ class OneToOneTests(TestCase):
          with self.assertRaises(Restaurant.DoesNotExist):
              place.restaurant

 +    def test_unsaved_object_with_supplied_primary_key(self):
 +        link1 = Place.objects.create(name="Ushaka", address="Ethekwini")
 +        link2 = ManualPrimaryKey(primary_key="YTREWQ", name="moonwalk")
 +        msg = (
 +            "save() prohibited to prevent data loss due to unsaved
 related object "
 +            "'link2'."
 +        )
 +        with self.assertRaisesMessage(ValueError, msg):
 +            MultiModel.objects.create(link1=link1, link2=link2,
 name="Unsavable")
 +
      def test_reverse_relationship_cache_cascade(self):
          """
          Regression test for #9023: accessing the reverse relationship
 shouldn't
 }}}

 which results in
 {{{#!python
 FE
 ======================================================================
 ERROR: test_unsaved_object_with_supplied_primary_key
 (one_to_one.tests.OneToOneTests.test_unsaved_object_with_supplied_primary_key)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "/home/clifford/code/django/django/db/backends/sqlite3/base.py",
 line 304, in check_constraints
     raise IntegrityError(
 django.db.utils.IntegrityError: The row in table 'one_to_one_multimodel'
 with primary key '1' has an invalid foreign key:
 one_to_one_multimodel.link2_id contains a value 'YTREWQ' that does not
 have a corresponding value in one_to_one_manualprimarykey.primary_key.

 ======================================================================
 FAIL: test_unsaved_object_with_supplied_primary_key
 (one_to_one.tests.OneToOneTests.test_unsaved_object_with_supplied_primary_key)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "/home/clifford/code/django/tests/one_to_one/tests.py", line 180,
 in test_unsaved_object_with_supplied_primary_key
     with self.assertRaisesMessage(ValueError, msg):
   File "/usr/lib/python3.12/contextlib.py", line 144, in __exit__
     next(self.gen)
 AssertionError: ValueError not raised

 ----------------------------------------------------------------------
 Ran 1 test in 0.004s

 FAILED (failures=1, errors=1)
 }}}
 And here is the culprit
 
[https://github.com/django/django/blob/ef6a83789b310a441237a190a493c9586a4cb260/django/db/models/base.py#L1220C17-L1224C22
 code]:
 This is done in a few other places. My proposal is to replace such uses
 cases of `_is_pk_set` with `instance._state.adding` checks.

--
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36259#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/010701959dd4c079-b65b2359-2480-4fd2-967e-076d42feb558-000000%40eu-central-1.amazonses.com.

Reply via email to