Opened 5 weeks ago
Closed 5 weeks ago
#36073 closed Bug (invalid)
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)
Change History (3)
comment:1 by , 5 weeks ago
Cc: | added |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:2 by , 5 weeks ago
comment:3 by , 5 weeks ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
Yes, it's the same for regular primary keys which makes me think whether this is not valid
It's possible that the test code from serializers that inspired this is not fully defined.
Jacob if you can add more information we can look at this again
(for reference, same thing for regular primary keys)
-
tests/custom_pk/fields.py
diff --git a/tests/custom_pk/fields.py b/tests/custom_pk/fields.py index 2d70c6b6dc..3908b426c9 100644
a b class MyWrapper: 20 20 return self.value == other 21 21 22 22 23 class MyWrapperField (models.CharField):23 class MyWrapperFieldWithoutGetDbPrepValue(models.CharField): 24 24 def __init__(self, *args, **kwargs): 25 25 kwargs["max_length"] = 10 26 26 super().__init__(*args, **kwargs) … … class MyWrapperField(models.CharField): 51 51 return str(value) 52 52 return value 53 53 54 class MyWrapperField(models.CharField): 54 55 def get_db_prep_value(self, value, connection, prepared=False): 55 56 if not value: 56 57 return -
tests/custom_pk/models.py
diff --git a/tests/custom_pk/models.py b/tests/custom_pk/models.py index 000ea7a29d..75b1031e10 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 PKWithoutGetDbPrepValue(models.Model): 46 id = MyWrapperFieldWithoutGetDbPrepValue(primary_key=True) -
tests/custom_pk/tests.py
diff --git a/tests/custom_pk/tests.py b/tests/custom_pk/tests.py index 5f865b680a..3bfd32c29c 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, CustomAutoFieldModel, Employee, Foo 5 from .models import Bar, Business, CustomAutoFieldModel, Employee, Foo, PKWithoutGetDbPrepValue 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 obj = PKWithoutGetDbPrepValue() 169 obj.id = MyWrapper("1") 170 obj.save() # passes with force_insert=True 171 167 172 def test_unicode_pk(self): 168 173 # Primary key may be Unicode string. 169 174 Business.objects.create(name="jaźń")
I don't think this is related to composite primary keys, it errors if it's a regular primary key as well