Fixed #7560 -- Moved a lot of the value conversion preparation for
loading/saving interactions with the databases into django.db.backend. This helps external db backend writers and removes a bunch of database-specific if-tests in django.db.models.fields. Great work from Leo Soto. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8131 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
7bc728c826
commit
b3b71a0922
|
@ -5,6 +5,9 @@ except ImportError:
|
||||||
# Import copy of _thread_local.py from Python 2.4
|
# Import copy of _thread_local.py from Python 2.4
|
||||||
from django.utils._threading_local import local
|
from django.utils._threading_local import local
|
||||||
|
|
||||||
|
from django.db.backends import util
|
||||||
|
from django.utils import datetime_safe
|
||||||
|
|
||||||
class BaseDatabaseWrapper(local):
|
class BaseDatabaseWrapper(local):
|
||||||
"""
|
"""
|
||||||
Represents a database connection.
|
Represents a database connection.
|
||||||
|
@ -36,12 +39,13 @@ class BaseDatabaseWrapper(local):
|
||||||
return cursor
|
return cursor
|
||||||
|
|
||||||
def make_debug_cursor(self, cursor):
|
def make_debug_cursor(self, cursor):
|
||||||
from django.db.backends import util
|
|
||||||
return util.CursorDebugWrapper(cursor, self)
|
return util.CursorDebugWrapper(cursor, self)
|
||||||
|
|
||||||
class BaseDatabaseFeatures(object):
|
class BaseDatabaseFeatures(object):
|
||||||
allows_group_by_ordinal = True
|
allows_group_by_ordinal = True
|
||||||
inline_fk_references = True
|
inline_fk_references = True
|
||||||
|
# True if django.db.backend.utils.typecast_timestamp is used on values
|
||||||
|
# returned from dates() calls.
|
||||||
needs_datetime_string_cast = True
|
needs_datetime_string_cast = True
|
||||||
supports_constraints = True
|
supports_constraints = True
|
||||||
supports_tablespaces = False
|
supports_tablespaces = False
|
||||||
|
@ -49,10 +53,7 @@ class BaseDatabaseFeatures(object):
|
||||||
uses_custom_query_class = False
|
uses_custom_query_class = False
|
||||||
empty_fetchmany_value = []
|
empty_fetchmany_value = []
|
||||||
update_can_self_select = True
|
update_can_self_select = True
|
||||||
supports_usecs = True
|
|
||||||
time_field_needs_date = False
|
|
||||||
interprets_empty_strings_as_nulls = False
|
interprets_empty_strings_as_nulls = False
|
||||||
date_field_supports_time_value = True
|
|
||||||
can_use_chunked_reads = True
|
can_use_chunked_reads = True
|
||||||
|
|
||||||
class BaseDatabaseOperations(object):
|
class BaseDatabaseOperations(object):
|
||||||
|
@ -263,3 +264,64 @@ class BaseDatabaseOperations(object):
|
||||||
"""Prepares a value for use in a LIKE query."""
|
"""Prepares a value for use in a LIKE query."""
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
|
return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
|
||||||
|
|
||||||
|
def value_to_db_date(self, value):
|
||||||
|
"""
|
||||||
|
Transform a date value to an object compatible with what is expected
|
||||||
|
by the backend driver for date columns.
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return datetime_safe.new_date(value).strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
def value_to_db_datetime(self, value):
|
||||||
|
"""
|
||||||
|
Transform a datetime value to an object compatible with what is expected
|
||||||
|
by the backend driver for date columns.
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return unicode(value)
|
||||||
|
|
||||||
|
def value_to_db_time(self, value):
|
||||||
|
"""
|
||||||
|
Transform a datetime value to an object compatible with what is expected
|
||||||
|
by the backend driver for date columns.
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return unicode(value)
|
||||||
|
|
||||||
|
def value_to_db_decimal(self, value, max_digits, decimal_places):
|
||||||
|
"""
|
||||||
|
Transform a decimal.Decimal value to an object compatible with what is
|
||||||
|
expected by the backend driver for decimal (numeric) columns.
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return util.format_number(value, max_digits, decimal_places)
|
||||||
|
|
||||||
|
def year_lookup_bounds(self, value):
|
||||||
|
"""
|
||||||
|
Returns a two-elements list with the lower and upper bound to be used
|
||||||
|
with a BETWEEN operator to query a field value using a year lookup
|
||||||
|
|
||||||
|
`value` is an int, containing the looked-up year.
|
||||||
|
"""
|
||||||
|
first = '%s-01-01 00:00:00'
|
||||||
|
second = '%s-12-31 23:59:59.999999'
|
||||||
|
return [first % value, second % value]
|
||||||
|
|
||||||
|
def year_lookup_bounds_for_date_field(self, value):
|
||||||
|
"""
|
||||||
|
Returns a two-elements list with the lower and upper bound to be used
|
||||||
|
with a BETWEEN operator to query a DateField value using a year lookup
|
||||||
|
|
||||||
|
`value` is an int, containing the looked-up year.
|
||||||
|
|
||||||
|
By default, it just calls `self.year_lookup_bounds`. Some backends need
|
||||||
|
this hook because on their DB date fields can't be compared to values
|
||||||
|
which include a time part.
|
||||||
|
"""
|
||||||
|
return self.year_lookup_bounds(value)
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
inline_fk_references = False
|
inline_fk_references = False
|
||||||
empty_fetchmany_value = ()
|
empty_fetchmany_value = ()
|
||||||
update_can_self_select = False
|
update_can_self_select = False
|
||||||
supports_usecs = False
|
|
||||||
|
|
||||||
class DatabaseOperations(BaseDatabaseOperations):
|
class DatabaseOperations(BaseDatabaseOperations):
|
||||||
def date_extract_sql(self, lookup_type, field_name):
|
def date_extract_sql(self, lookup_type, field_name):
|
||||||
|
@ -124,6 +123,24 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def value_to_db_datetime(self, value):
|
||||||
|
# MySQL doesn't support microseconds
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return unicode(value.replace(microsecond=0))
|
||||||
|
|
||||||
|
def value_to_db_time(self, value):
|
||||||
|
# MySQL doesn't support microseconds
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return unicode(value.replace(microsecond=0))
|
||||||
|
|
||||||
|
def year_lookup_bounds(self, value):
|
||||||
|
# Again, no microseconds
|
||||||
|
first = '%s-01-01 00:00:00'
|
||||||
|
second = '%s-12-31 23:59:59.99'
|
||||||
|
return [first % value, second % value]
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
features = DatabaseFeatures()
|
features = DatabaseFeatures()
|
||||||
ops = DatabaseOperations()
|
ops = DatabaseOperations()
|
||||||
|
|
|
@ -5,6 +5,8 @@ Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
|
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
|
||||||
from django.db.backends.oracle import query
|
from django.db.backends.oracle import query
|
||||||
|
@ -28,9 +30,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
supports_tablespaces = True
|
supports_tablespaces = True
|
||||||
uses_case_insensitive_names = True
|
uses_case_insensitive_names = True
|
||||||
uses_custom_query_class = True
|
uses_custom_query_class = True
|
||||||
time_field_needs_date = True
|
|
||||||
interprets_empty_strings_as_nulls = True
|
interprets_empty_strings_as_nulls = True
|
||||||
date_field_supports_time_value = False
|
|
||||||
|
|
||||||
class DatabaseOperations(BaseDatabaseOperations):
|
class DatabaseOperations(BaseDatabaseOperations):
|
||||||
def autoinc_sql(self, table, column):
|
def autoinc_sql(self, table, column):
|
||||||
|
@ -180,6 +180,21 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
def tablespace_sql(self, tablespace, inline=False):
|
def tablespace_sql(self, tablespace, inline=False):
|
||||||
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace))
|
return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace))
|
||||||
|
|
||||||
|
def value_to_db_time(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if isinstance(value, basestring):
|
||||||
|
return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
|
||||||
|
return datetime.datetime(1900, 1, 1, value.hour, value.minute,
|
||||||
|
value.second, value.microsecond)
|
||||||
|
|
||||||
|
def year_lookup_bounds_for_date_field(self, value):
|
||||||
|
first = '%s-01-01'
|
||||||
|
second = '%s-12-31'
|
||||||
|
return [first % value, second % value]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
features = DatabaseFeatures()
|
features = DatabaseFeatures()
|
||||||
ops = DatabaseOperations()
|
ops = DatabaseOperations()
|
||||||
|
|
|
@ -84,6 +84,12 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
# sql_flush() implementations). Just return SQL at this point
|
# sql_flush() implementations). Just return SQL at this point
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
|
def year_lookup_bounds(self, value):
|
||||||
|
first = '%s-01-01'
|
||||||
|
second = '%s-12-31 23:59:59.999999'
|
||||||
|
return [first % value, second % value]
|
||||||
|
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
features = DatabaseFeatures()
|
features = DatabaseFeatures()
|
||||||
ops = DatabaseOperations()
|
ops = DatabaseOperations()
|
||||||
|
@ -159,7 +165,7 @@ def _sqlite_extract(lookup_type, dt):
|
||||||
dt = util.typecast_timestamp(dt)
|
dt = util.typecast_timestamp(dt)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return None
|
return None
|
||||||
return str(getattr(dt, lookup_type))
|
return getattr(dt, lookup_type)
|
||||||
|
|
||||||
def _sqlite_date_trunc(lookup_type, dt):
|
def _sqlite_date_trunc(lookup_type, dt):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -117,3 +117,10 @@ def truncate_name(name, length=None):
|
||||||
hash = md5.md5(name).hexdigest()[:4]
|
hash = md5.md5(name).hexdigest()[:4]
|
||||||
|
|
||||||
return '%s%s' % (name[:length-4], hash)
|
return '%s%s' % (name[:length-4], hash)
|
||||||
|
|
||||||
|
def format_number(value, max_digits, decimal_places):
|
||||||
|
"""
|
||||||
|
Formats a number into a string with the requisite number of digits and
|
||||||
|
decimal places.
|
||||||
|
"""
|
||||||
|
return u"%.*f" % (decimal_places, value)
|
||||||
|
|
|
@ -218,19 +218,30 @@ 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):
|
||||||
|
"""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```
|
||||||
|
"""
|
||||||
|
return value
|
||||||
|
|
||||||
def get_db_prep_save(self, value):
|
def get_db_prep_save(self, value):
|
||||||
"Returns field's value prepared for saving into a database."
|
"Returns field's value prepared for saving into a database."
|
||||||
return value
|
return self.get_db_prep_value(value)
|
||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
def get_db_prep_lookup(self, lookup_type, value):
|
||||||
"Returns field's value prepared for database lookup."
|
"Returns field's value prepared for database lookup."
|
||||||
if hasattr(value, 'as_sql'):
|
if hasattr(value, 'as_sql'):
|
||||||
sql, params = value.as_sql()
|
sql, params = value.as_sql()
|
||||||
return QueryWrapper(('(%s)' % sql), params)
|
return QueryWrapper(('(%s)' % sql), params)
|
||||||
if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'):
|
if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'):
|
||||||
return [value]
|
return [value]
|
||||||
|
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
|
||||||
|
return [self.get_db_prep_value(value)]
|
||||||
elif lookup_type in ('range', 'in'):
|
elif lookup_type in ('range', 'in'):
|
||||||
return value
|
return [self.get_db_prep_value(v) 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':
|
||||||
|
@ -246,19 +257,12 @@ class Field(object):
|
||||||
value = int(value)
|
value = int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("The __year lookup type requires an integer argument")
|
raise ValueError("The __year lookup type requires an integer argument")
|
||||||
if settings.DATABASE_ENGINE == 'sqlite3':
|
|
||||||
first = '%s-01-01'
|
if self.get_internal_type() == 'DateField':
|
||||||
second = '%s-12-31 23:59:59.999999'
|
return connection.ops.year_lookup_bounds_for_date_field(value)
|
||||||
elif not connection.features.date_field_supports_time_value and self.get_internal_type() == 'DateField':
|
|
||||||
first = '%s-01-01'
|
|
||||||
second = '%s-12-31'
|
|
||||||
elif not connection.features.supports_usecs:
|
|
||||||
first = '%s-01-01 00:00:00'
|
|
||||||
second = '%s-12-31 23:59:59.99'
|
|
||||||
else:
|
else:
|
||||||
first = '%s-01-01 00:00:00'
|
return connection.ops.year_lookup_bounds(value)
|
||||||
second = '%s-12-31 23:59:59.999999'
|
|
||||||
return [first % value, second % value]
|
|
||||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
||||||
|
|
||||||
def has_default(self):
|
def has_default(self):
|
||||||
|
@ -457,6 +461,11 @@ class AutoField(Field):
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
raise validators.ValidationError, _("This value must be an integer.")
|
raise validators.ValidationError, _("This value must be an integer.")
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return int(value)
|
||||||
|
|
||||||
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
|
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
|
||||||
if not rel:
|
if not rel:
|
||||||
return [] # Don't add a FormField unless it's in a related context.
|
return [] # Don't add a FormField unless it's in a related context.
|
||||||
|
@ -498,6 +507,11 @@ class BooleanField(Field):
|
||||||
if value in ('f', 'False', '0'): return False
|
if value in ('f', 'False', '0'): return False
|
||||||
raise validators.ValidationError, _("This value must be either True or False.")
|
raise validators.ValidationError, _("This value must be either True or False.")
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return bool(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.CheckboxField]
|
return [oldforms.CheckboxField]
|
||||||
|
|
||||||
|
@ -559,15 +573,6 @@ class DateField(Field):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
|
raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
|
||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
|
||||||
if lookup_type in ('range', 'in'):
|
|
||||||
value = [smart_unicode(v) for v in value]
|
|
||||||
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'):
|
|
||||||
value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
|
|
||||||
else:
|
|
||||||
value = smart_unicode(value)
|
|
||||||
return Field.get_db_prep_lookup(self, lookup_type, value)
|
|
||||||
|
|
||||||
def pre_save(self, model_instance, add):
|
def pre_save(self, model_instance, add):
|
||||||
if self.auto_now or (self.auto_now_add and add):
|
if self.auto_now or (self.auto_now_add and add):
|
||||||
value = datetime.datetime.now()
|
value = datetime.datetime.now()
|
||||||
|
@ -591,16 +596,9 @@ class DateField(Field):
|
||||||
else:
|
else:
|
||||||
return self.editable or self.auto_now or self.auto_now_add
|
return self.editable or self.auto_now or self.auto_now_add
|
||||||
|
|
||||||
def get_db_prep_save(self, value):
|
def get_db_prep_value(self, value):
|
||||||
# Casts dates into string format for entry into database.
|
# Casts dates into the format expected by the backend
|
||||||
if value is not None:
|
return connection.ops.value_to_db_date(self.to_python(value))
|
||||||
try:
|
|
||||||
value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
|
|
||||||
except AttributeError:
|
|
||||||
# If value is already a string it won't have a strftime method,
|
|
||||||
# so we'll just let it pass through.
|
|
||||||
pass
|
|
||||||
return Field.get_db_prep_save(self, value)
|
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.DateField]
|
return [oldforms.DateField]
|
||||||
|
@ -629,33 +627,37 @@ class DateTimeField(DateField):
|
||||||
return value
|
return value
|
||||||
if isinstance(value, datetime.date):
|
if isinstance(value, datetime.date):
|
||||||
return datetime.datetime(value.year, value.month, value.day)
|
return datetime.datetime(value.year, value.month, value.day)
|
||||||
|
|
||||||
|
# Attempt to parse a datetime:
|
||||||
|
value = smart_str(value)
|
||||||
|
# split usecs, because they are not recognized by strptime.
|
||||||
|
if '.' in value:
|
||||||
|
try:
|
||||||
|
value, usecs = value.split('.')
|
||||||
|
usecs = int(usecs)
|
||||||
|
except ValueError:
|
||||||
|
raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.')
|
||||||
|
else:
|
||||||
|
usecs = 0
|
||||||
|
kwargs = {'microsecond': usecs}
|
||||||
try: # Seconds are optional, so try converting seconds first.
|
try: # Seconds are optional, so try converting seconds first.
|
||||||
return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6])
|
return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6],
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
try: # Try without seconds.
|
try: # Try without seconds.
|
||||||
return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5])
|
return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5],
|
||||||
|
**kwargs)
|
||||||
except ValueError: # Try without hour/minutes/seconds.
|
except ValueError: # Try without hour/minutes/seconds.
|
||||||
try:
|
try:
|
||||||
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
|
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
|
||||||
|
**kwargs)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
|
raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.')
|
||||||
|
|
||||||
def get_db_prep_save(self, value):
|
def get_db_prep_value(self, value):
|
||||||
# Casts dates into string format for entry into database.
|
# Casts dates into the format expected by the backend
|
||||||
if value is not None:
|
return connection.ops.value_to_db_datetime(self.to_python(value))
|
||||||
# MySQL will throw a warning if microseconds are given, because it
|
|
||||||
# doesn't support microseconds.
|
|
||||||
if not connection.features.supports_usecs and hasattr(value, 'microsecond'):
|
|
||||||
value = value.replace(microsecond=0)
|
|
||||||
value = smart_unicode(value)
|
|
||||||
return Field.get_db_prep_save(self, value)
|
|
||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
|
||||||
if lookup_type in ('range', 'in'):
|
|
||||||
value = [smart_unicode(v) for v in value]
|
|
||||||
else:
|
|
||||||
value = smart_unicode(value)
|
|
||||||
return Field.get_db_prep_lookup(self, lookup_type, value)
|
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.DateField, oldforms.TimeField]
|
return [oldforms.DateField, oldforms.TimeField]
|
||||||
|
@ -720,26 +722,18 @@ class DecimalField(Field):
|
||||||
Formats a number into a string with the requisite number of digits and
|
Formats a number into a string with the requisite number of digits and
|
||||||
decimal places.
|
decimal places.
|
||||||
"""
|
"""
|
||||||
num_chars = self.max_digits
|
# Method moved to django.db.backends.util.
|
||||||
# Allow for a decimal point
|
#
|
||||||
if self.decimal_places > 0:
|
# It is preserved because it is used by the oracle backend
|
||||||
num_chars += 1
|
# (django.db.backends.oracle.query), and also for
|
||||||
# Allow for a minus sign
|
# backwards-compatibility with any external code which may have used
|
||||||
if value < 0:
|
# this method.
|
||||||
num_chars += 1
|
from django.db.backends import util
|
||||||
|
return util.format_number(value, self.max_digits, self.decimal_places)
|
||||||
|
|
||||||
return u"%.*f" % (self.decimal_places, value)
|
def get_db_prep_value(self, value):
|
||||||
|
return connection.ops.value_to_db_decimal(value, self.max_digits,
|
||||||
def get_db_prep_save(self, value):
|
self.decimal_places)
|
||||||
value = self._format(value)
|
|
||||||
return super(DecimalField, self).get_db_prep_save(value)
|
|
||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
|
||||||
if lookup_type in ('range', 'in'):
|
|
||||||
value = [self._format(v) for v in value]
|
|
||||||
else:
|
|
||||||
value = self._format(value)
|
|
||||||
return super(DecimalField, self).get_db_prep_lookup(lookup_type, value)
|
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
|
return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
|
||||||
|
@ -778,7 +772,7 @@ class FileField(Field):
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "FileField"
|
return "FileField"
|
||||||
|
|
||||||
def get_db_prep_save(self, value):
|
def get_db_prep_value(self, value):
|
||||||
"Returns field's value prepared for saving into a database."
|
"Returns field's value prepared for saving into a database."
|
||||||
# Need to convert UploadedFile objects provided via a form to unicode for database insertion
|
# Need to convert UploadedFile objects provided via a form to unicode for database insertion
|
||||||
if hasattr(value, 'name'):
|
if hasattr(value, 'name'):
|
||||||
|
@ -919,6 +913,11 @@ class FilePathField(Field):
|
||||||
class FloatField(Field):
|
class FloatField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return float(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.FloatField]
|
return [oldforms.FloatField]
|
||||||
|
|
||||||
|
@ -966,6 +965,11 @@ class ImageField(FileField):
|
||||||
|
|
||||||
class IntegerField(Field):
|
class IntegerField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
|
def get_db_prep_value(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return int(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.IntegerField]
|
return [oldforms.IntegerField]
|
||||||
|
|
||||||
|
@ -1013,6 +1017,11 @@ class NullBooleanField(Field):
|
||||||
if value in ('f', 'False', '0'): return False
|
if value in ('f', 'False', '0'): return False
|
||||||
raise validators.ValidationError, _("This value must be either None, True or False.")
|
raise validators.ValidationError, _("This value must be either None, True or False.")
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return bool(value)
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.NullBooleanField]
|
return [oldforms.NullBooleanField]
|
||||||
|
|
||||||
|
@ -1025,7 +1034,7 @@ class NullBooleanField(Field):
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(NullBooleanField, self).formfield(**defaults)
|
return super(NullBooleanField, self).formfield(**defaults)
|
||||||
|
|
||||||
class PhoneNumberField(IntegerField):
|
class PhoneNumberField(Field):
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.PhoneNumberField]
|
return [oldforms.PhoneNumberField]
|
||||||
|
|
||||||
|
@ -1107,20 +1116,34 @@ class TimeField(Field):
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "TimeField"
|
return "TimeField"
|
||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
def to_python(self, value):
|
||||||
if connection.features.time_field_needs_date:
|
if value is None:
|
||||||
# Oracle requires a date in order to parse.
|
return None
|
||||||
def prep(value):
|
if isinstance(value, datetime.time):
|
||||||
if isinstance(value, datetime.time):
|
return value
|
||||||
value = datetime.datetime.combine(datetime.date(1900, 1, 1), value)
|
|
||||||
return smart_unicode(value)
|
# Attempt to parse a datetime:
|
||||||
|
value = smart_str(value)
|
||||||
|
# split usecs, because they are not recognized by strptime.
|
||||||
|
if '.' in value:
|
||||||
|
try:
|
||||||
|
value, usecs = value.split('.')
|
||||||
|
usecs = int(usecs)
|
||||||
|
except ValueError:
|
||||||
|
raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')
|
||||||
else:
|
else:
|
||||||
prep = smart_unicode
|
usecs = 0
|
||||||
if lookup_type in ('range', 'in'):
|
kwargs = {'microsecond': usecs}
|
||||||
value = [prep(v) for v in value]
|
|
||||||
else:
|
try: # Seconds are optional, so try converting seconds first.
|
||||||
value = prep(value)
|
return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6],
|
||||||
return Field.get_db_prep_lookup(self, lookup_type, value)
|
**kwargs)
|
||||||
|
except ValueError:
|
||||||
|
try: # Try without seconds.
|
||||||
|
return datetime.time(*time.strptime(value, '%H:%M')[3:5],
|
||||||
|
**kwargs)
|
||||||
|
except ValueError:
|
||||||
|
raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')
|
||||||
|
|
||||||
def pre_save(self, model_instance, add):
|
def pre_save(self, model_instance, add):
|
||||||
if self.auto_now or (self.auto_now_add and add):
|
if self.auto_now or (self.auto_now_add and add):
|
||||||
|
@ -1130,23 +1153,9 @@ 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_save(self, value):
|
def get_db_prep_value(self, value):
|
||||||
# Casts dates into string format for entry into database.
|
# Casts times into the format expected by the backend
|
||||||
if value is not None:
|
return connection.ops.value_to_db_time(self.to_python(value))
|
||||||
# MySQL will throw a warning if microseconds are given, because it
|
|
||||||
# doesn't support microseconds.
|
|
||||||
if not connection.features.supports_usecs and hasattr(value, 'microsecond'):
|
|
||||||
value = value.replace(microsecond=0)
|
|
||||||
if connection.features.time_field_needs_date:
|
|
||||||
# cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
|
|
||||||
if isinstance(value, datetime.time):
|
|
||||||
value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
|
|
||||||
value.second, value.microsecond)
|
|
||||||
elif isinstance(value, basestring):
|
|
||||||
value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
|
|
||||||
else:
|
|
||||||
value = smart_unicode(value)
|
|
||||||
return Field.get_db_prep_save(self, value)
|
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
return [oldforms.TimeField]
|
return [oldforms.TimeField]
|
||||||
|
|
|
@ -385,8 +385,8 @@ Python object type we want to store in the model's attribute.
|
||||||
called when it is created, you should be using `The SubfieldBase metaclass`_
|
called when it is created, you should be using `The SubfieldBase metaclass`_
|
||||||
mentioned earlier. Otherwise ``to_python()`` won't be called automatically.
|
mentioned earlier. Otherwise ``to_python()`` won't be called automatically.
|
||||||
|
|
||||||
``get_db_prep_save(self, value)``
|
``get_db_prep_value(self, value)``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This is the reverse of ``to_python()`` when working with the database backends
|
This is the reverse of ``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
|
||||||
|
@ -399,10 +399,20 @@ For example::
|
||||||
class HandField(models.Field):
|
class HandField(models.Field):
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
def get_db_prep_save(self, value):
|
def get_db_prep_value(self, value):
|
||||||
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)])
|
||||||
|
|
||||||
|
``get_db_prep_save(self, 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 need a special
|
||||||
|
conversion when being saved that is not the same as the used for normal query
|
||||||
|
parameters (which is implemented by ``get_db_prep_value``).
|
||||||
|
|
||||||
|
|
||||||
``pre_save(self, model_instance, add)``
|
``pre_save(self, model_instance, add)``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -440,14 +450,21 @@ by with handling the lookup types that need special handling for your field
|
||||||
and pass the rest of the ``get_db_prep_lookup()`` method of the parent class.
|
and pass the rest of the ``get_db_prep_lookup()`` method of the parent class.
|
||||||
|
|
||||||
If you needed to implement ``get_db_prep_save()``, you will usually need to
|
If you needed to implement ``get_db_prep_save()``, you will usually need to
|
||||||
implement ``get_db_prep_lookup()``. The usual reason is because of the
|
implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be
|
||||||
``range`` and ``in`` lookups. In these case, you will passed a list of
|
called by the default implementation, to manage ``exact``, ``gt``, ``gte``,
|
||||||
objects (presumably of the right type) and will need to convert them to a list
|
``lt``, ``lte``, ``in`` and ``range`` lookups.
|
||||||
of things of the right type for passing to the database. Sometimes you can
|
|
||||||
reuse ``get_db_prep_save()``, or at least factor out some common pieces from
|
|
||||||
both methods into a help function.
|
|
||||||
|
|
||||||
For example::
|
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
|
||||||
|
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
|
||||||
|
pieces.
|
||||||
|
|
||||||
|
For example, the following code implements ``get_db_prep_lookup`` to limit the
|
||||||
|
accepted lookup types to ``exact`` and ``in``::
|
||||||
|
|
||||||
class HandField(models.Field):
|
class HandField(models.Field):
|
||||||
# ...
|
# ...
|
||||||
|
@ -455,9 +472,9 @@ For example::
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
def get_db_prep_lookup(self, lookup_type, value):
|
||||||
# We only handle 'exact' and 'in'. All others are errors.
|
# We only handle 'exact' and 'in'. All others are errors.
|
||||||
if lookup_type == 'exact':
|
if lookup_type == 'exact':
|
||||||
return self.get_db_prep_save(value)
|
return self.get_db_prep_value(value)
|
||||||
elif lookup_type == 'in':
|
elif lookup_type == 'in':
|
||||||
return [self.get_db_prep_save(v) for v in value]
|
return [self.get_db_prep_value(v) for v in value]
|
||||||
else:
|
else:
|
||||||
raise TypeError('Lookup type %r not supported.' % lookup_type)
|
raise TypeError('Lookup type %r not supported.' % lookup_type)
|
||||||
|
|
||||||
|
@ -557,7 +574,7 @@ we can reuse some existing conversion code::
|
||||||
|
|
||||||
def flatten_data(self, follow, obj=None):
|
def flatten_data(self, follow, obj=None):
|
||||||
value = self._get_val_from_obj(obj)
|
value = self._get_val_from_obj(obj)
|
||||||
return {self.attname: self.get_db_prep_save(value)}
|
return {self.attname: self.get_db_prep_value(value)}
|
||||||
|
|
||||||
Some general advice
|
Some general advice
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
@ -31,7 +31,8 @@ class Article(models.Model):
|
||||||
SELECT id, headline, pub_date
|
SELECT id, headline, pub_date
|
||||||
FROM custom_methods_article
|
FROM custom_methods_article
|
||||||
WHERE pub_date = %s
|
WHERE pub_date = %s
|
||||||
AND id != %s""", [str(self.pub_date), self.id])
|
AND id != %s""", [connection.ops.value_to_db_date(self.pub_date),
|
||||||
|
self.id])
|
||||||
# The asterisk in "(*row)" tells Python to expand the list into
|
# The asterisk in "(*row)" tells Python to expand the list into
|
||||||
# positional arguments to Article().
|
# positional arguments to Article().
|
||||||
return [self.__class__(*row) for row in cursor.fetchall()]
|
return [self.__class__(*row) for row in cursor.fetchall()]
|
||||||
|
|
|
@ -16,6 +16,7 @@ class Person(models.Model):
|
||||||
birthdate = models.DateField()
|
birthdate = models.DateField()
|
||||||
favorite_moment = models.DateTimeField()
|
favorite_moment = models.DateTimeField()
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
|
best_time = models.TimeField()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -28,7 +29,8 @@ __test__ = {'API_TESTS':"""
|
||||||
... 'name': 'John',
|
... 'name': 'John',
|
||||||
... 'birthdate': datetime.date(2000, 5, 3),
|
... 'birthdate': datetime.date(2000, 5, 3),
|
||||||
... 'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
|
... 'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
|
||||||
... 'email': 'john@example.com'
|
... 'email': 'john@example.com',
|
||||||
|
... 'best_time': datetime.time(16, 20),
|
||||||
... }
|
... }
|
||||||
>>> p = Person(**valid_params)
|
>>> p = Person(**valid_params)
|
||||||
>>> p.validate()
|
>>> p.validate()
|
||||||
|
@ -130,6 +132,22 @@ datetime.datetime(2002, 4, 3, 13, 23)
|
||||||
>>> p.favorite_moment
|
>>> p.favorite_moment
|
||||||
datetime.datetime(2002, 4, 3, 0, 0)
|
datetime.datetime(2002, 4, 3, 0, 0)
|
||||||
|
|
||||||
|
>>> p = Person(**dict(valid_params, best_time='16:20:00'))
|
||||||
|
>>> p.validate()
|
||||||
|
{}
|
||||||
|
>>> p.best_time
|
||||||
|
datetime.time(16, 20)
|
||||||
|
|
||||||
|
>>> p = Person(**dict(valid_params, best_time='16:20'))
|
||||||
|
>>> p.validate()
|
||||||
|
{}
|
||||||
|
>>> p.best_time
|
||||||
|
datetime.time(16, 20)
|
||||||
|
|
||||||
|
>>> p = Person(**dict(valid_params, best_time='bar'))
|
||||||
|
>>> p.validate()['best_time']
|
||||||
|
[u'Enter a valid time in HH:MM[:ss[.uuuuuu]] format.']
|
||||||
|
|
||||||
>>> p = Person(**dict(valid_params, email='john@example.com'))
|
>>> p = Person(**dict(valid_params, email='john@example.com'))
|
||||||
>>> p.validate()
|
>>> p.validate()
|
||||||
{}
|
{}
|
||||||
|
@ -153,5 +171,7 @@ u'john@example.com'
|
||||||
[u'This field is required.']
|
[u'This field is required.']
|
||||||
>>> errors['birthdate']
|
>>> errors['birthdate']
|
||||||
[u'This field is required.']
|
[u'This field is required.']
|
||||||
|
>>> errors['best_time']
|
||||||
|
[u'This field is required.']
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
|
|
@ -20,16 +20,26 @@ ValidationError: [u'This value must be a decimal number.']
|
||||||
>>> x = f.to_python(2)
|
>>> x = f.to_python(2)
|
||||||
>>> y = f.to_python('2.6')
|
>>> y = f.to_python('2.6')
|
||||||
|
|
||||||
>>> f.get_db_prep_save(x)
|
>>> f._format(x)
|
||||||
u'2.0'
|
u'2.0'
|
||||||
>>> f.get_db_prep_save(y)
|
>>> f._format(y)
|
||||||
u'2.6'
|
u'2.6'
|
||||||
>>> f.get_db_prep_save(None)
|
>>> f._format(None)
|
||||||
>>> f.get_db_prep_lookup('exact', x)
|
|
||||||
[u'2.0']
|
|
||||||
>>> f.get_db_prep_lookup('exact', y)
|
|
||||||
[u'2.6']
|
|
||||||
>>> f.get_db_prep_lookup('exact', None)
|
>>> f.get_db_prep_lookup('exact', None)
|
||||||
[None]
|
[None]
|
||||||
|
|
||||||
|
# DateTimeField and TimeField to_python should support usecs:
|
||||||
|
>>> f = DateTimeField()
|
||||||
|
>>> f.to_python('2001-01-02 03:04:05.000006')
|
||||||
|
datetime.datetime(2001, 1, 2, 3, 4, 5, 6)
|
||||||
|
>>> f.to_python('2001-01-02 03:04:05.999999')
|
||||||
|
datetime.datetime(2001, 1, 2, 3, 4, 5, 999999)
|
||||||
|
|
||||||
|
>>> f = TimeField()
|
||||||
|
>>> f.to_python('01:02:03.000004')
|
||||||
|
datetime.time(1, 2, 3, 4)
|
||||||
|
>>> f.to_python('01:02:03.999999')
|
||||||
|
datetime.time(1, 2, 3, 999999)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -29,6 +29,9 @@ class Movie(models.Model):
|
||||||
class Party(models.Model):
|
class Party(models.Model):
|
||||||
when = models.DateField()
|
when = models.DateField()
|
||||||
|
|
||||||
|
class Event(models.Model):
|
||||||
|
when = models.DateTimeField()
|
||||||
|
|
||||||
__test__ = {'API_TESTS': """
|
__test__ = {'API_TESTS': """
|
||||||
(NOTE: Part of the regression test here is merely parsing the model
|
(NOTE: Part of the regression test here is merely parsing the model
|
||||||
declaration. The verbose_name, in particular, did not always work.)
|
declaration. The verbose_name, in particular, did not always work.)
|
||||||
|
@ -68,5 +71,21 @@ u''
|
||||||
>>> [p.when for p in Party.objects.filter(when__year = 1998)]
|
>>> [p.when for p in Party.objects.filter(when__year = 1998)]
|
||||||
[datetime.date(1998, 12, 31)]
|
[datetime.date(1998, 12, 31)]
|
||||||
|
|
||||||
|
# Check that get_next_by_FIELD and get_previous_by_FIELD don't crash when we
|
||||||
|
# have usecs values stored on the database
|
||||||
|
#
|
||||||
|
# [It crashed after the Field.get_db_prep_* refactor, because on most backends
|
||||||
|
# DateTimeFields supports usecs, but DateTimeField.to_python didn't recognize
|
||||||
|
# them. (Note that Model._get_next_or_previous_by_FIELD coerces values to
|
||||||
|
# strings)]
|
||||||
|
#
|
||||||
|
>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 16, 0, 0))
|
||||||
|
>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 6, 1, 1))
|
||||||
|
>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 13, 1, 1))
|
||||||
|
>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 12, 0, 20, 24))
|
||||||
|
>>> e.get_next_by_when().when
|
||||||
|
datetime.datetime(2000, 1, 1, 13, 1, 1)
|
||||||
|
>>> e.get_previous_by_when().when
|
||||||
|
datetime.datetime(2000, 1, 1, 6, 1, 1)
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue