Author: Alex
Date: 2009-12-03 00:25:45 -0600 (Thu, 03 Dec 2009)
New Revision: 11786
Modified:
django/branches/soc2009/multidb/TODO
django/branches/soc2009/multidb/django/db/models/fields/__init__.py
django/branches/soc2009/multidb/django/db/models/fields/files.py
django/branches/soc2009/multidb/django/db/models/fields/related.py
django/branches/soc2009/multidb/django/db/models/fields/subclassing.py
django/branches/soc2009/multidb/django/db/models/query.py
django/branches/soc2009/multidb/django/db/models/related.py
django/branches/soc2009/multidb/django/db/models/sql/compiler.py
django/branches/soc2009/multidb/django/db/models/sql/expressions.py
django/branches/soc2009/multidb/django/db/models/sql/query.py
django/branches/soc2009/multidb/django/db/models/sql/where.py
django/branches/soc2009/multidb/docs/howto/custom-model-fields.txt
django/branches/soc2009/multidb/docs/internals/deprecation.txt
django/branches/soc2009/multidb/docs/releases/1.2.txt
Log:
[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.
Modified: django/branches/soc2009/multidb/TODO
===================================================================
--- django/branches/soc2009/multidb/TODO 2009-12-02 00:30:00 UTC (rev
11785)
+++ django/branches/soc2009/multidb/TODO 2009-12-03 06:25:45 UTC (rev
11786)
@@ -7,10 +7,6 @@
* 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
~~~~~~~~~~~~~~~~~
Modified: django/branches/soc2009/multidb/django/db/models/fields/__init__.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/fields/__init__.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/fields/__init__.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -180,21 +180,56 @@
"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 @@
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 @@
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 @@
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 @@
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,32 +529,22 @@
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))
+ if not prepared:
+ value = self.get_prep_value(value)
+ return connection.ops.value_to_db_date(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)
-
def value_to_string(self, obj):
val = self._get_val_from_obj(obj)
if val is None:
@@ -619,9 +599,14 @@
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 @@
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 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 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 @@
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 @@
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)
Modified: django/branches/soc2009/multidb/django/db/models/fields/files.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/fields/files.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/fields/files.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -232,13 +232,12 @@
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:
Modified: django/branches/soc2009/multidb/django/db/models/fields/related.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/fields/related.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/fields/related.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -120,7 +120,7 @@
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 @@
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
Modified: django/branches/soc2009/multidb/django/db/models/fields/subclassing.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/fields/subclassing.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/fields/subclassing.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -11,25 +11,53 @@
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 @@
"""
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):
Modified: django/branches/soc2009/multidb/django/db/models/query.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/query.py 2009-12-02
00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/query.py 2009-12-03
06:25:45 UTC (rev 11786)
@@ -739,6 +739,9 @@
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 @@
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 @@
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,8 +882,8 @@
(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):
if self.flat and len(self._fields) == 1:
Modified: django/branches/soc2009/multidb/django/db/models/related.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/related.py 2009-12-02
00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/related.py 2009-12-03
06:25:45 UTC (rev 11786)
@@ -18,9 +18,10 @@
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."
Modified: django/branches/soc2009/multidb/django/db/models/sql/compiler.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/sql/compiler.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/sql/compiler.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -837,7 +837,7 @@
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
Modified: django/branches/soc2009/multidb/django/db/models/sql/expressions.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/sql/expressions.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/sql/expressions.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -11,6 +11,9 @@
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)
Modified: django/branches/soc2009/multidb/django/db/models/sql/query.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/sql/query.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/sql/query.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -141,6 +141,9 @@
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")
Modified: django/branches/soc2009/multidb/django/db/models/sql/where.py
===================================================================
--- django/branches/soc2009/multidb/django/db/models/sql/where.py
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/django/db/models/sql/where.py
2009-12-03 06:25:45 UTC (rev 11786)
@@ -62,8 +62,8 @@
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 @@
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 @@
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 @@
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 @@
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)
Modified: django/branches/soc2009/multidb/docs/howto/custom-model-fields.txt
===================================================================
--- django/branches/soc2009/multidb/docs/howto/custom-model-fields.txt
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/docs/howto/custom-model-fields.txt
2009-12-03 06:25:45 UTC (rev 11786)
@@ -396,36 +396,58 @@
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,8 +475,14 @@
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
Django filter lookups: ``exact``, ``iexact``, ``contains``, ``icontains``,
@@ -470,34 +498,42 @@
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Modified: django/branches/soc2009/multidb/docs/internals/deprecation.txt
===================================================================
--- django/branches/soc2009/multidb/docs/internals/deprecation.txt
2009-12-02 00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/docs/internals/deprecation.txt
2009-12-03 06:25:45 UTC (rev 11786)
@@ -35,6 +35,11 @@
(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
Modified: django/branches/soc2009/multidb/docs/releases/1.2.txt
===================================================================
--- django/branches/soc2009/multidb/docs/releases/1.2.txt 2009-12-02
00:30:00 UTC (rev 11785)
+++ django/branches/soc2009/multidb/docs/releases/1.2.txt 2009-12-03
06:25:45 UTC (rev 11786)
@@ -141,6 +141,74 @@
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
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en.