Opened 12 hours ago
Last modified 5 hours ago
#36073 new Bug
Custom field without get_db_prep_value() errors on updates if part of composite primary key
Reported by: | Jacob Walls | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Release blocker | Keywords: | |
Cc: | Csirmaz Bendegúz | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
A model with a custom field only implementing get_db_prep_save()
but not get_db_prep_value()
fails on save()
when used in a composite primary key, but only for updates. Inserts work fine.
This fell out of #36062 where the test model I adjusted happened to have a custom field with these characteristics.
I didn't get as far as verifying this to be relevant, but I noticed while stepping through the code that SQLInsertCompiler
has logic to add `pk` to the list of fields, so I wonder if it's not being prepared the same way in SQLUpdateCompiler
?
-
tests/custom_pk/fields.py
diff --git a/tests/custom_pk/fields.py b/tests/custom_pk/fields.py index 2d70c6b6dc..135b315ffe 100644
a b class MyWrapperField(models.CharField): 59 59 return value 60 60 61 61 62 class MyWrapperFieldWithoutGetDbPrepValue(models.CharField): 63 def to_python(self, value): 64 if not value: 65 return 66 if not isinstance(value, MyWrapper): 67 value = MyWrapper(value) 68 return value 69 70 def from_db_value(self, value, expression, connection): 71 if not value: 72 return 73 return MyWrapper(value) 74 75 def get_db_prep_save(self, value, connection): 76 if not value: 77 return 78 if isinstance(value, MyWrapper): 79 return str(value) 80 return value 81 82 62 83 class MyAutoField(models.BigAutoField): 63 84 def from_db_value(self, value, expression, connection): 64 85 if value is None: -
tests/custom_pk/models.py
diff --git a/tests/custom_pk/models.py b/tests/custom_pk/models.py index 000ea7a29d..4ea5190d5f 100644
a b this behavior by explicitly adding ``primary_key=True`` to a field. 7 7 8 8 from django.db import models 9 9 10 from .fields import MyAutoField, MyWrapperField 10 from .fields import MyAutoField, MyWrapperField, MyWrapperFieldWithoutGetDbPrepValue 11 11 12 12 13 13 class Employee(models.Model): … … class Foo(models.Model): 40 40 41 41 class CustomAutoFieldModel(models.Model): 42 42 id = MyAutoField(primary_key=True) 43 44 45 class CompositePKWithoutGetDbPrepValue(models.Model): 46 pk = models.CompositePrimaryKey("id", "tenant") 47 id = MyWrapperFieldWithoutGetDbPrepValue() 48 tenant = models.SmallIntegerField() -
tests/custom_pk/tests.py
diff --git a/tests/custom_pk/tests.py b/tests/custom_pk/tests.py index 5f865b680a..fa371942e6 100644
a b from django.db import IntegrityError, transaction 2 2 from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature 3 3 4 4 from .fields import MyWrapper 5 from .models import Bar, Business, C ustomAutoFieldModel, Employee, Foo5 from .models import Bar, Business, CompositePKWithoutGetDbPrepValue, CustomAutoFieldModel, Employee, Foo 6 6 7 7 8 8 class BasicCustomPKTests(TestCase): … … class CustomPKTests(TestCase): 164 164 Business.objects.create(name="Bears") 165 165 Business.objects.create(pk="Tears") 166 166 167 def test_custom_composite_pk_save_force_update(self): 168 """A custom primary key field that implements get_db_prep_save() 169 rather than get_db_prep_value() still saves.""" 170 obj = CompositePKWithoutGetDbPrepValue() 171 obj.id = MyWrapper("1") 172 obj.tenant = 1 173 obj.save() # passes with force_insert=True 174 167 175 def test_unicode_pk(self): 168 176 # Primary key may be Unicode string. 169 177 Business.objects.create(name="jaźń")
ERROR: test_custom_composite_pk_save_force_update (custom_pk.tests.CustomPKTests.test_custom_composite_pk_save_force_update) A custom primary key field that implements get_db_prep_save() ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/.../django/django/db/backends/utils.py", line 105, in _execute return self.cursor.execute(sql, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/.../django/django/db/backends/sqlite3/base.py", line 360, in execute return super().execute(query, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ sqlite3.ProgrammingError: Error binding parameter 3: type 'MyWrapper' is not supported The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/Users/.../django/tests/custom_pk/tests.py", line 173, in test_custom_composite_pk_save_force_update bar.save() # passes with force_insert=True ^^^^^^^^^^ ... File "/Users/.../django/django/db/backends/sqlite3/base.py", line 360, in execute return super().execute(query, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ django.db.utils.ProgrammingError: Error binding parameter 3: type 'MyWrapper' is not supported ---------------------------------------------------------------------- Ran 16 tests in 0.011s FAILED (errors=1, skipped=1)