1
0
mirror of https://github.com/django/django.git synced 2025-07-05 02:09:13 +00:00

[soc2009/multidb] Added connection parameter to the get_db_prep_* family of functions. This allows us to generate the lookup and save values for Fields in a backend specific manner.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11264 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Alex Gaynor 2009-07-17 15:57:43 +00:00
parent 6060ee7c77
commit 8395544774
10 changed files with 74 additions and 50 deletions

View File

@ -18,6 +18,7 @@ from django.db.models.options import Options
from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS
from django.db.models import signals from django.db.models import signals
from django.db.models.loading import register_models, get_model from django.db.models.loading import register_models, get_model
from django.db.utils import call_with_connection
from django.utils.functional import curry from django.utils.functional import curry
from django.utils.encoding import smart_str, force_unicode, smart_unicode from django.utils.encoding import smart_str, force_unicode, smart_unicode
from django.conf import settings from django.conf import settings
@ -483,9 +484,11 @@ class Model(object):
if not pk_set: if not pk_set:
if force_update: if force_update:
raise ValueError("Cannot force an update in save() with no primary key.") raise ValueError("Cannot force an update in save() with no primary key.")
values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)] values = [(f, call_with_connection(f.get_db_prep_save, raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
for f in meta.local_fields if not isinstance(f, AutoField)]
else: else:
values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields] values = [(f, call_with_connection(f.get_db_prep_save, raw and getattr(self, f.attname) or f.pre_save(self, True), connection=connection))
for f in meta.local_fields]
if meta.order_with_respect_to: if meta.order_with_respect_to:
field = meta.order_with_respect_to field = meta.order_with_respect_to

View File

@ -11,6 +11,7 @@ except ImportError:
from django.db import connection from django.db import connection
from django.db.models import signals from django.db.models import signals
from django.db.models.query_utils import QueryWrapper from django.db.models.query_utils import QueryWrapper
from django.db.utils import call_with_connection
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.conf import settings from django.conf import settings
from django import forms from django import forms
@ -178,7 +179,7 @@ class Field(object):
"Returns field's value just before saving." "Returns field's value just before saving."
return getattr(model_instance, self.attname) return getattr(model_instance, self.attname)
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
"""Returns field's value prepared for interacting with the database """Returns field's value prepared for interacting with the database
backend. backend.
@ -187,11 +188,12 @@ class Field(object):
""" """
return value return value
def get_db_prep_save(self, value): def get_db_prep_save(self, value, connection):
"Returns field's value prepared for saving into a database." "Returns field's value prepared for saving into a database."
return self.get_db_prep_value(value) return call_with_connection(self.get_db_prep_value, value,
connection=connection)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value, connection):
"Returns field's value prepared for database lookup." "Returns field's value prepared for database lookup."
if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'): if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'):
# If the value has a relabel_aliases method, it will need to # If the value has a relabel_aliases method, it will need to
@ -208,9 +210,9 @@ class Field(object):
if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', 'search'): if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', 'search'):
return [value] return [value]
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
return [self.get_db_prep_value(value)] return [call_with_connection(self.get_db_prep_value, value, connection=connection)]
elif lookup_type in ('range', 'in'): elif lookup_type in ('range', 'in'):
return [self.get_db_prep_value(v) for v in value] return [call_with_connection(self.get_db_prep_value, v, connection=connection) for v in value]
elif lookup_type in ('contains', 'icontains'): elif lookup_type in ('contains', 'icontains'):
return ["%%%s%%" % connection.ops.prep_for_like_query(value)] return ["%%%s%%" % connection.ops.prep_for_like_query(value)]
elif lookup_type == 'iexact': elif lookup_type == 'iexact':
@ -374,7 +376,7 @@ class AutoField(Field):
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("This value must be an integer.")) _("This value must be an integer."))
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
if value is None: if value is None:
return None return None
return int(value) return int(value)
@ -417,14 +419,15 @@ class BooleanField(Field):
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("This value must be either True or False.")) _("This value must be either True or False."))
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value, connection):
# Special-case handling for filters coming from a web request (e.g. the # 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 # 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 # passing in a list, you might as well make things the right type when
# constructing the list. # constructing the list.
if value in ('1', '0'): if value in ('1', '0'):
value = bool(int(value)) value = bool(int(value))
return super(BooleanField, self).get_db_prep_lookup(lookup_type, value) return call_with_connection(super(BooleanField, self).get_db_prep_lookup,
lookup_type, value, connection=connection)
def validate(self, lookup_type, value): def validate(self, lookup_type, value):
if super(BooleanField, self).validate(lookup_type, value): if super(BooleanField, self).validate(lookup_type, value):
@ -433,7 +436,7 @@ class BooleanField(Field):
value = int(value) value = int(value)
bool(value) bool(value)
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
if value is None: if value is None:
return None return None
return bool(value) return bool(value)
@ -536,14 +539,15 @@ class DateField(Field):
setattr(cls, 'get_previous_by_%s' % self.name, setattr(cls, 'get_previous_by_%s' % self.name,
curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False)) curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False))
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value, connection):
# For "__month", "__day", and "__week_day" lookups, convert the value # For "__month", "__day", and "__week_day" lookups, convert the value
# to an int so the database backend always sees a consistent type. # to an int so the database backend always sees a consistent type.
if lookup_type in ('month', 'day', 'week_day'): if lookup_type in ('month', 'day', 'week_day'):
return [int(value)] return [int(value)]
return super(DateField, self).get_db_prep_lookup(lookup_type, value) return call_with_connection(super(DateField, self).get_db_prep_lookup,
lookup_type, value, connection=connection)
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
# Casts dates into the format expected by the backend # Casts dates into the format expected by the backend
return connection.ops.value_to_db_date(self.to_python(value)) return connection.ops.value_to_db_date(self.to_python(value))
@ -615,7 +619,7 @@ class DateTimeField(DateField):
raise exceptions.ValidationError( raise exceptions.ValidationError(
_('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.')) _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
# Casts dates into the format expected by the backend # Casts dates into the format expected by the backend
return connection.ops.value_to_db_datetime(self.to_python(value)) return connection.ops.value_to_db_datetime(self.to_python(value))
@ -671,11 +675,11 @@ class DecimalField(Field):
from django.db.backends import util from django.db.backends import util
return util.format_number(value, self.max_digits, self.decimal_places) return util.format_number(value, self.max_digits, self.decimal_places)
def get_db_prep_save(self, value): def get_db_prep_save(self, value, connection):
return connection.ops.value_to_db_decimal(self.to_python(value), return connection.ops.value_to_db_decimal(self.to_python(value),
self.max_digits, self.decimal_places) self.max_digits, self.decimal_places)
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
return self.to_python(value) return self.to_python(value)
def formfield(self, **kwargs): def formfield(self, **kwargs):
@ -719,7 +723,7 @@ class FilePathField(Field):
class FloatField(Field): class FloatField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
if value is None: if value is None:
return None return None
return float(value) return float(value)
@ -743,7 +747,7 @@ class FloatField(Field):
class IntegerField(Field): class IntegerField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
if value is None: if value is None:
return None return None
return int(value) return int(value)
@ -796,21 +800,22 @@ class NullBooleanField(Field):
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("This value must be either None, True or False.")) _("This value must be either None, True or False."))
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value, connection):
# Special-case handling for filters coming from a web request (e.g. the # 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 # 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 # passing in a list, you might as well make things the right type when
# constructing the list. # constructing the list.
if value in ('1', '0'): if value in ('1', '0'):
value = bool(int(value)) value = bool(int(value))
return super(NullBooleanField, self).get_db_prep_lookup(lookup_type, value) return call_with_connection(super(NullBooleanField, self).get_db_prep_lookup,
lookup_type, value, connection=connection)
def validate(self, lookup_type, value): def validate(self, lookup_type, value):
if value in ('1', '0'): if value in ('1', '0'):
value = int(value) value = int(value)
bool(value) bool(value)
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
if value is None: if value is None:
return None return None
return bool(value) return bool(value)
@ -926,7 +931,7 @@ class TimeField(Field):
else: else:
return super(TimeField, self).pre_save(model_instance, add) return super(TimeField, self).pre_save(model_instance, add)
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
# Casts times into the format expected by the backend # Casts times into the format expected by the backend
return connection.ops.value_to_db_time(self.to_python(value)) return connection.ops.value_to_db_time(self.to_python(value))

View File

@ -10,6 +10,7 @@ from django.core.files.images import ImageFile, get_image_dimensions
from django.core.files.uploadedfile import UploadedFile from django.core.files.uploadedfile import UploadedFile
from django.utils.functional import curry from django.utils.functional import curry
from django.db.models import signals from django.db.models import signals
from django.db.utils import call_with_connection
from django.utils.encoding import force_unicode, smart_str from django.utils.encoding import force_unicode, smart_str
from django.utils.translation import ugettext_lazy, ugettext as _ from django.utils.translation import ugettext_lazy, ugettext as _
from django import forms from django import forms
@ -232,12 +233,13 @@ class FileField(Field):
def get_internal_type(self): def get_internal_type(self):
return "FileField" return "FileField"
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value, connection):
if hasattr(value, 'name'): if hasattr(value, 'name'):
value = value.name value = value.name
return super(FileField, self).get_db_prep_lookup(lookup_type, value) return call_with_connection(super(FileField, self).get_db_prep_lookup,
lookup_type, value, connection=connection)
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
"Returns field's value prepared for saving into a database." "Returns field's value prepared for saving into a database."
# Need to convert File objects provided via a form to unicode for database insertion # Need to convert File objects provided via a form to unicode for database insertion
if value is None: if value is None:

View File

