Skip to content

Commit 02a557a

Browse files
author
erdenezul
authoredJul 27, 2018
Merge pull request MongoEngine#1825 from orsinium/date-field
Date field
2 parents ae783d4 + 6da27e5 commit 02a557a

File tree

3 files changed

+164
-3
lines changed

3 files changed

+164
-3
lines changed
 

‎docs/changelog.rst

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
Changelog
33
=========
44

5+
Changes in 0.15.4
6+
=================
7+
- Added `DateField` #513
8+
59
Changes in 0.15.3
610
=================
711
- Subfield resolve error in generic_emdedded_document query #1651 #1652

‎mongoengine/fields.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
__all__ = (
4545
'StringField', 'URLField', 'EmailField', 'IntField', 'LongField',
46-
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField',
46+
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField', 'DateField',
4747
'ComplexDateTimeField', 'EmbeddedDocumentField', 'ObjectIdField',
4848
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
4949
'SortedListField', 'EmbeddedDocumentListField', 'DictField',
@@ -525,6 +525,22 @@ def prepare_query_value(self, op, value):
525525
return super(DateTimeField, self).prepare_query_value(op, self.to_mongo(value))
526526

527527

528+
class DateField(DateTimeField):
529+
def to_mongo(self, value):
530+
value = super(DateField, self).to_mongo(value)
531+
# drop hours, minutes, seconds
532+
if isinstance(value, datetime.datetime):
533+
value = datetime.datetime(value.year, value.month, value.day)
534+
return value
535+
536+
def to_python(self, value):
537+
value = super(DateField, self).to_python(value)
538+
# convert datetime to date
539+
if isinstance(value, datetime.datetime):
540+
value = datetime.date(value.year, value.month, value.day)
541+
return value
542+
543+
528544
class ComplexDateTimeField(StringField):
529545
"""
530546
ComplexDateTimeField handles microseconds exactly instead of rounding

‎tests/fields/fields.py

+143-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ class MyDoc(Document):
4646
md = MyDoc(dt='')
4747
self.assertRaises(ValidationError, md.save)
4848

49+
def test_date_from_empty_string(self):
50+
"""
51+
Ensure an exception is raised when trying to
52+
cast an empty string to datetime.
53+
"""
54+
class MyDoc(Document):
55+
dt = DateField()
56+
57+
md = MyDoc(dt='')
58+
self.assertRaises(ValidationError, md.save)
59+
4960
def test_datetime_from_whitespace_string(self):
5061
"""
5162
Ensure an exception is raised when trying to
@@ -57,6 +68,17 @@ class MyDoc(Document):
5768
md = MyDoc(dt=' ')
5869
self.assertRaises(ValidationError, md.save)
5970

71+
def test_date_from_whitespace_string(self):
72+
"""
73+
Ensure an exception is raised when trying to
74+
cast a whitespace-only string to datetime.
75+
"""
76+
class MyDoc(Document):
77+
dt = DateField()
78+
79+
md = MyDoc(dt=' ')
80+
self.assertRaises(ValidationError, md.save)
81+
6082
def test_default_values_nothing_set(self):
6183
"""Ensure that default field values are used when creating
6284
a document.
@@ -66,13 +88,14 @@ class Person(Document):
6688
age = IntField(default=30, required=False)
6789
userid = StringField(default=lambda: 'test', required=True)
6890
created = DateTimeField(default=datetime.datetime.utcnow)
91+
day = DateField(default=datetime.date.today)
6992

7093
person = Person(name="Ross")
7194

7295
# Confirm saving now would store values
7396
data_to_be_saved = sorted(person.to_mongo().keys())
7497
self.assertEqual(data_to_be_saved,
75-
['age', 'created', 'name', 'userid']
98+
['age', 'created', 'day', 'name', 'userid']
7699
)
77100

78101
self.assertTrue(person.validate() is None)
@@ -81,16 +104,18 @@ class Person(Document):
81104
self.assertEqual(person.age, person.age)
82105
self.assertEqual(person.userid, person.userid)
83106
self.assertEqual(person.created, person.created)
107+
self.assertEqual(person.day, person.day)
84108

85109
self.assertEqual(person._data['name'], person.name)
86110
self.assertEqual(person._data['age'], person.age)
87111
self.assertEqual(person._data['userid'], person.userid)
88112
self.assertEqual(person._data['created'], person.created)
113+
self.assertEqual(person._data['day'], person.day)
89114

90115
# Confirm introspection changes nothing
91116
data_to_be_saved = sorted(person.to_mongo().keys())
92117
self.assertEqual(
93-
data_to_be_saved, ['age', 'created', 'name', 'userid'])
118+
data_to_be_saved, ['age', 'created', 'day', 'name', 'userid'])
94119

95120
def test_default_values_set_to_None(self):
96121
"""Ensure that default field values are used even when
@@ -662,6 +687,32 @@ class LogEntry(Document):
662687
log.time = 'ABC'
663688
self.assertRaises(ValidationError, log.validate)
664689

690+
def test_date_validation(self):
691+
"""Ensure that invalid values cannot be assigned to datetime
692+
fields.
693+
"""
694+
class LogEntry(Document):
695+
time = DateField()
696+
697+
log = LogEntry()
698+
log.time = datetime.datetime.now()
699+
log.validate()
700+
701+
log.time = datetime.date.today()
702+
log.validate()
703+
704+
log.time = datetime.datetime.now().isoformat(' ')
705+
log.validate()
706+
707+
if dateutil:
708+
log.time = datetime.datetime.now().isoformat('T')
709+
log.validate()
710+
711+
log.time = -1
712+
self.assertRaises(ValidationError, log.validate)
713+
log.time = 'ABC'
714+
self.assertRaises(ValidationError, log.validate)
715+
665716
def test_datetime_tz_aware_mark_as_changed(self):
666717
from mongoengine import connection
667718

@@ -733,6 +784,51 @@ class LogEntry(Document):
733784
self.assertNotEqual(log.date, d1)
734785
self.assertEqual(log.date, d2)
735786

787+
def test_date(self):
788+
"""Tests showing pymongo date fields
789+
790+
See: http://api.mongodb.org/python/current/api/bson/son.html#dt
791+
"""
792+
class LogEntry(Document):
793+
date = DateField()
794+
795+
LogEntry.drop_collection()
796+
797+
# Test can save dates
798+
log = LogEntry()
799+
log.date = datetime.date.today()
800+
log.save()
801+
log.reload()
802+
self.assertEqual(log.date, datetime.date.today())
803+
804+
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999)
805+
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1)
806+
log = LogEntry()
807+
log.date = d1
808+
log.save()
809+
log.reload()
810+
self.assertEqual(log.date, d1.date())
811+
self.assertEqual(log.date, d2.date())
812+
813+
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999)
814+
d2 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9000)
815+
log.date = d1
816+
log.save()
817+
log.reload()
818+
self.assertEqual(log.date, d1.date())
819+
self.assertEqual(log.date, d2.date())
820+
821+
if not six.PY3:
822+
# Pre UTC dates microseconds below 1000 are dropped
823+
# This does not seem to be true in PY3
824+
d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999)
825+
d2 = datetime.datetime(1969, 12, 31, 23, 59, 59)
826+
log.date = d1
827+
log.save()
828+
log.reload()
829+
self.assertEqual(log.date, d1.date())
830+
self.assertEqual(log.date, d2.date())
831+
736832
def test_datetime_usage(self):
737833
"""Tests for regular datetime fields"""
738834
class LogEntry(Document):
@@ -787,6 +883,51 @@ class LogEntry(Document):
787883
)
788884
self.assertEqual(logs.count(), 5)
789885

886+
def test_date_usage(self):
887+
"""Tests for regular datetime fields"""
888+
class LogEntry(Document):
889+
date = DateField()
890+
891+
LogEntry.drop_collection()
892+
893+
d1 = datetime.datetime(1970, 1, 1, 0, 0, 1)
894+
log = LogEntry()
895+
log.date = d1
896+
log.validate()
897+
log.save()
898+
899+
for query in (d1, d1.isoformat(' ')):
900+
log1 = LogEntry.objects.get(date=query)
901+
self.assertEqual(log, log1)
902+
903+
if dateutil:
904+
log1 = LogEntry.objects.get(date=d1.isoformat('T'))
905+
self.assertEqual(log, log1)
906+
907+
# create additional 19 log entries for a total of 20
908+
for i in range(1971, 1990):
909+
d = datetime.datetime(i, 1, 1, 0, 0, 1)
910+
LogEntry(date=d).save()
911+
912+
self.assertEqual(LogEntry.objects.count(), 20)
913+
914+
# Test ordering
915+
logs = LogEntry.objects.order_by("date")
916+
i = 0
917+
while i < 19:
918+
self.assertTrue(logs[i].date <= logs[i + 1].date)
919+
i += 1
920+
921+
logs = LogEntry.objects.order_by("-date")
922+
i = 0
923+
while i < 19:
924+
self.assertTrue(logs[i].date >= logs[i + 1].date)
925+
i += 1
926+
927+
# Test searching
928+
logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1))
929+
self.assertEqual(logs.count(), 10)
930+
790931
def test_complexdatetime_storage(self):
791932
"""Tests for complex datetime fields - which can handle
792933
microseconds without rounding.

0 commit comments

Comments
 (0)
Please sign in to comment.