Fixed #22936 -- Obsoleted Field.get_prep_lookup()/get_db_prep_lookup()
Thanks Tim Graham for completing the initial patch.
This commit is contained in:
parent
1206eea11e
commit
388bb5bd9a
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = '?|'
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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] == '+'
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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`
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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>']
|
||||||
|
|
Loading…
Reference in New Issue