mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
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
|
||||
from django.utils._threading_local import local
|
||||
|
||||
from django.db.backends import util
|
||||
from django.utils import datetime_safe
|
||||
|
||||
class BaseDatabaseWrapper(local):
|
||||
"""
|
||||
Represents a database connection.
|
||||
@ -36,12 +39,13 @@ class BaseDatabaseWrapper(local):
|
||||
return cursor
|
||||
|
||||
def make_debug_cursor(self, cursor):
|
||||
from django.db.backends import util
|
||||
return util.CursorDebugWrapper(cursor, self)
|
||||
|
||||
class BaseDatabaseFeatures(object):
|
||||
allows_group_by_ordinal = 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
|
||||
supports_constraints = True
|
||||
supports_tablespaces = False
|
||||
@ -49,10 +53,7 @@ class BaseDatabaseFeatures(object):
|
||||
uses_custom_query_class = False
|
||||
empty_fetchmany_value = []
|
||||
update_can_self_select = True
|
||||
supports_usecs = True
|
||||
time_field_needs_date = False
|
||||
interprets_empty_strings_as_nulls = False
|
||||
date_field_supports_time_value = True
|
||||
can_use_chunked_reads = True
|
||||
|
||||
class BaseDatabaseOperations(object):
|
||||
@ -263,3 +264,64 @@ class BaseDatabaseOperations(object):
|
||||
"""Prepares a value for use in a LIKE query."""
|
||||
from django.utils.encoding import smart_unicode
|
||||
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
|
||||
empty_fetchmany_value = ()
|
||||
update_can_self_select = False
|
||||
supports_usecs = False
|
||||
|
||||
class DatabaseOperations(BaseDatabaseOperations):
|
||||
def date_extract_sql(self, lookup_type, field_name):
|
||||
@ -124,6 +123,24 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
else:
|
||||
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):
|
||||
features = DatabaseFeatures()
|
||||
ops = DatabaseOperations()
|
||||
|
@ -5,6 +5,8 @@ Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
|
||||
"""
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import time
|
||||
|
||||
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
|
||||
from django.db.backends.oracle import query
|
||||
@ -28,9 +30,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||
supports_tablespaces = True
|
||||
uses_case_insensitive_names = True
|
||||
uses_custom_query_class = True
|
||||
time_field_needs_date = True
|
||||
interprets_empty_strings_as_nulls = True
|
||||
date_field_supports_time_value = False
|
||||
|
||||
class DatabaseOperations(BaseDatabaseOperations):
|
||||
def autoinc_sql(self, table, column):
|
||||
@ -180,6 +180,21 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
def tablespace_sql(self, tablespace, inline=False):
|
||||
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):
|
||||
features = DatabaseFeatures()
|
||||
ops = DatabaseOperations()
|
||||
|
@ -84,6 +84,12 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
# sql_flush() implementations). Just return SQL at this point
|
||||
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):
|
||||
features = DatabaseFeatures()
|
||||
ops = DatabaseOperations()
|
||||
@ -159,7 +165,7 @@ def _sqlite_extract(lookup_type, dt):
|
||||
dt = util.typecast_timestamp(dt)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return str(getattr(dt, lookup_type))
|
||||
return getattr(dt, lookup_type)
|
||||
|
||||
def _sqlite_date_trunc(lookup_type, dt):
|
||||
try:
|
||||
|
@ -117,3 +117,10 @@ def truncate_name(name, length=None):
|
||||
hash = md5.md5(name).hexdigest()[:4]
|
||||
|
||||
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."
|
||||
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):
|
||||
"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):
|
||||
"Returns field's value prepared for database lookup."
|
||||
if hasattr(value, 'as_sql'):
|
||||
sql, params = value.as_sql()
|
||||
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]
|
||||
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
|
||||
return [self.get_db_prep_value(value)]
|
||||
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'):
|
||||
return ["%%%s%%" % connection.ops.prep_for_like_query(value)]
|
||||
elif lookup_type == 'iexact':
|
||||
@ -246,19 +257,12 @@ class Field(object):
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
raise ValueError("The __year lookup type requires an integer argument")
|
||||
if settings.DATABASE_ENGINE == 'sqlite3':
|
||||
first = '%s-01-01'
|
||||
second = '%s-12-31 23:59:59.999999'
|
||||
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'
|
||||
|
||||
if self.get_internal_type() == 'DateField':
|
||||
return connection.ops.year_lookup_bounds_for_date_field(value)
|
||||
else:
|
||||
first = '%s-01-01 00:00:00'
|
||||
second = '%s-12-31 23:59:59.999999'
|
||||
return [first % value, second % value]
|
||||
return connection.ops.year_lookup_bounds(value)
|
||||
|
||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
||||
|
||||
def has_default(self):
|
||||
@ -457,6 +461,11 @@ class AutoField(Field):
|
||||
except (TypeError, ValueError):
|
||||
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):
|
||||
if not rel:
|
||||
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
|
||||
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):
|
||||
return [oldforms.CheckboxField]
|
||||
|
||||
@ -559,15 +573,6 @@ class DateField(Field):
|
||||
except ValueError:
|
||||
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):
|
||||
if self.auto_now or (self.auto_now_add and add):
|
||||
value = datetime.datetime.now()
|
||||
@ -591,16 +596,9 @@ class DateField(Field):
|
||||
else:
|
||||
return self.editable or self.auto_now or self.auto_now_add
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
# Casts dates into string format for entry into database.
|
||||
if value is not None:
|
||||
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_db_prep_value(self, value):
|
||||
# Casts dates into the format expected by the backend
|
||||
return connection.ops.value_to_db_date(self.to_python(value))
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [oldforms.DateField]
|
||||
@ -629,33 +627,37 @@ class DateTimeField(DateField):
|
||||
return value
|
||||
if isinstance(value, datetime.date):
|
||||
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.
|
||||
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:
|
||||
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.
|
||||
try:
|
||||
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
|
||||
return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
|
||||
**kwargs)
|
||||
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):
|
||||
# Casts dates into string format for entry into database.
|
||||
if value is not None:
|
||||
# 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_db_prep_value(self, value):
|
||||
# Casts dates into the format expected by the backend
|
||||
return connection.ops.value_to_db_datetime(self.to_python(value))
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
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
|
||||
decimal places.
|
||||
"""
|
||||
num_chars = self.max_digits
|
||||
# Allow for a decimal point
|
||||
if self.decimal_places > 0:
|
||||
num_chars += 1
|
||||
# Allow for a minus sign
|
||||
if value < 0:
|
||||
num_chars += 1
|
||||
# Method moved to django.db.backends.util.
|
||||
#
|
||||
# It is preserved because it is used by the oracle backend
|
||||
# (django.db.backends.oracle.query), and also for
|
||||
# backwards-compatibility with any external code which may have used
|
||||
# this method.
|
||||
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_save(self, value):
|
||||
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_db_prep_value(self, value):
|
||||
return connection.ops.value_to_db_decimal(value, self.max_digits,
|
||||
self.decimal_places)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
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):
|
||||
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."
|
||||
# Need to convert UploadedFile objects provided via a form to unicode for database insertion
|
||||
if hasattr(value, 'name'):
|
||||
@ -919,6 +913,11 @@ class FilePathField(Field):
|
||||
class FloatField(Field):
|
||||
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):
|
||||
return [oldforms.FloatField]
|
||||
|
||||
@ -966,6 +965,11 @@ class ImageField(FileField):
|
||||
|
||||
class IntegerField(Field):
|
||||
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):
|
||||
return [oldforms.IntegerField]
|
||||
|
||||
@ -1013,6 +1017,11 @@ class NullBooleanField(Field):
|
||||
if value in ('f', 'False', '0'): return 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):
|
||||
return [oldforms.NullBooleanField]
|
||||
|
||||
@ -1025,7 +1034,7 @@ class NullBooleanField(Field):
|
||||
defaults.update(kwargs)
|
||||
return super(NullBooleanField, self).formfield(**defaults)
|
||||
|
||||
class PhoneNumberField(IntegerField):
|
||||
class PhoneNumberField(Field):
|
||||
def get_manipulator_field_objs(self):
|
||||
return [oldforms.PhoneNumberField]
|
||||
|
||||
@ -1107,20 +1116,34 @@ class TimeField(Field):
|
||||
def get_internal_type(self):
|
||||
return "TimeField"
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
if connection.features.time_field_needs_date:
|
||||
# Oracle requires a date in order to parse.
|
||||
def prep(value):
|
||||
if isinstance(value, datetime.time):
|
||||
value = datetime.datetime.combine(datetime.date(1900, 1, 1), value)
|
||||
return smart_unicode(value)
|
||||
def to_python(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, datetime.time):
|
||||
return 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:
|
||||
prep = smart_unicode
|
||||
if lookup_type in ('range', 'in'):
|
||||
value = [prep(v) for v in value]
|
||||
else:
|
||||
value = prep(value)
|
||||
return Field.get_db_prep_lookup(self, lookup_type, value)
|
||||
usecs = 0
|
||||
kwargs = {'microsecond': usecs}
|
||||
|
||||
try: # Seconds are optional, so try converting seconds first.
|
||||
return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6],
|
||||
**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):
|
||||
if self.auto_now or (self.auto_now_add and add):
|
||||
@ -1130,23 +1153,9 @@ class TimeField(Field):
|
||||
else:
|
||||
return super(TimeField, self).pre_save(model_instance, add)
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
# Casts dates into string format for entry into database.
|
||||
if value is not None:
|
||||
# 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_db_prep_value(self, value):
|
||||
# Casts times into the format expected by the backend
|
||||
return connection.ops.value_to_db_time(self.to_python(value))
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
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`_
|
||||
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
|
||||
(as opposed to serialization). The ``value`` parameter is the current value of
|
||||
@ -399,10 +399,20 @@ For example::
|
||||
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,
|
||||
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)``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -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.
|
||||
|
||||
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
|
||||
``range`` and ``in`` lookups. In these case, you will passed 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. Sometimes you can
|
||||
reuse ``get_db_prep_save()``, or at least factor out some common pieces from
|
||||
both methods into a help function.
|
||||
implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be
|
||||
called by the default implementation, to manage ``exact``, ``gt``, ``gte``,
|
||||
``lt``, ``lte``, ``in`` and ``range`` lookups.
|
||||
|
||||
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):
|
||||
# ...
|
||||
@ -455,9 +472,9 @@ For example::
|
||||
def get_db_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_save(value)
|
||||
return self.get_db_prep_value(value)
|
||||
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:
|
||||
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):
|
||||
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
|
||||
--------------------
|
||||
|
@ -31,7 +31,8 @@ class Article(models.Model):
|
||||
SELECT id, headline, pub_date
|
||||
FROM custom_methods_article
|
||||
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
|
||||
# positional arguments to Article().
|
||||
return [self.__class__(*row) for row in cursor.fetchall()]
|
||||
|
@ -16,6 +16,7 @@ class Person(models.Model):
|
||||
birthdate = models.DateField()
|
||||
favorite_moment = models.DateTimeField()
|
||||
email = models.EmailField()
|
||||
best_time = models.TimeField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
@ -28,7 +29,8 @@ __test__ = {'API_TESTS':"""
|
||||
... 'name': 'John',
|
||||
... 'birthdate': datetime.date(2000, 5, 3),
|
||||
... '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.validate()
|
||||
@ -130,6 +132,22 @@ datetime.datetime(2002, 4, 3, 13, 23)
|
||||
>>> p.favorite_moment
|
||||
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.validate()
|
||||
{}
|
||||
@ -153,5 +171,7 @@ u'john@example.com'
|
||||
[u'This field is required.']
|
||||
>>> errors['birthdate']
|
||||
[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)
|
||||
>>> y = f.to_python('2.6')
|
||||
|
||||
>>> f.get_db_prep_save(x)
|
||||
>>> f._format(x)
|
||||
u'2.0'
|
||||
>>> f.get_db_prep_save(y)
|
||||
>>> f._format(y)
|
||||
u'2.6'
|
||||
>>> f.get_db_prep_save(None)
|
||||
>>> f.get_db_prep_lookup('exact', x)
|
||||
[u'2.0']
|
||||
>>> f.get_db_prep_lookup('exact', y)
|
||||
[u'2.6']
|
||||
>>> f._format(None)
|
||||
>>> f.get_db_prep_lookup('exact', 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):
|
||||
when = models.DateField()
|
||||
|
||||
class Event(models.Model):
|
||||
when = models.DateTimeField()
|
||||
|
||||
__test__ = {'API_TESTS': """
|
||||
(NOTE: Part of the regression test here is merely parsing the model
|
||||
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)]
|
||||
[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
Block a user