mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
[soc2009/multidb] Cleaned up the double processing required by validate() by splitting get_db_prep_* functions into db-specific and non-db-specific parts. Patch from Russell Keith-Magee.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11786 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
5a5e082161
commit
0ca0ed0453
4
TODO
4
TODO
@ -7,10 +7,6 @@ Required for v1.2
|
||||
* Finalize the sql.Query internals
|
||||
* Clean up the use of db.backend.query_class()
|
||||
* Verify it still works with GeoDjango
|
||||
* Cleanup of new API entry points
|
||||
* validate() on a field
|
||||
* name/purpose clash with Honza?
|
||||
* any overlap with existing methods?
|
||||
|
||||
Optional for v1.2
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
@ -180,21 +180,56 @@ class Field(object):
|
||||
"Returns field's value just before saving."
|
||||
return getattr(model_instance, self.attname)
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
"Perform preliminary non-db specific value checks and conversions."
|
||||
return value
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
"""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```
|
||||
"""
|
||||
if not prepared:
|
||||
value = self.get_prep_value(value)
|
||||
return value
|
||||
|
||||
def get_db_prep_save(self, value, connection):
|
||||
"Returns field's value prepared for saving into a database."
|
||||
return self.get_db_prep_value(value, connection=connection)
|
||||
return self.get_db_prep_value(value, connection=connection, prepared=False)
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection):
|
||||
def get_prep_lookup(self, lookup_type, value):
|
||||
"Perform preliminary non-db specific lookup checks and conversions"
|
||||
if hasattr(value, 'prepare'):
|
||||
return value.prepare()
|
||||
if hasattr(value, '_prepare'):
|
||||
return value._prepare()
|
||||
|
||||
if lookup_type in (
|
||||
'regex', 'iregex', 'month', 'day', 'week_day', 'search',
|
||||
'contains', 'icontains', 'iexact', 'startswith', 'istartswith',
|
||||
'endswith', 'iendswith', 'isnull'
|
||||
):
|
||||
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]
|
||||
elif lookup_type == 'year':
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
raise ValueError("The __year lookup type requires an integer argument")
|
||||
|
||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
||||
|
||||
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)
|
||||
if hasattr(value, 'get_compiler'):
|
||||
value = value.get_compiler(connection=connection)
|
||||
if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'):
|
||||
# If the value has a relabel_aliases method, it will need to
|
||||
# be invoked before the final SQL is evaluated
|
||||
@ -206,13 +241,12 @@ class Field(object):
|
||||
sql, params = value._as_sql(connection=connection)
|
||||
return QueryWrapper(('(%s)' % sql), params)
|
||||
|
||||
|
||||
if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', 'search'):
|
||||
return [value]
|
||||
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
|
||||
return [self.get_db_prep_value(value, connection=connection)]
|
||||
return [self.get_db_prep_value(value, connection=connection, prepared=prepared)]
|
||||
elif lookup_type in ('range', 'in'):
|
||||
return [self.get_db_prep_value(v, connection=connection) for v in value]
|
||||
return [self.get_db_prep_value(v, connection=connection, prepared=prepared) for v in value]
|
||||
elif lookup_type in ('contains', 'icontains'):
|
||||
return ["%%%s%%" % connection.ops.prep_for_like_query(value)]
|
||||
elif lookup_type == 'iexact':
|
||||
@ -224,36 +258,11 @@ class Field(object):
|
||||
elif lookup_type == 'isnull':
|
||||
return []
|
||||
elif lookup_type == 'year':
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
raise ValueError("The __year lookup type requires an integer argument")
|
||||
|
||||
if self.get_internal_type() == 'DateField':
|
||||
return connection.ops.year_lookup_bounds_for_date_field(value)
|
||||
else:
|
||||
return connection.ops.year_lookup_bounds(value)
|
||||
|
||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
||||
|
||||
def validate(self, lookup_type, value):
|
||||
"""
|
||||
Validate that the data is valid, as much so as possible without knowing
|
||||
what connection we are using. Returns True if the value was
|
||||
successfully validated and false if the value wasn't validated (this
|
||||
doesn't consider whether the value was actually valid, an exception is
|
||||
raised in those circumstances).
|
||||
"""
|
||||
if hasattr(value, 'validate') or hasattr(value, '_validate'):
|
||||
if hasattr(value, 'validate'):
|
||||
value.validate()
|
||||
else:
|
||||
value._validate()
|
||||
return True
|
||||
if lookup_type == 'isnull':
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_default(self):
|
||||
"Returns a boolean of whether this field has a default value."
|
||||
return self.default is not NOT_PROVIDED
|
||||
@ -376,22 +385,11 @@ class AutoField(Field):
|
||||
raise exceptions.ValidationError(
|
||||
_("This value must be an integer."))
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return int(value)
|
||||
|
||||
def validate(self, lookup_type, value):
|
||||
if super(AutoField, self).validate(lookup_type, value):
|
||||
return
|
||||
if value is None or hasattr(value, 'as_sql'):
|
||||
return
|
||||
if lookup_type in ('range', 'in'):
|
||||
for val in value:
|
||||
int(val)
|
||||
else:
|
||||
int(value)
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
assert not cls._meta.has_auto_field, "A model can't have more than one AutoField."
|
||||
super(AutoField, self).contribute_to_class(cls, name)
|
||||
@ -419,24 +417,16 @@ class BooleanField(Field):
|
||||
raise exceptions.ValidationError(
|
||||
_("This value must be either True or False."))
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection):
|
||||
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_db_prep_lookup(lookup_type, value,
|
||||
connection=connection)
|
||||
return super(BooleanField, self).get_prep_lookup(lookup_type, value)
|
||||
|
||||
def validate(self, lookup_type, value):
|
||||
if super(BooleanField, self).validate(lookup_type, value):
|
||||
return
|
||||
if value in ('1', '0'):
|
||||
value = int(value)
|
||||
bool(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value)
|
||||
@ -539,31 +529,21 @@ class DateField(Field):
|
||||
setattr(cls, 'get_previous_by_%s' % self.name,
|
||||
curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False))
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection):
|
||||
def get_prep_lookup(self, lookup_type, value):
|
||||
# For "__month", "__day", and "__week_day" lookups, convert the value
|
||||
# to an int so the database backend always sees a consistent type.
|
||||
if lookup_type in ('month', 'day', 'week_day'):
|
||||
return [int(value)]
|
||||
return super(DateField, self).get_db_prep_lookup(lookup_type, value,
|
||||
connection=connection)
|
||||
return int(value)
|
||||
return super(DateField, self).get_prep_lookup(lookup_type, value)
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
return self.to_python(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
# Casts dates into the format expected by the backend
|
||||
return connection.ops.value_to_db_date(self.to_python(value))
|
||||
|
||||
def validate(self, lookup_type, value):
|
||||
if super(DateField, self).validate(lookup_type, value):
|
||||
return
|
||||
if value is None:
|
||||
return
|
||||
if lookup_type in ('month', 'day', 'year', 'week_day'):
|
||||
int(value)
|
||||
return
|
||||
if lookup_type in ('in', 'range'):
|
||||
for val in value:
|
||||
self.to_python(val)
|
||||
return
|
||||
self.to_python(value)
|
||||
if not prepared:
|
||||
value = self.get_prep_value(value)
|
||||
return connection.ops.value_to_db_date(value)
|
||||
|
||||
def value_to_string(self, obj):
|
||||
val = self._get_val_from_obj(obj)
|
||||
@ -619,9 +599,14 @@ class DateTimeField(DateField):
|
||||
raise exceptions.ValidationError(
|
||||
_('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
return self.to_python(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
# Casts dates into the format expected by the backend
|
||||
return connection.ops.value_to_db_datetime(self.to_python(value))
|
||||
if not prepared:
|
||||
value = self.get_prep_value(value)
|
||||
return connection.ops.value_to_db_datetime(value)
|
||||
|
||||
def value_to_string(self, obj):
|
||||
val = self._get_val_from_obj(obj)
|
||||
@ -679,7 +664,7 @@ class DecimalField(Field):
|
||||
return connection.ops.value_to_db_decimal(self.to_python(value),
|
||||
self.max_digits, self.decimal_places)
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
return self.to_python(value)
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
@ -723,7 +708,7 @@ class FilePathField(Field):
|
||||
class FloatField(Field):
|
||||
empty_strings_allowed = False
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return float(value)
|
||||
@ -747,7 +732,7 @@ class FloatField(Field):
|
||||
|
||||
class IntegerField(Field):
|
||||
empty_strings_allowed = False
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return int(value)
|
||||
@ -800,22 +785,16 @@ class NullBooleanField(Field):
|
||||
raise exceptions.ValidationError(
|
||||
_("This value must be either None, True or False."))
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection):
|
||||
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_db_prep_lookup(lookup_type,
|
||||
value, connection=connection)
|
||||
return super(NullBooleanField, self).get_prep_lookup(lookup_type, value)
|
||||
|
||||
def validate(self, lookup_type, value):
|
||||
if value in ('1', '0'):
|
||||
value = int(value)
|
||||
bool(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value)
|
||||
@ -931,9 +910,14 @@ class TimeField(Field):
|
||||
else:
|
||||
return super(TimeField, self).pre_save(model_instance, add)
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
return self.to_python(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
# Casts times into the format expected by the backend
|
||||
return connection.ops.value_to_db_time(self.to_python(value))
|
||||
if not prepared:
|
||||
value = self.get_prep_value(value)
|
||||
return connection.ops.value_to_db_time(value)
|
||||
|
||||
def value_to_string(self, obj):
|
||||
val = self._get_val_from_obj(obj)
|
||||
|
@ -232,13 +232,12 @@ class FileField(Field):
|
||||
def get_internal_type(self):
|
||||
return "FileField"
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection):
|
||||
def get_prep_lookup(self, lookup_type, value):
|
||||
if hasattr(value, 'name'):
|
||||
value = value.name
|
||||
return super(FileField, self).get_db_prep_lookup(lookup_type, value,
|
||||
connection=connection)
|
||||
return super(FileField, self).get_prep_lookup(lookup_type, value)
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
"Returns field's value prepared for saving into a database."
|
||||
# Need to convert File objects provided via a form to unicode for database insertion
|
||||
if value is None:
|
||||
|
@ -120,7 +120,7 @@ class RelatedField(object):
|
||||
if not cls._meta.abstract:
|
||||
self.contribute_to_related_class(other, self.related)
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection):
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
||||
# If we are doing a lookup on a Related Field, we must be
|
||||
# comparing object instances. The value should be the PK of value,
|
||||
# not value itself.
|
||||
@ -140,14 +140,16 @@ class RelatedField(object):
|
||||
if field:
|
||||
if lookup_type in ('range', 'in'):
|
||||
v = [v]
|
||||
v = field.get_db_prep_lookup(lookup_type, v, connection=connection)
|
||||
v = field.get_db_prep_lookup(lookup_type, v,
|
||||
connection=connection, prepared=prepared)
|
||||
if isinstance(v, list):
|
||||
v = v[0]
|
||||
return v
|
||||
|
||||
if not prepared:
|
||||
value = self.get_prep_lookup(lookup_type, value)
|
||||
if hasattr(value, 'get_compiler'):
|
||||
value = value.get_compiler(connection=connection)
|
||||
|
||||
if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'):
|
||||
# If the value has a relabel_aliases method, it will need to
|
||||
# be invoked before the final SQL is evaluated
|
||||
|
@ -11,25 +11,53 @@ from warnings import warn
|
||||
|
||||
def call_with_connection(func):
|
||||
arg_names, varargs, varkwargs, defaults = getargspec(func)
|
||||
takes_connection = 'connection' in arg_names or varkwargs
|
||||
if not takes_connection:
|
||||
warn("A Field class whose %s method doesn't take connection has been "
|
||||
"defined. Please add a connection argument" % func.__name__,
|
||||
updated = ('connection' in arg_names or varkwargs)
|
||||
if not updated:
|
||||
warn("A Field class whose %s method hasn't been updated to take a "
|
||||
"`connection` argument." % func.__name__,
|
||||
PendingDeprecationWarning, stacklevel=2)
|
||||
|
||||
def inner(*args, **kwargs):
|
||||
if 'connection' not in kwargs:
|
||||
from django.db import connection
|
||||
kwargs['connection'] = connection
|
||||
warn("%s has been called without providing a connection argument. "
|
||||
"Please provide one" % func.__name__, PendingDeprecationWarning,
|
||||
warn("%s has been called without providing a connection argument. " %
|
||||
func.__name__, PendingDeprecationWarning,
|
||||
stacklevel=1)
|
||||
if takes_connection:
|
||||
if updated:
|
||||
return func(*args, **kwargs)
|
||||
if 'connection' in kwargs:
|
||||
del kwargs['connection']
|
||||
return func(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
def call_with_connection_and_prepared(func):
|
||||
arg_names, varargs, varkwargs, defaults = getargspec(func)
|
||||
updated = (
|
||||
('connection' in arg_names or varkwargs) and
|
||||
('prepared' in arg_names or varkwargs)
|
||||
)
|
||||
if not updated:
|
||||
warn("A Field class whose %s method hasn't been updated to take "
|
||||
"`connection` and `prepared` arguments." % func.__name__,
|
||||
PendingDeprecationWarning, stacklevel=2)
|
||||
|
||||
def inner(*args, **kwargs):
|
||||
if 'connection' not in kwargs:
|
||||
from django.db import connection
|
||||
kwargs['connection'] = connection
|
||||
warn("%s has been called without providing a connection argument. " %
|
||||
func.__name__, PendingDeprecationWarning,
|
||||
stacklevel=1)
|
||||
if updated:
|
||||
return func(*args, **kwargs)
|
||||
if 'connection' in kwargs:
|
||||
del kwargs['connection']
|
||||
if 'prepared' in kwargs:
|
||||
del kwargs['prepared']
|
||||
return func(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
class LegacyConnection(type):
|
||||
"""
|
||||
A metaclass to normalize arguments give to the get_db_prep_* and db_type
|
||||
@ -37,9 +65,10 @@ class LegacyConnection(type):
|
||||
"""
|
||||
def __new__(cls, names, bases, attrs):
|
||||
new_cls = super(LegacyConnection, cls).__new__(cls, names, bases, attrs)
|
||||
for attr in ('db_type', 'get_db_prep_save', 'get_db_prep_lookup',
|
||||
'get_db_prep_value'):
|
||||
for attr in ('db_type', 'get_db_prep_save'):
|
||||
setattr(new_cls, attr, call_with_connection(getattr(new_cls, attr)))
|
||||
for attr in ('get_db_prep_lookup', 'get_db_prep_value'):
|
||||
setattr(new_cls, attr, call_with_connection_and_prepared(getattr(new_cls, attr)))
|
||||
return new_cls
|
||||
|
||||
class SubfieldBase(LegacyConnection):
|
||||
|
@ -739,6 +739,9 @@ class QuerySet(object):
|
||||
self.query.add_fields(field_names, False)
|
||||
self.query.set_group_by()
|
||||
|
||||
def _prepare(self):
|
||||
return self
|
||||
|
||||
def _as_sql(self, connection):
|
||||
"""
|
||||
Returns the internal query's SQL and parameters (as a tuple).
|
||||
@ -748,13 +751,6 @@ class QuerySet(object):
|
||||
return obj.query.get_compiler(connection=connection).as_nested_sql()
|
||||
raise ValueError("Can't do subqueries with queries on different DBs.")
|
||||
|
||||
def _validate(self):
|
||||
"""
|
||||
A normal QuerySet is always valid when used as the RHS of a filter,
|
||||
since it automatically gets filtered down to 1 field.
|
||||
"""
|
||||
pass
|
||||
|
||||
# When used as part of a nested query, a queryset will never be an "always
|
||||
# empty" result.
|
||||
value_annotation = True
|
||||
@ -877,7 +873,7 @@ class ValuesQuerySet(QuerySet):
|
||||
return obj.query.get_compiler(connection=connection).as_nested_sql()
|
||||
raise ValueError("Can't do subqueries with queries on different DBs.")
|
||||
|
||||
def _validate(self):
|
||||
def _prepare(self):
|
||||
"""
|
||||
Validates that we aren't trying to do a query like
|
||||
value__in=qs.values('value1', 'value2'), which isn't valid.
|
||||
@ -886,7 +882,7 @@ class ValuesQuerySet(QuerySet):
|
||||
(not self._fields and len(self.model._meta.fields) > 1)):
|
||||
raise TypeError('Cannot use a multi-field %s as a filter value.'
|
||||
% self.__class__.__name__)
|
||||
|
||||
return self
|
||||
|
||||
class ValuesListQuerySet(ValuesQuerySet):
|
||||
def iterator(self):
|
||||
|
@ -18,9 +18,10 @@ class RelatedObject(object):
|
||||
self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name)
|
||||
self.var_name = self.opts.object_name.lower()
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection):
|
||||
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)
|
||||
return self.field.get_db_prep_lookup(lookup_type, value,
|
||||
connection=connection, prepared=prepared)
|
||||
|
||||
def editable_fields(self):
|
||||
"Get the fields in this class that should be edited inline."
|
||||
|
@ -837,7 +837,7 @@ class SQLUpdateCompiler(SQLCompiler):
|
||||
self.query.related_ids = idents
|
||||
else:
|
||||
# The fast path. Filters and updates in one query.
|
||||
self.query.add_filter(('pk__in', query.get_compiler(self.using)))
|
||||
self.query.add_filter(('pk__in', query))
|
||||
for alias in self.query.tables[1:]:
|
||||
self.query.alias_refcount[alias] = 0
|
||||
|
||||
|
@ -11,6 +11,9 @@ class SQLEvaluator(object):
|
||||
self.contains_aggregate = False
|
||||
self.expression.prepare(self, query, allow_joins)
|
||||
|
||||
def prepare(self):
|
||||
return self
|
||||
|
||||
def as_sql(self, qn, connection):
|
||||
return self.expression.evaluate(self, qn, connection)
|
||||
|
||||
|
@ -141,6 +141,9 @@ class Query(object):
|
||||
|
||||
self.__dict__.update(obj_dict)
|
||||
|
||||
def prepare(self):
|
||||
return self
|
||||
|
||||
def get_compiler(self, using=None, connection=None):
|
||||
if using is None and connection is None:
|
||||
raise ValueError("Need either using or connection")
|
||||
|
@ -62,8 +62,8 @@ class WhereNode(tree.Node):
|
||||
else:
|
||||
annotation = bool(value)
|
||||
|
||||
if hasattr(obj, "process"):
|
||||
obj.validate(lookup_type, value)
|
||||
if hasattr(obj, "prepare"):
|
||||
value = obj.prepare(lookup_type, value)
|
||||
super(WhereNode, self).add((obj, lookup_type, annotation, value),
|
||||
connector)
|
||||
return
|
||||
@ -143,7 +143,7 @@ class WhereNode(tree.Node):
|
||||
raise EmptyResultSet
|
||||
else:
|
||||
params = Field().get_db_prep_lookup(lookup_type, params_or_value,
|
||||
connection=connection)
|
||||
connection=connection, prepared=True)
|
||||
if isinstance(lvalue, tuple):
|
||||
# A direct database column lookup.
|
||||
field_sql = self.sql_for_columns(lvalue, qn, connection)
|
||||
@ -262,6 +262,11 @@ class Constraint(object):
|
||||
def __init__(self, alias, col, field):
|
||||
self.alias, self.col, self.field = alias, col, field
|
||||
|
||||
def prepare(self, lookup_type, value):
|
||||
if self.field:
|
||||
return self.field.get_prep_lookup(lookup_type, value)
|
||||
return value
|
||||
|
||||
def process(self, lookup_type, value, connection):
|
||||
"""
|
||||
Returns a tuple of data suitable for inclusion in a WhereNode
|
||||
@ -272,14 +277,14 @@ class Constraint(object):
|
||||
try:
|
||||
if self.field:
|
||||
params = self.field.get_db_prep_lookup(lookup_type, value,
|
||||
connection=connection)
|
||||
connection=connection, prepared=True)
|
||||
db_type = self.field.db_type(connection=connection)
|
||||
else:
|
||||
# This branch is used at times when we add a comparison to NULL
|
||||
# (we don't really want to waste time looking up the associated
|
||||
# field object at the calling location).
|
||||
params = Field().get_db_prep_lookup(lookup_type, value,
|
||||
connection=connection)
|
||||
connection=connection, prepared=True)
|
||||
db_type = None
|
||||
except ObjectDoesNotExist:
|
||||
raise EmptyShortCircuit
|
||||
@ -289,7 +294,3 @@ class Constraint(object):
|
||||
def relabel_aliases(self, change_map):
|
||||
if self.alias in change_map:
|
||||
self.alias = change_map[self.alias]
|
||||
|
||||
def validate(self, lookup_type, value):
|
||||
if hasattr(self.field, 'validate'):
|
||||
self.field.validate(lookup_type, value)
|
||||
|
@ -396,36 +396,58 @@ Python object type we want to store in the model's attribute.
|
||||
called when it is created, you should be using `The SubfieldBase metaclass`_
|
||||
mentioned earlier. Otherwise :meth:`to_python` won't be called automatically.
|
||||
|
||||
Converting Python objects to database values
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Converting Python objects to query values
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. method:: get_db_prep_value(self, value, connection)
|
||||
.. method:: get_prep_value(self, value)
|
||||
|
||||
This is the reverse of :meth:`to_python` when working with the database backends
|
||||
(as opposed to serialization). The ``value`` parameter is the current value of
|
||||
the model's attribute (a field has no reference to its containing model, so it
|
||||
cannot retrieve the value itself), and the method should return data in a format
|
||||
that can be used as a parameter in a query for the database backend. The
|
||||
specific connection that will be used for the query is passed as the
|
||||
``connection`` parameter, this allows you to generate the value in a backend
|
||||
specific mannner if necessary.
|
||||
This is the reverse of :meth:`to_python` when working with the
|
||||
database backends (as opposed to serialization). The ``value``
|
||||
parameter is the current value of the model's attribute (a field has
|
||||
no reference to its containing model, so it cannot retrieve the value
|
||||
itself), and the method should return data in a format that has been
|
||||
prepared for use as a parameter in a query.
|
||||
|
||||
This conversion should *not* include any database-specific
|
||||
conversions. If database-specific conversions are required, they
|
||||
should be made in the call to :meth:`get_db_prep_value`.
|
||||
|
||||
For example::
|
||||
|
||||
class HandField(models.Field):
|
||||
# ...
|
||||
|
||||
def get_db_prep_value(self, value, connection):
|
||||
def get_prep_value(self, value):
|
||||
return ''.join([''.join(l) for l in (value.north,
|
||||
value.east, value.south, value.west)])
|
||||
|
||||
Converting query values to database values
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. method:: get_db_prep_value(self, value, connection, prepared=False)
|
||||
|
||||
Some data types (for example, dates) need to be in a specific format
|
||||
before they can be used by a database backend.
|
||||
:meth:`get_db_prep_value` is the method where those conversions should
|
||||
be made. The specific connection that will be used for the query is
|
||||
passed as the ``connection`` parameter. This allows you to use
|
||||
backend-specific conversion logic if it is required.
|
||||
|
||||
The ``prepared`` argument describes whether or not the value has
|
||||
already been passed through :meth:`get_prep_value` conversions. When
|
||||
``prepared`` is False, the default implementation of
|
||||
:meth:`get_db_prep_value` will call :meth:`get_prep_value` to do
|
||||
initial data conversions before performing any database-specific
|
||||
processing.
|
||||
|
||||
.. method:: get_db_prep_save(self, value, connection)
|
||||
|
||||
Same as the above, but called when the Field value must be *saved* to the
|
||||
database. As the default implementation just calls ``get_db_prep_value``, you
|
||||
shouldn't need to implement this method unless your custom field needs a
|
||||
special conversion when being saved that is not the same as the conversion used
|
||||
for normal query parameters (which is implemented by ``get_db_prep_value``).
|
||||
Same as the above, but called when the Field value must be *saved* to
|
||||
the database. As the default implementation just calls
|
||||
``get_db_prep_value``, you shouldn't need to implement this method
|
||||
unless your custom field needs a special conversion when being saved
|
||||
that is not the same as the conversion used for normal query
|
||||
parameters (which is implemented by ``get_db_prep_value``).
|
||||
|
||||
Preprocessing values before saving
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -453,7 +475,13 @@ correct value.
|
||||
Preparing values for use in database lookups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. method:: get_db_prep_lookup(self, lookup_type, value, connection)
|
||||
As with value conversions, preparing a value for database lookups is a
|
||||
two phase process.
|
||||
|
||||
.. method:: get_prep_lookup(self, lookup_type, value)
|
||||
|
||||
:meth:`get_prep_lookup` performs the first phase of lookup preparation,
|
||||
performing generic data validity checks
|
||||
|
||||
Prepares the ``value`` for passing to the database when used in a lookup (a
|
||||
``WHERE`` constraint in SQL). The ``lookup_type`` will be one of the valid
|
||||
@ -470,34 +498,42 @@ by with handling the lookup types that need special handling for your field
|
||||
and pass the rest to the :meth:`get_db_prep_lookup` method of the parent class.
|
||||
|
||||
If you needed to implement ``get_db_prep_save()``, you will usually need to
|
||||
implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be
|
||||
implement ``get_prep_lookup()``. If you don't, ``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_db_prep_lookup`` will receive
|
||||
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_db_prep_value()``, or at least factor out some common
|
||||
time, you can reuse ``get_prep_value()``, or at least factor out some common
|
||||
pieces.
|
||||
|
||||
For example, the following code implements ``get_db_prep_lookup`` to limit the
|
||||
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_db_prep_lookup(self, lookup_type, value):
|
||||
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_db_prep_value(value)]
|
||||
return [self.get_prep_value(value)]
|
||||
elif lookup_type == 'in':
|
||||
return [self.get_db_prep_value(v) for v in value]
|
||||
return [self.get_prep_value(v) for v in value]
|
||||
else:
|
||||
raise TypeError('Lookup type %r not supported.' % lookup_type)
|
||||
|
||||
.. method:: get_db_prep_lookup(self, lookup_type, value, connection, prepared=False)
|
||||
|
||||
Performs any database-specific data conversions required by a lookup.
|
||||
As with :meth:`get_db_prep_value`, the specific connection that will
|
||||
be used for the query is passed as the ``connection`` parameter.
|
||||
The ``prepared`` argument describes whether the value has already been
|
||||
prepared with :meth:`get_prep_lookup`.
|
||||
|
||||
Specifying the form field for a model field
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -35,6 +35,11 @@ their deprecation, as per the :ref:`Django deprecation policy
|
||||
(i.e., ``sqlite3`` instead of ``django.db.backends.sqlite3``) will be
|
||||
removed.
|
||||
|
||||
* The ``get_db_prep_save``, ``get_db_prep_value`` and
|
||||
``get_db_prep_lookup`` methods on Field were modified in 1.2 to support
|
||||
multiple databases. In 1.4, the support functions that allow methods
|
||||
with the old prototype to continue working will be removed.
|
||||
|
||||
* 2.0
|
||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
||||
|
@ -141,6 +141,74 @@ appear in ``__dict__`` for a model instance. If your code relies on
|
||||
iterating over __dict__ to obtain a list of fields, you must now
|
||||
filter out ``_state`` attribute of out ``__dict__``.
|
||||
|
||||
``get_db_prep_*()`` methods on Field
|
||||
------------------------------------
|
||||
|
||||
Prior to v1.2, a custom field had the option of defining several
|
||||
functions to support conversion of Python values into
|
||||
database-compatible values. A custom field might look something like::
|
||||
|
||||
class CustomModelField(models.Field):
|
||||
# ...
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
# ...
|
||||
|
||||
def get_db_prep_value(self, value):
|
||||
# ...
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
# ...
|
||||
|
||||
In 1.2, these three methods have undergone a change in prototype, and
|
||||
two extra methods have been introduced::
|
||||
|
||||
class CustomModelField(models.Field):
|
||||
# ...
|
||||
|
||||
def get_prep_value(self, value):
|
||||
# ...
|
||||
|
||||
def get_prep_lookup(self, lookup_type, value):
|
||||
# ...
|
||||
|
||||
def get_db_prep_save(self, value, connection):
|
||||
# ...
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
# ...
|
||||
|
||||
def get_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
||||
# ...
|
||||
|
||||
These changes are required to support multiple databases -
|
||||
``get_db_prep_*`` can no longer make any assumptions regarding the
|
||||
database for which it is preparing. The ``connection`` argument now
|
||||
provides the preparation methods with the specific connection for
|
||||
which the value is being prepared.
|
||||
|
||||
The two new methods exist to differentiate general data preparation
|
||||
requirements, and requirements that are database-specific. The
|
||||
``prepared`` argument is used to indicate to the database preparation
|
||||
methods whether generic value preparation has been performed. If
|
||||
an unprepared (i.e., ``prepared=False``) value is provided to the
|
||||
``get_db_prep_*()`` calls, they should invoke the corresponding
|
||||
``get_prep_*()`` calls to perform generic data preparation.
|
||||
|
||||
Conversion functions has been provided which will transparently
|
||||
convert functions adhering to the old prototype into functions
|
||||
compatible with the new prototype. However, this conversion function
|
||||
will be removed in Django 1.4, so you should upgrade your Field
|
||||
definitions to use the new prototype.
|
||||
|
||||
If your ``get_db_prep_*()`` methods made no use of the database
|
||||
connection, you should be able to upgrade by renaming
|
||||
``get_db_prep_value()`` to ``get_prep_value()`` and
|
||||
``get_db_prep_lookup()`` to ``get_prep_lookup()`. If you require
|
||||
database specific conversions, then you will need to provide an
|
||||
implementation ``get_db_prep_*`` that uses the ``connection``
|
||||
argument to resolve database-specific values.
|
||||
|
||||
.. _deprecated-features-1.2:
|
||||
|
||||
Features deprecated in 1.2
|
||||
|
Loading…
x
Reference in New Issue
Block a user