@ -117,7 +117,7 @@ class RelatedField(object):
if not cls._meta.abstract: if not cls._meta.abstract:
self.contribute_to_related_class(other, self.related) self.contribute_to_related_class(other, self.related)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value, connection):
# If we are doing a lookup on a Related Field, we must be # If we are doing a lookup on a Related Field, we must be
# comparing object instances. The value should be the PK of value, # comparing object instances. The value should be the PK of value,
# not value itself. # not value itself.
@ -137,7 +137,8 @@ class RelatedField(object):
if field: if field:
if lookup_type in ('range', 'in'): if lookup_type in ('range', 'in'):
v = [v] v = [v]
v = field.get_db_prep_lookup(lookup_type, v) v = call_with_connection(field.get_db_prep_lookup,
lookup_type, v, connection=connection)
if isinstance(v, list): if isinstance(v, list):
v = v[0] v = v[0]
return v return v
@ -720,11 +721,12 @@ class ForeignKey(RelatedField, Field):
return getattr(field_default, self.rel.get_related_field().attname) return getattr(field_default, self.rel.get_related_field().attname)
return field_default return field_default
def get_db_prep_save(self, value): def get_db_prep_save(self, value, connection):
if value == '' or value == None: if value == '' or value == None:
return None return None
else: else:
return self.rel.get_related_field().get_db_prep_save(value) return call_with_connection(self.rel.get_related_field().get_db_prep_save,
value, connection=connection)
def value_to_string(self, obj): def value_to_string(self, obj):
if not obj: if not obj:

View File

@ -18,7 +18,7 @@ class RelatedObject(object):
self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name) self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name)
self.var_name = self.opts.object_name.lower() self.var_name = self.opts.object_name.lower()
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value, connection):
# Defer to the actual field definition for db prep # 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)

View File

@ -8,6 +8,7 @@ from django.db.models.sql.datastructures import Date
from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.query import Query from django.db.models.sql.query import Query
from django.db.models.sql.where import AND, Constraint from django.db.models.sql.where import AND, Constraint
from django.db.utils import call_with_connection
__all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery', __all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'DateQuery',
'AggregateQuery'] 'AggregateQuery']
@ -243,7 +244,8 @@ class UpdateQuery(Query):
if hasattr(val, 'prepare_database_save'): if hasattr(val, 'prepare_database_save'):
val = val.prepare_database_save(field) val = val.prepare_database_save(field)
else: else:
val = field.get_db_prep_save(val) val = call_with_connection(field.get_db_prep_save,
val, connection=self.connection)
# Getting the placeholder for the field. # Getting the placeholder for the field.
if hasattr(field, 'get_placeholder'): if hasattr(field, 'get_placeholder'):

View File

@ -143,7 +143,8 @@ class WhereNode(tree.Node):
except EmptyShortCircuit: except EmptyShortCircuit:
raise EmptyResultSet raise EmptyResultSet
else: else:
params = Field().get_db_prep_lookup(lookup_type, params_or_value) params = call_with_connection(Field().get_db_prep_lookup,
lookup_type, params_or_value, connection=connection)
if isinstance(lvalue, tuple): if isinstance(lvalue, tuple):
# A direct database column lookup. # A direct database column lookup.
field_sql = self.sql_for_columns(lvalue, qn, connection) field_sql = self.sql_for_columns(lvalue, qn, connection)
@ -266,13 +267,15 @@ class Constraint(object):
from django.db.models.base import ObjectDoesNotExist from django.db.models.base import ObjectDoesNotExist
try: try:
if self.field: if self.field:
params = self.field.get_db_prep_lookup(lookup_type, value) params = call_with_connection(self.field.get_db_prep_lookup,
lookup_type, value, connection=connection)
db_type = call_with_connection(self.field.db_type, connection=connection) db_type = call_with_connection(self.field.db_type, connection=connection)
else: else:
# This branch is used at times when we add a comparison to NULL # 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 # (we don't really want to waste time looking up the associated
# field object at the calling location). # field object at the calling location).
params = Field().get_db_prep_lookup(lookup_type, value) params = call_with_connection(Field().get_db_prep_lookup,
lookup_type, value, connection=connection)
db_type = None db_type = None
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise EmptyShortCircuit raise EmptyShortCircuit

View File

@ -3,6 +3,7 @@ Helper functions for creating Form classes from Django models
and database field objects. and database field objects.
""" """
from django.db.utils import call_with_connection
from django.utils.encoding import smart_unicode, force_unicode from django.utils.encoding import smart_unicode, force_unicode
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.text import get_text_list, capfirst from django.utils.text import get_text_list, capfirst
@ -474,7 +475,8 @@ class BaseModelFormSet(BaseFormSet):
pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name) pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
pk = self.data[pk_key] pk = self.data[pk_key]
pk_field = self.model._meta.pk pk_field = self.model._meta.pk
pk = pk_field.get_db_prep_lookup('exact', pk) pk = call_with_connection(pk_field.get_db_prep_lookup, 'exact', pk,
connection=self.get_queryset().query.connection)
if isinstance(pk, list): if isinstance(pk, list):
pk = pk[0] pk = pk[0]
kwargs['instance'] = self._existing_object(pk) kwargs['instance'] = self._existing_object(pk)

View File

@ -399,24 +399,27 @@ mentioned earlier. Otherwise :meth:`to_python` won't be called automatically.
Converting Python objects to database values Converting Python objects to database values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. method:: get_db_prep_value(self, value) .. method:: get_db_prep_value(self, value, connection)
This is the reverse of :meth:`to_python` when working with the database backends 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 (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 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 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. 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.
For example:: For example::
class HandField(models.Field): class HandField(models.Field):
# ... # ...
def get_db_prep_value(self, value): def get_db_prep_value(self, value, connection):
return ''.join([''.join(l) for l in (value.north, return ''.join([''.join(l) for l in (value.north,
value.east, value.south, value.west)]) value.east, value.south, value.west)])
.. method:: get_db_prep_save(self, value) .. method:: get_db_prep_save(self, value, connection)
Same as the above, but called when the Field value must be *saved* to the 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 database. As the default implementation just calls ``get_db_prep_value``, you
@ -450,7 +453,7 @@ correct value.
Preparing values for use in database lookups Preparing values for use in database lookups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. method:: get_db_prep_lookup(self, lookup_type, value) .. method:: get_db_prep_lookup(self, lookup_type, value, connection)
Prepares the ``value`` for passing to the database when used in a lookup (a 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 ``WHERE`` constraint in SQL). The ``lookup_type`` will be one of the valid

View File

@ -44,8 +44,9 @@ class DecimalFieldTests(django.test.TestCase):
self.assertEqual(f._format(None), None) self.assertEqual(f._format(None), None)
def test_get_db_prep_lookup(self): def test_get_db_prep_lookup(self):
from django.db import connection
f = models.DecimalField(max_digits=5, decimal_places=1) f = models.DecimalField(max_digits=5, decimal_places=1)
self.assertEqual(f.get_db_prep_lookup('exact', None), [None]) self.assertEqual(f.get_db_prep_lookup('exact', None, connection), [None])
def test_filter_with_strings(self): def test_filter_with_strings(self):
""" """
@ -98,13 +99,14 @@ class DateTimeFieldTests(unittest.TestCase):
class BooleanFieldTests(unittest.TestCase): class BooleanFieldTests(unittest.TestCase):
def _test_get_db_prep_lookup(self, f): def _test_get_db_prep_lookup(self, f):
self.assertEqual(f.get_db_prep_lookup('exact', True), [True]) from django.db import connection
self.assertEqual(f.get_db_prep_lookup('exact', '1'), [True]) self.assertEqual(f.get_db_prep_lookup('exact', True, connection), [True])
self.assertEqual(f.get_db_prep_lookup('exact', 1), [True]) self.assertEqual(f.get_db_prep_lookup('exact', '1', connection), [True])
self.assertEqual(f.get_db_prep_lookup('exact', False), [False]) self.assertEqual(f.get_db_prep_lookup('exact', 1, connection), [True])
self.assertEqual(f.get_db_prep_lookup('exact', '0'), [False]) self.assertEqual(f.get_db_prep_lookup('exact', False, connection), [False])
self.assertEqual(f.get_db_prep_lookup('exact', 0), [False]) self.assertEqual(f.get_db_prep_lookup('exact', '0', connection), [False])
self.assertEqual(f.get_db_prep_lookup('exact', None), [None]) self.assertEqual(f.get_db_prep_lookup('exact', 0, connection), [False])
self.assertEqual(f.get_db_prep_lookup('exact', None, connection), [None])
def test_booleanfield_get_db_prep_lookup(self): def test_booleanfield_get_db_prep_lookup(self):
self._test_get_db_prep_lookup(models.BooleanField()) self._test_get_db_prep_lookup(models.BooleanField())