1
0
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:
Alex Gaynor 2009-12-03 06:25:45 +00:00
parent 5a5e082161
commit 0ca0ed0453
14 changed files with 279 additions and 156 deletions

4
TODO
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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."

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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