From 388bb5bd9aa3cd43825cd8a3632a57d8204f875f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 23 Apr 2016 19:13:31 +0200 Subject: [PATCH] Fixed #22936 -- Obsoleted Field.get_prep_lookup()/get_db_prep_lookup() Thanks Tim Graham for completing the initial patch. --- django/contrib/admin/filters.py | 3 ++ django/contrib/admin/widgets.py | 1 - django/contrib/gis/db/models/fields.py | 7 --- django/contrib/postgres/fields/jsonb.py | 7 --- django/contrib/postgres/fields/ranges.py | 2 +- django/contrib/postgres/lookups.py | 7 ++- django/db/models/expressions.py | 2 +- django/db/models/fields/__init__.py | 58 ++------------------ django/db/models/fields/files.py | 5 -- django/db/models/fields/related_lookups.py | 14 ++--- django/db/models/fields/reverse_related.py | 7 --- django/db/models/lookups.py | 33 ++++++++++-- docs/howto/custom-model-fields.txt | 61 ---------------------- docs/ref/models/fields.txt | 23 +------- docs/releases/1.10.txt | 19 +++++++ tests/model_fields/test_booleanfield.py | 26 ++++----- tests/model_fields/test_custom_fields.py | 22 -------- tests/model_fields/test_decimalfield.py | 7 +-- tests/queries/tests.py | 5 +- 19 files changed, 89 insertions(+), 220 deletions(-) delete mode 100644 tests/model_fields/test_custom_fields.py diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index bb9731bdfcf..f986bca713e 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -232,6 +232,9 @@ class BooleanFieldListFilter(FieldListFilter): self.lookup_val = request.GET.get(self.lookup_kwarg) self.lookup_val2 = request.GET.get(self.lookup_kwarg2) super(BooleanFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) + if (self.used_parameters and self.lookup_kwarg in self.used_parameters and + self.used_parameters[self.lookup_kwarg] in ('1', '0')): + self.used_parameters[self.lookup_kwarg] = bool(int(self.used_parameters[self.lookup_kwarg])) def expected_parameters(self): return [self.lookup_kwarg, self.lookup_kwarg2] diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 8967200318d..e3553de70ab 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -128,7 +128,6 @@ def url_params_from_lookup_dict(lookups): if isinstance(v, (tuple, list)): v = ','.join(str(x) for x in v) elif isinstance(v, bool): - # See django.db.fields.BooleanField.get_prep_lookup v = ('0', '1')[v] else: v = six.text_type(v) diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index 4fa96744196..8452a11c5f1 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -316,13 +316,6 @@ class GeometryField(GeoSelectFormatMixin, BaseSpatialField): params = [connection.ops.Adapter(value)] return params - def get_prep_lookup(self, lookup_type, value): - if lookup_type == 'contains': - # 'contains' name might conflict with the "normal" contains lookup, - # for which the value is not prepared, but left as-is. - return self.get_prep_value(value) - return super(GeometryField, self).get_prep_lookup(lookup_type, value) - def get_db_prep_save(self, value, connection): "Prepares the value for saving in the database." if not value: diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index d580ea2efdd..ae83d9e379d 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -31,13 +31,6 @@ class JSONField(Field): return Json(value) return value - def get_prep_lookup(self, lookup_type, value): - if lookup_type in ('has_key', 'has_keys', 'has_any_keys'): - return value - if isinstance(value, (dict, list)): - return Json(value) - return super(JSONField, self).get_prep_lookup(lookup_type, value) - def validate(self, value, model_instance): super(JSONField, self).validate(value, model_instance) try: diff --git a/django/contrib/postgres/fields/ranges.py b/django/contrib/postgres/fields/ranges.py index 3816de84f1a..ab7708d288c 100644 --- a/django/contrib/postgres/fields/ranges.py +++ b/django/contrib/postgres/fields/ranges.py @@ -154,7 +154,7 @@ class RangeContainedBy(models.Lookup): return sql % (lhs, rhs), params def get_prep_lookup(self): - return RangeField().get_prep_lookup(self.lookup_name, self.rhs) + return RangeField().get_prep_value(self.rhs) models.DateField.register_lookup(RangeContainedBy) diff --git a/django/contrib/postgres/lookups.py b/django/contrib/postgres/lookups.py index 1a717256783..a39c9679e71 100644 --- a/django/contrib/postgres/lookups.py +++ b/django/contrib/postgres/lookups.py @@ -1,4 +1,5 @@ from django.db.models import Lookup, Transform +from django.utils.encoding import force_text from .search import SearchVector, SearchVectorExact, SearchVectorField @@ -29,14 +30,18 @@ class Overlap(PostgresSimpleLookup): class HasKey(PostgresSimpleLookup): lookup_name = 'has_key' operator = '?' + prepare_rhs = False class HasKeys(PostgresSimpleLookup): lookup_name = 'has_keys' operator = '?&' + def get_prep_lookup(self): + return [force_text(item) for item in self.rhs] -class HasAnyKeys(PostgresSimpleLookup): + +class HasAnyKeys(HasKeys): lookup_name = 'has_any_keys' operator = '?|' diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index b1133e24ea3..df82d2ec58e 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -213,7 +213,7 @@ class BaseExpression(object): def _prepare(self, field): """ - Hook used by Field.get_prep_lookup() to do custom preparation. + Hook used by Lookup.get_prep_lookup() to do custom preparation. """ return self diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 5951681e81e..e567e122501 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -741,8 +741,7 @@ class Field(RegisterLookupMixin): """Returns field's value prepared for interacting with the database backend. - Used by the default implementations of ``get_db_prep_save``and - `get_db_prep_lookup``` + Used by the default implementations of get_db_prep_save(). """ if not prepared: value = self.get_prep_value(value) @@ -755,36 +754,6 @@ class Field(RegisterLookupMixin): return self.get_db_prep_value(value, connection=connection, prepared=False) - def get_prep_lookup(self, lookup_type, value): - """ - Perform preliminary non-db specific lookup checks and conversions - """ - if hasattr(value, '_prepare'): - return value._prepare(self) - - if lookup_type in { - 'iexact', 'contains', 'icontains', - 'startswith', 'istartswith', 'endswith', 'iendswith', - 'isnull', 'search', 'regex', 'iregex', - }: - return value - elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): - return self.get_prep_value(value) - elif lookup_type in ('range', 'in'): - return [self.get_prep_value(v) for v in value] - return self.get_prep_value(value) - - def get_db_prep_lookup(self, lookup_type, value, connection, - prepared=False): - """ - Returns field's value prepared for database lookup. - """ - if not prepared: - value = self.get_prep_lookup(lookup_type, value) - prepared = True - - return [value] - def has_default(self): """ Returns a boolean of whether this field has a default value. @@ -1049,20 +1018,11 @@ class BooleanField(Field): params={'value': value}, ) - def get_prep_lookup(self, lookup_type, value): - # Special-case handling for filters coming from a Web request (e.g. the - # admin interface). Only works for scalar values (not lists). If you're - # passing in a list, you might as well make things the right type when - # constructing the list. - if value in ('1', '0'): - value = bool(int(value)) - return super(BooleanField, self).get_prep_lookup(lookup_type, value) - def get_prep_value(self, value): value = super(BooleanField, self).get_prep_value(value) if value is None: return None - return bool(value) + return self.to_python(value) def formfield(self, **kwargs): # Unlike most fields, BooleanField figures out include_blank from @@ -1453,8 +1413,6 @@ class DateTimeField(DateField): # contribute_to_class is inherited from DateField, it registers # get_next_by_FOO and get_prev_by_FOO - # get_prep_lookup is inherited from DateField - def get_prep_value(self, value): value = super(DateTimeField, self).get_prep_value(value) value = self.to_python(value) @@ -2051,21 +2009,11 @@ class NullBooleanField(Field): params={'value': value}, ) - def get_prep_lookup(self, lookup_type, value): - # Special-case handling for filters coming from a Web request (e.g. the - # admin interface). Only works for scalar values (not lists). If you're - # passing in a list, you might as well make things the right type when - # constructing the list. - if value in ('1', '0'): - value = bool(int(value)) - return super(NullBooleanField, self).get_prep_lookup(lookup_type, - value) - def get_prep_value(self, value): value = super(NullBooleanField, self).get_prep_value(value) if value is None: return None - return bool(value) + return self.to_python(value) def formfield(self, **kwargs): defaults = { diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index 3ba2c14325d..f18e2f39b3e 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -271,11 +271,6 @@ class FileField(Field): def get_internal_type(self): return "FileField" - def get_prep_lookup(self, lookup_type, value): - if hasattr(value, 'name'): - value = value.name - return super(FileField, self).get_prep_lookup(lookup_type, value) - def get_prep_value(self, value): "Returns field's value prepared for saving into a database." value = super(FileField, self).get_prep_value(value) diff --git a/django/db/models/fields/related_lookups.py b/django/db/models/fields/related_lookups.py index 0f5a8b5e211..3123e940736 100644 --- a/django/db/models/fields/related_lookups.py +++ b/django/db/models/fields/related_lookups.py @@ -44,15 +44,15 @@ class RelatedIn(In): if not isinstance(self.lhs, MultiColSource) and self.rhs_is_direct_value(): # If we get here, we are dealing with single-column relations. self.rhs = [get_normalized_value(val, self.lhs)[0] for val in self.rhs] - # We need to run the related field's get_prep_lookup(). Consider case + # We need to run the related field's get_prep_value(). Consider case # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself # doesn't have validation for non-integers, so we must run validation # using the target field. if hasattr(self.lhs.output_field, 'get_path_info'): - # Run the target field's get_prep_lookup. We can safely assume there is + # Run the target field's get_prep_value. We can safely assume there is # only one as we don't get to the direct value branch otherwise. - self.rhs = self.lhs.output_field.get_path_info()[-1].target_fields[-1].get_prep_lookup( - self.lookup_name, self.rhs) + target_field = self.lhs.output_field.get_path_info()[-1].target_fields[-1] + self.rhs = [target_field.get_prep_value(v) for v in self.rhs] return super(RelatedIn, self).get_prep_lookup() def as_sql(self, compiler, connection): @@ -88,15 +88,15 @@ class RelatedLookupMixin(object): if not isinstance(self.lhs, MultiColSource) and self.rhs_is_direct_value(): # If we get here, we are dealing with single-column relations. self.rhs = get_normalized_value(self.rhs, self.lhs)[0] - # We need to run the related field's get_prep_lookup(). Consider case + # We need to run the related field's get_prep_value(). Consider case # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself # doesn't have validation for non-integers, so we must run validation # using the target field. if hasattr(self.lhs.output_field, 'get_path_info'): # Get the target field. We can safely assume there is only one # as we don't get to the direct value branch otherwise. - self.rhs = self.lhs.output_field.get_path_info()[-1].target_fields[-1].get_prep_lookup( - self.lookup_name, self.rhs) + target_field = self.lhs.output_field.get_path_info()[-1].target_fields[-1] + self.rhs = target_field.get_prep_value(self.rhs) return super(RelatedLookupMixin, self).get_prep_lookup() diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py index c477ab5a932..1554261246f 100644 --- a/django/db/models/fields/reverse_related.py +++ b/django/db/models/fields/reverse_related.py @@ -110,9 +110,6 @@ class ForeignObjectRel(object): def one_to_one(self): return self.field.one_to_one - def get_prep_lookup(self, lookup_name, value): - return self.field.get_prep_lookup(lookup_name, value) - def get_lookup(self, lookup_name): return self.field.get_lookup(lookup_name) @@ -142,10 +139,6 @@ class ForeignObjectRel(object): (x._get_pk_val(), smart_text(x)) for x in self.related_model._default_manager.all() ] - def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): - # Defer to the actual field definition for db prep - return self.field.get_db_prep_lookup(lookup_type, value, connection=connection, prepared=prepared) - def is_hidden(self): "Should the related object be hidden?" return bool(self.related_name) and self.related_name[-1] == '+' diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 62acfcb887a..9f398b2b1a2 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -16,6 +16,7 @@ from django.utils.six.moves import range class Lookup(object): lookup_name = None + prepare_rhs = True def __init__(self, lhs, rhs): self.lhs, self.rhs = lhs, rhs @@ -56,12 +57,14 @@ class Lookup(object): return sqls, sqls_params def get_prep_lookup(self): - return self.lhs.output_field.get_prep_lookup(self.lookup_name, self.rhs) + if hasattr(self.rhs, '_prepare'): + return self.rhs._prepare(self.lhs.output_field) + if self.prepare_rhs and hasattr(self.lhs.output_field, 'get_prep_value'): + return self.lhs.output_field.get_prep_value(self.rhs) + return self.rhs def get_db_prep_lookup(self, value, connection): - return ( - '%s', self.lhs.output_field.get_db_prep_lookup( - self.lookup_name, value, connection, prepared=True)) + return ('%s', [value]) def process_lhs(self, compiler, connection, lhs=None): lhs = lhs or self.lhs @@ -199,6 +202,7 @@ Field.register_lookup(Exact) class IExact(BuiltinLookup): lookup_name = 'iexact' + prepare_rhs = False def process_rhs(self, qn, connection): rhs, params = super(IExact, self).process_rhs(qn, connection) @@ -254,6 +258,13 @@ IntegerField.register_lookup(IntegerLessThan) class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup): lookup_name = 'in' + def get_prep_lookup(self): + if hasattr(self.rhs, '_prepare'): + return self.rhs._prepare(self.lhs.output_field) + if hasattr(self.lhs.output_field, 'get_prep_value'): + return [self.lhs.output_field.get_prep_value(v) for v in self.rhs] + return self.rhs + def process_rhs(self, compiler, connection): db_rhs = getattr(self.rhs, '_db', None) if db_rhs is not None and db_rhs != connection.alias: @@ -335,6 +346,7 @@ class PatternLookup(BuiltinLookup): class Contains(PatternLookup): lookup_name = 'contains' + prepare_rhs = False def process_rhs(self, qn, connection): rhs, params = super(Contains, self).process_rhs(qn, connection) @@ -346,11 +358,13 @@ Field.register_lookup(Contains) class IContains(Contains): lookup_name = 'icontains' + prepare_rhs = False Field.register_lookup(IContains) class StartsWith(PatternLookup): lookup_name = 'startswith' + prepare_rhs = False def process_rhs(self, qn, connection): rhs, params = super(StartsWith, self).process_rhs(qn, connection) @@ -362,6 +376,7 @@ Field.register_lookup(StartsWith) class IStartsWith(PatternLookup): lookup_name = 'istartswith' + prepare_rhs = False def process_rhs(self, qn, connection): rhs, params = super(IStartsWith, self).process_rhs(qn, connection) @@ -373,6 +388,7 @@ Field.register_lookup(IStartsWith) class EndsWith(PatternLookup): lookup_name = 'endswith' + prepare_rhs = False def process_rhs(self, qn, connection): rhs, params = super(EndsWith, self).process_rhs(qn, connection) @@ -384,6 +400,7 @@ Field.register_lookup(EndsWith) class IEndsWith(PatternLookup): lookup_name = 'iendswith' + prepare_rhs = False def process_rhs(self, qn, connection): rhs, params = super(IEndsWith, self).process_rhs(qn, connection) @@ -396,6 +413,11 @@ Field.register_lookup(IEndsWith) class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup): lookup_name = 'range' + def get_prep_lookup(self): + if hasattr(self.rhs, '_prepare'): + return self.rhs._prepare(self.lhs.output_field) + return [self.lhs.output_field.get_prep_value(v) for v in self.rhs] + def get_rhs_op(self, connection, rhs): return "BETWEEN %s AND %s" % (rhs[0], rhs[1]) @@ -411,6 +433,7 @@ Field.register_lookup(Range) class IsNull(BuiltinLookup): lookup_name = 'isnull' + prepare_rhs = False def as_sql(self, compiler, connection): sql, params = compiler.compile(self.lhs) @@ -423,6 +446,7 @@ Field.register_lookup(IsNull) class Search(BuiltinLookup): lookup_name = 'search' + prepare_rhs = False def as_sql(self, compiler, connection): warnings.warn( @@ -438,6 +462,7 @@ Field.register_lookup(Search) class Regex(BuiltinLookup): lookup_name = 'regex' + prepare_rhs = False def as_sql(self, compiler, connection): if self.lookup_name in connection.operators: diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 99dd1e217ae..17304197871 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -577,67 +577,6 @@ the end. You should also update the model's attribute if you make any changes to the value so that code holding references to the model will always see the correct value. -.. _preparing-values-for-use-in-database-lookups: - -Preparing values for use in database lookups -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -As with value conversions, preparing a value for database lookups is a -two phase process. - -:meth:`.get_prep_lookup` performs the first phase of lookup preparation: -type conversion and data validation. - -Prepares the ``value`` for passing to the database when used in a lookup (a -``WHERE`` constraint in SQL). The ``lookup_type`` parameter will be one of the -valid Django filter lookups: ``exact``, ``iexact``, ``contains``, ``icontains``, -``gt``, ``gte``, ``lt``, ``lte``, ``in``, ``startswith``, ``istartswith``, -``endswith``, ``iendswith``, ``range``, ``year``, ``month``, ``day``, -``isnull``, ``search``, ``regex``, and ``iregex``. - -If you are using :doc:`custom lookups `, the -``lookup_type`` can be any ``lookup_name`` used by the project's custom lookups. - -Your method must be prepared to handle all of these ``lookup_type`` values and -should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a -list when you were expecting an object, for example) or a ``TypeError`` if -your field does not support that type of lookup. For many fields, you can get -by with handling the lookup types that need special handling for your field -and pass the rest to the :meth:`~Field.get_db_prep_lookup` method of the parent -class. - -If you needed to implement :meth:`.get_db_prep_save`, you will usually need to -implement :meth:`.get_prep_lookup`. If you don't, :meth:`.get_prep_value` will -be called by the default implementation, to manage ``exact``, ``gt``, ``gte``, -``lt``, ``lte``, ``in`` and ``range`` lookups. - -You may also want to implement this method to limit the lookup types that could -be used with your custom field type. - -Note that, for ``"range"`` and ``"in"`` lookups, ``get_prep_lookup`` will receive -a list of objects (presumably of the right type) and will need to convert them -to a list of things of the right type for passing to the database. Most of the -time, you can reuse ``get_prep_value()``, or at least factor out some common -pieces. - -For example, the following code implements ``get_prep_lookup`` to limit the -accepted lookup types to ``exact`` and ``in``:: - - class HandField(models.Field): - # ... - - def get_prep_lookup(self, lookup_type, value): - # We only handle 'exact' and 'in'. All others are errors. - if lookup_type == 'exact': - return self.get_prep_value(value) - elif lookup_type == 'in': - return [self.get_prep_value(v) for v in value] - else: - raise TypeError('Lookup type %r not supported.' % lookup_type) - -For performing database-specific data conversions required by a lookup, -you can override :meth:`~Field.get_db_prep_lookup`. - .. _specifying-form-field-for-model-field: Specifying the form field for a model field diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 3ecc213892f..5bd1a148b83 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1717,8 +1717,7 @@ Field API reference ``Field`` is an abstract class that represents a database table column. Django uses fields to create the database table (:meth:`db_type`), to map Python types to database (:meth:`get_prep_value`) and vice-versa - (:meth:`from_db_value`), and to apply :doc:`/ref/models/lookups` - (:meth:`get_prep_lookup`). + (:meth:`from_db_value`). A field is thus a fundamental piece in different Django APIs, notably, :class:`models ` and :class:`querysets @@ -1847,26 +1846,6 @@ Field API reference See :ref:`preprocessing-values-before-saving` for usage. - When a lookup is used on a field, the value may need to be "prepared". - Django exposes two methods for this: - - .. method:: get_prep_lookup(lookup_type, value) - - Prepares ``value`` to the database prior to be used in a lookup. - The ``lookup_type`` will be the registered name of the lookup. For - example: ``"exact"``, ``"iexact"``, or ``"contains"``. - - See :ref:`preparing-values-for-use-in-database-lookups` for usage. - - .. method:: get_db_prep_lookup(lookup_type, value, connection, prepared=False) - - Similar to :meth:`get_db_prep_value`, but for performing a lookup. - - As with :meth:`get_db_prep_value`, the specific connection that will - be used for the query is passed as ``connection``. In addition, - ``prepared`` describes whether the value has already been prepared with - :meth:`get_prep_lookup`. - Fields often receive their values as a different type, either from serialization or from forms. diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 826752996ba..e750fb1d47b 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -677,6 +677,25 @@ You can check if your database has any of the removed hashers like this:: # Unsalted MD5 passwords might not have an 'md5$$' prefix: User.objects.filter(password__length=32) +``Field.get_prep_lookup()`` and ``Field.get_db_prep_lookup()`` methods are removed +---------------------------------------------------------------------------------- + +If you have a custom field that implements either of these methods, register a +custom lookup for it. For example:: + + from django.db.models import Field + from django.db.models.lookups import Exact + + class MyField(Field): + ... + + class MyFieldExact(Exact): + def get_prep_lookup(self): + # do_custom_stuff_for_myfield + .... + + MyField.register_lookup(MyFieldExact) + :mod:`django.contrib.gis` ------------------------- diff --git a/tests/model_fields/test_booleanfield.py b/tests/model_fields/test_booleanfield.py index 2975f4723cd..9c90bb57094 100644 --- a/tests/model_fields/test_booleanfield.py +++ b/tests/model_fields/test_booleanfield.py @@ -1,29 +1,29 @@ from django.core.exceptions import ValidationError -from django.db import IntegrityError, connection, models, transaction +from django.db import IntegrityError, models, transaction from django.test import SimpleTestCase, TestCase from .models import BooleanModel, FksToBooleans, NullBooleanModel class BooleanFieldTests(TestCase): - def _test_get_db_prep_lookup(self, f): - self.assertEqual(f.get_db_prep_lookup('exact', True, connection=connection), [True]) - self.assertEqual(f.get_db_prep_lookup('exact', '1', connection=connection), [True]) - self.assertEqual(f.get_db_prep_lookup('exact', 1, connection=connection), [True]) - self.assertEqual(f.get_db_prep_lookup('exact', False, connection=connection), [False]) - self.assertEqual(f.get_db_prep_lookup('exact', '0', connection=connection), [False]) - self.assertEqual(f.get_db_prep_lookup('exact', 0, connection=connection), [False]) - self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None]) + def _test_get_prep_value(self, f): + self.assertEqual(f.get_prep_value(True), True) + self.assertEqual(f.get_prep_value('1'), True) + self.assertEqual(f.get_prep_value(1), True) + self.assertEqual(f.get_prep_value(False), False) + self.assertEqual(f.get_prep_value('0'), False) + self.assertEqual(f.get_prep_value(0), False) + self.assertEqual(f.get_prep_value(None), None) def _test_to_python(self, f): self.assertIs(f.to_python(1), True) self.assertIs(f.to_python(0), False) - def test_booleanfield_get_db_prep_lookup(self): - self._test_get_db_prep_lookup(models.BooleanField()) + def test_booleanfield_get_prep_value(self): + self._test_get_prep_value(models.BooleanField()) - def test_nullbooleanfield_get_db_prep_lookup(self): - self._test_get_db_prep_lookup(models.NullBooleanField()) + def test_nullbooleanfield_get_prep_value(self): + self._test_get_prep_value(models.NullBooleanField()) def test_booleanfield_to_python(self): self._test_to_python(models.BooleanField()) diff --git a/tests/model_fields/test_custom_fields.py b/tests/model_fields/test_custom_fields.py deleted file mode 100644 index c41e19416e1..00000000000 --- a/tests/model_fields/test_custom_fields.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.db import connection, models -from django.test import SimpleTestCase - - -class CustomFieldTests(SimpleTestCase): - - def test_get_prep_value_count(self): - """ - Field values are not prepared twice in get_db_prep_lookup() (#14786). - """ - class NoopField(models.TextField): - def __init__(self, *args, **kwargs): - self.prep_value_count = 0 - super(NoopField, self).__init__(*args, **kwargs) - - def get_prep_value(self, value): - self.prep_value_count += 1 - return super(NoopField, self).get_prep_value(value) - - field = NoopField() - field.get_db_prep_lookup('exact', 'TEST', connection=connection, prepared=False) - self.assertEqual(field.prep_value_count, 1) diff --git a/tests/model_fields/test_decimalfield.py b/tests/model_fields/test_decimalfield.py index af9bd6c4be8..04145818166 100644 --- a/tests/model_fields/test_decimalfield.py +++ b/tests/model_fields/test_decimalfield.py @@ -2,7 +2,7 @@ from decimal import Decimal from django.core import validators from django.core.exceptions import ValidationError -from django.db import connection, models +from django.db import models from django.test import TestCase from .models import BigD, Foo @@ -27,9 +27,10 @@ class DecimalFieldTests(TestCase): self.assertEqual(f._format(f.to_python('2.6')), '2.6') self.assertEqual(f._format(None), None) - def test_get_db_prep_lookup(self): + def test_get_prep_value(self): f = models.DecimalField(max_digits=5, decimal_places=1) - self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None]) + self.assertEqual(f.get_prep_value(None), None) + self.assertEqual(f.get_prep_value('2.4'), Decimal('2.4')) def test_filter_with_strings(self): """ diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 098f47b8d10..6af66ba7f9e 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -1229,9 +1229,8 @@ class Queries2Tests(TestCase): ) def test_ticket12239(self): - # Float was being rounded to integer on gte queries on integer field. Tests - # show that gt, lt, gte, and lte work as desired. Note that the fix changes - # get_prep_lookup for gte and lt queries only. + # Custom lookups are registered to round float values correctly on gte + # and lt IntegerField queries. self.assertQuerysetEqual( Number.objects.filter(num__gt=11.9), ['']