diff options
| author | Simon Charette <charette.s@gmail.com> | 2025-11-22 13:32:34 -0500 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-03-12 20:01:02 -0400 |
| commit | 1a8fd5cf75bf855852f6bc2f75c3da9f7b145669 (patch) | |
| tree | 07805ec5df940451042b9c96b0cb2fa595f870e2 /tests | |
| parent | 7cb2221e9267dec3f1cf7c88b8686d38fd956639 (diff) | |
Fixed #36727 -- Deprecated Field.get_placeholder in favor of get_placeholder_sql.
The lack of ability of the get_placeholder call chain to return SQL and
parameters separated so they can be mogrified by the backend at execution time
forced implementations to dangerously interpolate potentially user controlled
values.
The get_placeholder_sql name was chosen due to its proximity to the previous
method, but other options such as Field.as_sql were considered but ultimately
rejected due to its different input signature compared to Expression.as_sql
that might have lead to confusion.
There is a lot of overlap between what Field.get_db_prep_value and
get_placeholder_sql do but folding the latter in the former would require
changing its return signature to return expression which is a way more invasive
change than what is proposed here.
Given we always call get_db_prep_value it might still be an avenue worth
exploring in the future to offer a publicly documented interface to allow field
to take an active part in the compilation chain.
Thanks Jacob for the review.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/model_fields/tests.py | 39 | ||||
| -rw-r--r-- | tests/postgres_tests/fields.py | 8 |
2 files changed, 44 insertions, 3 deletions
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 3d856d36c5..b27c07a92f 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -1,10 +1,12 @@ import pickle +import warnings from django import forms from django.core.exceptions import ValidationError -from django.db import models +from django.db import connection, models from django.test import SimpleTestCase, TestCase from django.utils.choices import CallableChoiceIterator +from django.utils.deprecation import RemovedInDjango70Warning from django.utils.functional import lazy from .models import ( @@ -147,6 +149,41 @@ class BasicFieldTests(SimpleTestCase): self.assertEqual(field_hash, hash(field)) + def test_get_placeholder_deprecation(self): + # RemovedInDjango70Warning: When the deprecation ends, remove + # completely. + msg = ( + "Field.get_placeholder is deprecated in favor of get_placeholder_sql. " + "Define model_fields.tests.BasicFieldTests." + "test_get_placeholder_deprecation.<locals>.SomeField.get_placeholder_sql " + "to return both SQL and parameters instead." + ) + with self.assertWarnsMessage(RemovedInDjango70Warning, msg): + + class SomeField(models.Field): + def get_placeholder(self, value, compiler, connection): + return "%s" + + def test_get_placeholder_sql_shim(self): + # RemovedInDjango70Warning: When the deprecation ends, remove + # completely. + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + + class SomeField(models.Field): + def get_placeholder(self, value, compiler, connection): + return "(1 + %s)" + + compiler = Bar.objects.all().query.get_compiler(connection=connection) + self.assertEqual( + SomeField().get_placeholder_sql(2, compiler, connection), + ("(1 + %s)", (2,)), + ) + self.assertEqual( + SomeField().get_placeholder_sql(models.Value(2), compiler, connection), + ("(1 + %s)", (2,)), + ) + class ChoicesTests(SimpleTestCase): @classmethod diff --git a/tests/postgres_tests/fields.py b/tests/postgres_tests/fields.py index d099effdd5..77370611ea 100644 --- a/tests/postgres_tests/fields.py +++ b/tests/postgres_tests/fields.py @@ -60,5 +60,9 @@ class EnumField(models.CharField): class OffByOneField(models.IntegerField): - def get_placeholder(self, value, compiler, connection): - return "(%s + 1)" + def get_placeholder_sql(self, value, compiler, connection): + if hasattr(value, "as_sql"): + sql, params = compiler.compile(value) + else: + sql, params = "%s", (value,) + return f"({sql} + %s)", (*params, 1) |
