Fixed #22936 -- Obsoleted Field.get_prep_lookup()/get_db_prep_lookup()

Thanks Tim Graham for completing the initial patch.
This commit is contained in:
Claude Paroz 2016-04-23 19:13:31 +02:00
parent 1206eea11e
commit 388bb5bd9a
19 changed files with 89 additions and 220 deletions

View File

@ -232,6 +232,9 @@ class BooleanFieldListFilter(FieldListFilter):
self.lookup_val = request.GET.get(self.lookup_kwarg) self.lookup_val = request.GET.get(self.lookup_kwarg)
self.lookup_val2 = request.GET.get(self.lookup_kwarg2) self.lookup_val2 = request.GET.get(self.lookup_kwarg2)
super(BooleanFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) 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): def expected_parameters(self):
return [self.lookup_kwarg, self.lookup_kwarg2] return [self.lookup_kwarg, self.lookup_kwarg2]

View File

@ -128,7 +128,6 @@ def url_params_from_lookup_dict(lookups):
if isinstance(v, (tuple, list)): if isinstance(v, (tuple, list)):
v = ','.join(str(x) for x in v) v = ','.join(str(x) for x in v)
elif isinstance(v, bool): elif isinstance(v, bool):
# See django.db.fields.BooleanField.get_prep_lookup
v = ('0', '1')[v] v = ('0', '1')[v]
else: else:
v = six.text_type(v) v = six.text_type(v)

View File

@ -316,13 +316,6 @@ class GeometryField(GeoSelectFormatMixin, BaseSpatialField):
params = [connection.ops.Adapter(value)] params = [connection.ops.Adapter(value)]
return params 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): def get_db_prep_save(self, value, connection):
"Prepares the value for saving in the database." "Prepares the value for saving in the database."
if not value: if not value:

View File

@ -31,13 +31,6 @@ class JSONField(Field):
return Json(value) return Json(value)
return 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): def validate(self, value, model_instance):
super(JSONField, self).validate(value, model_instance) super(JSONField, self).validate(value, model_instance)
try: try:

View File

@ -154,7 +154,7 @@ class RangeContainedBy(models.Lookup):
return sql % (lhs, rhs), params return sql % (lhs, rhs), params
def get_prep_lookup(self): 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) models.DateField.register_lookup(RangeContainedBy)

View File

@ -1,4 +1,5 @@
from django.db.models import Lookup, Transform from django.db.models import Lookup, Transform
from django.utils.encoding import force_text
from .search import SearchVector, SearchVectorExact, SearchVectorField from .search import SearchVector, SearchVectorExact, SearchVectorField
@ -29,14 +30,18 @@ class Overlap(PostgresSimpleLookup):
class HasKey(PostgresSimpleLookup): class HasKey(PostgresSimpleLookup):
lookup_name = 'has_key' lookup_name = 'has_key'
operator = '?' operator = '?'
prepare_rhs = False
class HasKeys(PostgresSimpleLookup): class HasKeys(PostgresSimpleLookup):
lookup_name = 'has_keys' lookup_name = 'has_keys'
operator = '?&' 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' lookup_name = 'has_any_keys'
operator = '?|' operator = '?|'

View File

@ -213,7 +213,7 @@ class BaseExpression(object):
def _prepare(self, field): 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 return self

View File

