fixed microsecond-level ordering/filtering bug with ComplexDateTimeField as well as unused separator option
This commit is contained in:
		| @@ -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 | ||||||
| ================ | ================ | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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. | ||||||
|         """ |         """ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user