fixed microsecond-level ordering/filtering bug with ComplexDateTimeField as well as unused separator option

This commit is contained in:
Jimmy Shen 2015-04-19 03:26:14 -04:00
parent 19dc312128
commit 2ffdbc7fc0
3 changed files with 47 additions and 30 deletions

View File

@ -10,6 +10,7 @@ Changes in 0.9.X - DEV
- Support += and *= for ListField #595 - Support += and *= for ListField #595
- Use sets for populating dbrefs to dereference - Use sets for populating dbrefs to dereference
- Fixed unpickled documents replacing the global field's list. #888 - Fixed unpickled documents replacing the global field's list. #888
- Fixed storage of microseconds in ComplexDateTimeField and unused separator option. #910
Changes in 0.9.0 Changes in 0.9.0
================ ================

View File

@ -458,37 +458,22 @@ class ComplexDateTimeField(StringField):
""" """
def __init__(self, separator=',', **kwargs): def __init__(self, separator=',', **kwargs):
self.names = ['year', 'month', 'day', 'hour', 'minute', 'second', self.names = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond']
'microsecond'] self.separator = separator
self.separtor = separator self.format = separator.join(['%Y', '%m', '%d', '%H', '%M', '%S', '%f'])
super(ComplexDateTimeField, self).__init__(**kwargs) super(ComplexDateTimeField, self).__init__(**kwargs)
def _leading_zero(self, number):
"""
Converts the given number to a string.
If it has only one digit, a leading zero so as it has always at least
two digits.
"""
if int(number) < 10:
return "0%s" % number
else:
return str(number)
def _convert_from_datetime(self, val): def _convert_from_datetime(self, val):
""" """
Convert a `datetime` object to a string representation (which will be Convert a `datetime` object to a string representation (which will be
stored in MongoDB). This is the reverse function of stored in MongoDB). This is the reverse function of
`_convert_from_string`. `_convert_from_string`.
>>> a = datetime(2011, 6, 8, 20, 26, 24, 192284) >>> a = datetime(2011, 6, 8, 20, 26, 24, 92284)
>>> RealDateTimeField()._convert_from_datetime(a) >>> ComplexDateTimeField()._convert_from_datetime(a)
'2011,06,08,20,26,24,192284' '2011,06,08,20,26,24,092284'
""" """
data = [] return val.strftime(self.format)
for name in self.names:
data.append(self._leading_zero(getattr(val, name)))
return ','.join(data)
def _convert_from_string(self, data): def _convert_from_string(self, data):
""" """
@ -496,16 +481,12 @@ class ComplexDateTimeField(StringField):
will manipulate). This is the reverse function of will manipulate). This is the reverse function of
`_convert_from_datetime`. `_convert_from_datetime`.
>>> a = '2011,06,08,20,26,24,192284' >>> a = '2011,06,08,20,26,24,092284'
>>> ComplexDateTimeField()._convert_from_string(a) >>> ComplexDateTimeField()._convert_from_string(a)
datetime.datetime(2011, 6, 8, 20, 26, 24, 192284) datetime.datetime(2011, 6, 8, 20, 26, 24, 92284)
""" """
data = data.split(',') values = map(int, data.split(self.separator))
data = map(int, data) return datetime.datetime(*values)
values = {}
for i in range(7):
values[self.names[i]] = data[i]
return datetime.datetime(**values)
def __get__(self, instance, owner): def __get__(self, instance, owner):
data = super(ComplexDateTimeField, self).__get__(instance, owner) data = super(ComplexDateTimeField, self).__get__(instance, owner)

View File

@ -5,6 +5,9 @@ sys.path[0:0] = [""]
import datetime import datetime
import unittest import unittest
import uuid import uuid
import math
import itertools
import re
try: try:
import dateutil import dateutil
@ -689,6 +692,7 @@ class FieldTest(unittest.TestCase):
""" """
class LogEntry(Document): class LogEntry(Document):
date = ComplexDateTimeField() date = ComplexDateTimeField()
date_with_dots = ComplexDateTimeField(separator='.')
LogEntry.drop_collection() LogEntry.drop_collection()
@ -729,6 +733,18 @@ class FieldTest(unittest.TestCase):
log1 = LogEntry.objects.get(date=d1) log1 = LogEntry.objects.get(date=d1)
self.assertEqual(log, log1) self.assertEqual(log, log1)
# Test string padding
microsecond = map(int, [math.pow(10, x) for x in xrange(6)])
mm = dd = hh = ii = ss = [1, 10]
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):
stored = LogEntry(date=datetime.datetime(*values)).to_mongo()['date']
self.assertTrue(re.match('^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$', stored) is not None)
# Test separator
stored = LogEntry(date_with_dots=datetime.datetime(2014, 1, 1)).to_mongo()['date_with_dots']
self.assertTrue(re.match('^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$', stored) is not None)
LogEntry.drop_collection() LogEntry.drop_collection()
def test_complexdatetime_usage(self): def test_complexdatetime_usage(self):
@ -787,6 +803,25 @@ class FieldTest(unittest.TestCase):
LogEntry.drop_collection() LogEntry.drop_collection()
# Test microsecond-level ordering/filtering
for microsecond in (99, 999, 9999, 10000):
LogEntry(date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond)).save()
logs = list(LogEntry.objects.order_by('date'))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date < next_log.date)
logs = list(LogEntry.objects.order_by('-date'))
for next_idx, log in enumerate(logs[:-1], start=1):
next_log = logs[next_idx]
self.assertTrue(log.date > next_log.date)
logs = LogEntry.objects.filter(date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000))
self.assertEqual(logs.count(), 4)
LogEntry.drop_collection()
def test_list_validation(self): def test_list_validation(self):
"""Ensure that a list field only accepts lists with valid elements. """Ensure that a list field only accepts lists with valid elements.
""" """