Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
  • Loading branch information...
commit b3b71a0922334c70bbc646a4873010f808196671 1 parent 7bc728c
@malcolmt malcolmt authored
View
70 django/db/backends/__init__.py
@@ -5,6 +5,9 @@
# 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 @@ def cursor(self):
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 @@ def prep_for_like_query(self, x):
"""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)
+
View
19 django/db/backends/mysql/base.py
@@ -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 @@ def sql_flush(self, style, tables, sequences):
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()
View
19 django/db/backends/oracle/base.py
@@ -5,6 +5,8 @@
"""
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 @@ def start_transaction_sql(self):
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()
View
8 django/db/backends/sqlite3/base.py
@@ -84,6 +84,12 @@ def sql_flush(self, style, tables, sequences):
# 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:
View
7 django/db/backends/util.py
@@ -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)
View
219 django/db/models/fields/__init__.py
@@ -218,19 +218,30 @@ def pre_save(self, model_instance, add):
"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 @@ def get_db_prep_lookup(self, lookup_type, value):
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 @@ def to_python(self, value):
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 @@ def to_python(self, value):
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 @@ def to_python(self, value):
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 @@ def get_follow(self, override=None):
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 @@ def to_python(self, value):
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 @@ def format_number(self, value):
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
-
- 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)
+ # 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)
- 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 @@ def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
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 @@ def get_internal_type(self):
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 @@ def formfield(self, **kwargs):
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 @@ def to_python(self, value):
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 @@ def formfield(self, **kwargs):
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 @@ def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=Fa
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)
- else:
- prep = smart_unicode
- if lookup_type in ('range', 'in'):
- value = [prep(v) for v in 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:
- 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 @@ def pre_save(self, model_instance, add):
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]
View
43 docs/custom_model_fields.txt
@@ -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
--------------------
View
3  tests/modeltests/custom_methods/models.py
@@ -31,7 +31,8 @@ def articles_from_same_day_2(self):
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()]
View
22 tests/modeltests/validation/models.py
@@ -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 @@ def __unicode__(self):
... '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 @@ def __unicode__(self):
>>> 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 @@ def __unicode__(self):
[u'This field is required.']
>>> errors['birthdate']
[u'This field is required.']
+>>> errors['best_time']
+[u'This field is required.']
"""}
View
24 tests/regressiontests/model_fields/tests.py
@@ -20,16 +20,26 @@
>>> 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)
+
+
"""
View
19 tests/regressiontests/model_regress/models.py
@@ -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 @@ class Party(models.Model):
>>> [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)
"""
}
Please sign in to comment.
Something went wrong with that request. Please try again.