@ -741,8 +741,7 @@ class Field(RegisterLookupMixin):
"""Returns field's value prepared for interacting with the database """Returns field's value prepared for interacting with the database
backend. backend.
Used by the default implementations of ``get_db_prep_save``and Used by the default implementations of get_db_prep_save().
`get_db_prep_lookup```
""" """
if not prepared: if not prepared:
value = self.get_prep_value(value) value = self.get_prep_value(value)
@ -755,36 +754,6 @@ class Field(RegisterLookupMixin):
return self.get_db_prep_value(value, connection=connection, return self.get_db_prep_value(value, connection=connection,
prepared=False) 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): def has_default(self):
""" """
Returns a boolean of whether this field has a default value. Returns a boolean of whether this field has a default value.
@ -1049,20 +1018,11 @@ class BooleanField(Field):
params={'value': value}, 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): def get_prep_value(self, value):
value = super(BooleanField, self).get_prep_value(value) value = super(BooleanField, self).get_prep_value(value)
if value is None: if value is None:
return None return None
return bool(value) return self.to_python(value)
def formfield(self, **kwargs): def formfield(self, **kwargs):
# Unlike most fields, BooleanField figures out include_blank from # 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 # contribute_to_class is inherited from DateField, it registers
# get_next_by_FOO and get_prev_by_FOO # get_next_by_FOO and get_prev_by_FOO
# get_prep_lookup is inherited from DateField
def get_prep_value(self, value): def get_prep_value(self, value):
value = super(DateTimeField, self).get_prep_value(value) value = super(DateTimeField, self).get_prep_value(value)
value = self.to_python(value) value = self.to_python(value)
@ -2051,21 +2009,11 @@ class NullBooleanField(Field):
params={'value': value}, 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): def get_prep_value(self, value):
value = super(NullBooleanField, self).get_prep_value(value) value = super(NullBooleanField, self).get_prep_value(value)
if value is None: if value is None:
return None return None
return bool(value) return self.to_python(value)
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = { defaults = {

View File

@ -271,11 +271,6 @@ class FileField(Field):
def get_internal_type(self): def get_internal_type(self):
return "FileField" 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): def get_prep_value(self, value):
"Returns field's value prepared for saving into a database." "Returns field's value prepared for saving into a database."
value = super(FileField, self).get_prep_value(value) value = super(FileField, self).get_prep_value(value)

View File

@ -44,15 +44,15 @@ class RelatedIn(In):
if not isinstance(self.lhs, MultiColSource) and self.rhs_is_direct_value(): if not isinstance(self.lhs, MultiColSource) and self.rhs_is_direct_value():
# If we get here, we are dealing with single-column relations. # 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] 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 # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself
# doesn't have validation for non-integers, so we must run validation # doesn't have validation for non-integers, so we must run validation
# using the target field. # using the target field.
if hasattr(self.lhs.output_field, 'get_path_info'): 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. # 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( target_field = self.lhs.output_field.get_path_info()[-1].target_fields[-1]
self.lookup_name, self.rhs) self.rhs = [target_field.get_prep_value(v) for v in self.rhs]
return super(RelatedIn, self).get_prep_lookup() return super(RelatedIn, self).get_prep_lookup()
def as_sql(self, compiler, connection): 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 not isinstance(self.lhs, MultiColSource) and self.rhs_is_direct_value():
# If we get here, we are dealing with single-column relations. # If we get here, we are dealing with single-column relations.
self.rhs = get_normalized_value(self.rhs, self.lhs)[0] 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 # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself
# doesn't have validation for non-integers, so we must run validation # doesn't have validation for non-integers, so we must run validation
# using the target field. # using the target field.
if hasattr(self.lhs.output_field, 'get_path_info'): if hasattr(self.lhs.output_field, 'get_path_info'):
# Get the target field. We can safely assume there is only one # Get the target field. We can safely assume there is only one
# as we don't get to the direct value branch otherwise. # 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( target_field = self.lhs.output_field.get_path_info()[-1].target_fields[-1]
self.lookup_name, self.rhs) self.rhs = target_field.get_prep_value(self.rhs)
return super(RelatedLookupMixin, self).get_prep_lookup() return super(RelatedLookupMixin, self).get_prep_lookup()

View File

@ -110,9 +110,6 @@ class ForeignObjectRel(object):
def one_to_one(self): def one_to_one(self):
return self.field.one_to_one 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): def get_lookup(self, lookup_name):
return self.field.get_lookup(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() (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): def is_hidden(self):
"Should the related object be hidden?" "Should the related object be hidden?"
return bool(self.related_name) and self.related_name[-1] == '+' return bool(self.related_name) and self.related_name[-1] == '+'

View File

@ -16,6 +16,7 @@ from django.utils.six.moves import range
class Lookup(object): class Lookup(object):
lookup_name = None lookup_name = None
prepare_rhs = True
def __init__(self, lhs, rhs): def __init__(self, lhs, rhs):
self.lhs, self.rhs = lhs, rhs self.lhs, self.rhs = lhs, rhs
@ -56,12 +57,14 @@ class Lookup(object):
return sqls, sqls_params return sqls, sqls_params
def get_prep_lookup(self): 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): def get_db_prep_lookup(self, value, connection):
return ( return ('%s', [value])
'%s', self.lhs.output_field.get_db_prep_lookup(
self.lookup_name, value, connection, prepared=True))
def process_lhs(self, compiler, connection, lhs=None): def process_lhs(self, compiler, connection, lhs=None):
lhs = lhs or self.lhs lhs = lhs or self.lhs
@ -199,6 +202,7 @@ Field.register_lookup(Exact)
class IExact(BuiltinLookup): class IExact(BuiltinLookup):
lookup_name = 'iexact' lookup_name = 'iexact'
prepare_rhs = False
def process_rhs(self, qn, connection): def process_rhs(self, qn, connection):
rhs, params = super(IExact, self).process_rhs(qn, connection) rhs, params = super(IExact, self).process_rhs(qn, connection)
@ -254,6 +258,13 @@ IntegerField.register_lookup(IntegerLessThan)
class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup): class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
lookup_name = 'in' 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): def process_rhs(self, compiler, connection):
db_rhs = getattr(self.rhs, '_db', None) db_rhs = getattr(self.rhs, '_db', None)
if db_rhs is not None and db_rhs != connection.alias: if db_rhs is not None and db_rhs != connection.alias:
@ -335,6 +346,7 @@ class PatternLookup(BuiltinLookup):
class Contains(PatternLookup): class Contains(PatternLookup):
lookup_name = 'contains' lookup_name = 'contains'
prepare_rhs = False
def process_rhs(self, qn, connection): def process_rhs(self, qn, connection):
rhs, params = super(Contains, self).process_rhs(qn, connection) rhs, params = super(Contains, self).process_rhs(qn, connection)
@ -346,11 +358,13 @@ Field.register_lookup(Contains)
class IContains(Contains): class IContains(Contains):
lookup_name = 'icontains' lookup_name = 'icontains'
prepare_rhs = False
Field.register_lookup(IContains) Field.register_lookup(IContains)
class StartsWith(PatternLookup): class StartsWith(PatternLookup):
lookup_name = 'startswith' lookup_name = 'startswith'
prepare_rhs = False
def process_rhs(self, qn, connection): def process_rhs(self, qn, connection):
rhs, params = super(StartsWith, self).process_rhs(qn, connection) rhs, params = super(StartsWith, self).process_rhs(qn, connection)
@ -362,6 +376,7 @@ Field.register_lookup(StartsWith)
class IStartsWith(PatternLookup): class IStartsWith(PatternLookup):
lookup_name = 'istartswith' lookup_name = 'istartswith'
prepare_rhs = False
def process_rhs(self, qn, connection): def process_rhs(self, qn, connection):
rhs, params = super(IStartsWith, self).process_rhs(qn, connection) rhs, params = super(IStartsWith, self).process_rhs(qn, connection)
@ -373,6 +388,7 @@ Field.register_lookup(IStartsWith)
class EndsWith(PatternLookup): class EndsWith(PatternLookup):
lookup_name = 'endswith' lookup_name = 'endswith'
prepare_rhs = False
def process_rhs(self, qn, connection): def process_rhs(self, qn, connection):
rhs, params = super(EndsWith, self).process_rhs(qn, connection) rhs, params = super(EndsWith, self).process_rhs(qn, connection)
@ -384,6 +400,7 @@ Field.register_lookup(EndsWith)
class IEndsWith(PatternLookup): class IEndsWith(PatternLookup):
lookup_name = 'iendswith' lookup_name = 'iendswith'
prepare_rhs = False
def process_rhs(self, qn, connection): def process_rhs(self, qn, connection):
rhs, params = super(IEndsWith, self).process_rhs(qn, connection) rhs, params = super(IEndsWith, self).process_rhs(qn, connection)
@ -396,6 +413,11 @@ Field.register_lookup(IEndsWith)
class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup): class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
lookup_name = 'range' 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): def get_rhs_op(self, connection, rhs):
return "BETWEEN %s AND %s" % (rhs[0], rhs[1]) return "BETWEEN %s AND %s" % (rhs[0], rhs[1])
@ -411,6 +433,7 @@ Field.register_lookup(Range)
class IsNull(BuiltinLookup): class IsNull(BuiltinLookup):
lookup_name = 'isnull' lookup_name = 'isnull'
prepare_rhs = False
def as_sql(self, compiler, connection): def as_sql(self, compiler, connection):
sql, params = compiler.compile(self.lhs) sql, params = compiler.compile(self.lhs)
@ -423,6 +446,7 @@ Field.register_lookup(IsNull)
class Search(BuiltinLookup): class Search(BuiltinLookup):
lookup_name = 'search' lookup_name = 'search'
prepare_rhs = False
def as_sql(self, compiler, connection): def as_sql(self, compiler, connection):
warnings.warn( warnings.warn(
@ -438,6 +462,7 @@ Field.register_lookup(Search)
class Regex(BuiltinLookup): class Regex(BuiltinLookup):
lookup_name = 'regex' lookup_name = 'regex'
prepare_rhs = False
def as_sql(self, compiler, connection): def as_sql(self, compiler, connection):
if self.lookup_name in connection.operators: if self.lookup_name in connection.operators:

View File

@ -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 to the value so that code holding references to the model will always see the
correct value. 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 </howto/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-form-field-for-model-field:
Specifying the form field for a model field Specifying the form field for a model field

View File

@ -1717,8 +1717,7 @@ Field API reference
``Field`` is an abstract class that represents a database table column. ``Field`` is an abstract class that represents a database table column.
Django uses fields to create the database table (:meth:`db_type`), to map Django uses fields to create the database table (:meth:`db_type`), to map
Python types to database (:meth:`get_prep_value`) and vice-versa Python types to database (:meth:`get_prep_value`) and vice-versa
(:meth:`from_db_value`), and to apply :doc:`/ref/models/lookups` (:meth:`from_db_value`).
(:meth:`get_prep_lookup`).
A field is thus a fundamental piece in different Django APIs, notably, A field is thus a fundamental piece in different Django APIs, notably,
:class:`models <django.db.models.Model>` and :class:`querysets :class:`models <django.db.models.Model>` and :class:`querysets
@ -1847,26 +1846,6 @@ Field API reference
See :ref:`preprocessing-values-before-saving` for usage. 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 Fields often receive their values as a different type, either from
serialization or from forms. serialization or from forms.

View File

@ -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: # Unsalted MD5 passwords might not have an 'md5$$' prefix:
User.objects.filter(password__length=32) 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` :mod:`django.contrib.gis`
------------------------- -------------------------

View File

@ -1,29 +1,29 @@
from django.core.exceptions import ValidationError 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 django.test import SimpleTestCase, TestCase
from .models import BooleanModel, FksToBooleans, NullBooleanModel from .models import BooleanModel, FksToBooleans, NullBooleanModel
class BooleanFieldTests(TestCase): class BooleanFieldTests(TestCase):
def _test_get_db_prep_lookup(self, f): def _test_get_prep_value(self, f):
self.assertEqual(f.get_db_prep_lookup('exact', True, connection=connection), [True]) self.assertEqual(f.get_prep_value(True), True)
self.assertEqual(f.get_db_prep_lookup('exact', '1', connection=connection), [True]) self.assertEqual(f.get_prep_value('1'), True)
self.assertEqual(f.get_db_prep_lookup('exact', 1, connection=connection), [True]) self.assertEqual(f.get_prep_value(1), True)
self.assertEqual(f.get_db_prep_lookup('exact', False, connection=connection), [False]) self.assertEqual(f.get_prep_value(False), False)
self.assertEqual(f.get_db_prep_lookup('exact', '0', connection=connection), [False]) self.assertEqual(f.get_prep_value('0'), False)
self.assertEqual(f.get_db_prep_lookup('exact', 0, connection=connection), [False]) self.assertEqual(f.get_prep_value(0), False)
self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None]) self.assertEqual(f.get_prep_value(None), None)
def _test_to_python(self, f): def _test_to_python(self, f):
self.assertIs(f.to_python(1), True) self.assertIs(f.to_python(1), True)
self.assertIs(f.to_python(0), False) self.assertIs(f.to_python(0), False)
def test_booleanfield_get_db_prep_lookup(self): def test_booleanfield_get_prep_value(self):
self._test_get_db_prep_lookup(models.BooleanField()) self._test_get_prep_value(models.BooleanField())
def test_nullbooleanfield_get_db_prep_lookup(self): def test_nullbooleanfield_get_prep_value(self):
self._test_get_db_prep_lookup(models.NullBooleanField()) self._test_get_prep_value(models.NullBooleanField())
def test_booleanfield_to_python(self): def test_booleanfield_to_python(self):
self._test_to_python(models.BooleanField()) self._test_to_python(models.BooleanField())

View File

@ -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)

View File

@ -2,7 +2,7 @@ from decimal import Decimal
from django.core import validators from django.core import validators
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import connection, models from django.db import models
from django.test import TestCase from django.test import TestCase
from .models import BigD, Foo 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(f.to_python('2.6')), '2.6')
self.assertEqual(f._format(None), None) 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) 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): def test_filter_with_strings(self):
""" """

View File

@ -1229,9 +1229,8 @@ class Queries2Tests(TestCase):
) )
def test_ticket12239(self): def test_ticket12239(self):
# Float was being rounded to integer on gte queries on integer field. Tests # Custom lookups are registered to round float values correctly on gte
# show that gt, lt, gte, and lte work as desired. Note that the fix changes # and lt IntegerField queries.
# get_prep_lookup for gte and lt queries only.
self.assertQuerysetEqual( self.assertQuerysetEqual(
Number.objects.filter(num__gt=11.9), Number.objects.filter(num__gt=11.9),
['<Number: 12>'] ['<Number: 12>']