Repository: Scille/umongo
Pull request #178 (2019-03-12 13:19:28)

Add DateField


Patch:
diff --git a/.travis.yml b/.travis.yml
index fb47e70..0738e93 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,7 @@ addons:
     - mongodb-org-shell
 language: python
 python:
-- '3.5'
+- '3.4'
 - '3.6'
 env:
 - DRIVER=pymongo
@@ -30,7 +30,7 @@ deploy:
   distributions: sdist bdist_wheel
   user: touilleMan
   password:
-    secure: CqBHylDHDlEqMBCFNCt1Wph+FGW3quow5iP6FRF+P4iOaNxCTQCs221/Owjx0AWofe4oYZJsKQR1C0FVEmTGvzId9PMOCd5B0tpGQTHZrxSynHC9c//lF68sXdQKo35+TXCk6cypdnKUDE8XW/lK52jOO5sczh6hXhu2rB1EghXwjpMUA2Kxmn4HIBLGLGlithG9uvUlyoYecGUpWu2evaoEw5FUdHkZZmyKZFuzXrxLTDLiKy6KBzRs2RE0cGmZDSa9afCcNstkJakK9lGk7w1GKodAshbMMwnGn9XTTxyUObTLpiTq7FXgMJMX8YWpkrJnK1YH9a/SiqD9pI/QeMhNV2C1PI3+sneDuu42zhz2palhD9b7gO+gGqQVnQ5TtLZlxl1Z8pI2sOdQc+M6L/AeYinzaUpR55AsjvWcKGp4azjUe6HWAdr/blUZ/5LHLQHGpua8Dq7W8miZ1+AEo3n+WuYRrX1l2fB8eWs25Rli6jZAUmngZtqaQs5l77osBZsOW3gBO+8uzVSBs9kKw16peHyY3YBe+loD5Sy7L9T7kqJgdTEGqixYCbgxVPv17TsyuiZyRBAas7fVJeuBR7Clx0C3p2RVX7ViXwOltaDLYoWW98/Pt4emNYopQJSGNtR8mMUmW8fxPyE2kThM7siGi8MhYZ4YHawuiAD10wY=
+    secure: cQN5qW1k5kMQprmgIWiqjXyvH5SdZCs93a1f5NVquo/NsxFvwvtm6J3Zmp8NaC4tvRzSpyolZyW+5o0ISXBMiLPpv4cqGHdSaKfzWRWtTUy+JdQjcZxcPi7vD7E+HJha1+7gjpoX8qXD8NyOlWr4VFhQxWd77cFA19tgIvhbDCMN1BadaCObNDI/D8jomVygGH/G5bk4QQc9+xl7JSbBzGcncEjUXa83WSFgXXuQtnFchArR12YQQSVN4Y1Rnm215haatJ511oGEby3PAkUw4kaScbW/lo8w2hAY/p9YRAV9+5SXASDCdta53opawoX9rFfGCiZhuil5XJCxz29IqLD5TtcWOyzte45Soz7VYf7wWdmokMCxZO3lMv6IH275VdZOYrDRrRAM9pO0HxPF5K6hZxi4tZgSsFgdV9t4qfjpQp0cazseCpiCSRtWbNWYXkrdm+LWnbIkC1Oz2np4Ehw+rEpRHZyd31ENyhKhG1n6GNuZrtTjIeRYnTolV5mOudZqvib8egqtck4ehWrA4rgJtYwzoLKEI2OtRYxjLaLrOKYn5rGrDM1ayeFQA13NCJ5LqCv6qFtswYVA96bKbB66HAygySeQZr/lYlgzAbmLi/bVV7JO+auG79RWyyr3n0Yz9rmovNuWEBCbAei3ghL8sRx2a9ifEveXVh3ZK0U=
   on:
     tags: true
     repo: Scille/umongo
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 0390329..77db671 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -122,6 +122,6 @@ Before you submit a pull request, check that it meets these guidelines:
 2. If the pull request adds functionality, the docs should be updated. Put
    your new functionality into a function with a docstring, and add the
    feature to the list in README.rst.
-3. The pull request should work for Python 3.5 and 3.6. Check
+3. The pull request should work for Python 3.4 and 3.5. Check
    https://travis-ci.org/touilleMan/umongo/pull_requests
    and make sure that the tests pass for all supported Python versions.
diff --git a/examples/flask/README.rst b/examples/flask/README.rst
index bb508eb..9b012c2 100644
--- a/examples/flask/README.rst
+++ b/examples/flask/README.rst
@@ -21,7 +21,7 @@ Displaying a user:
     Content-Length: 145
     Content-Type: application/json
     Date: Mon, 02 May 2016 10:07:35 GMT
-    Server: Werkzeug/0.14.1 Python/3.5.3
+    Server: Werkzeug/0.11.5 Python/3.4.3
 
     {
         "birthday": "1953-06-15T00:00:00+00:00", 
@@ -40,7 +40,7 @@ Bad payload while trying to create user:
     Content-Length: 132
     Content-Type: application/json
     Date: Mon, 02 May 2016 10:06:12 GMT
-    Server: Werkzeug/0.14.1 Python/3.5.3
+    Server: Werkzeug/0.11.5 Python/3.4.3
 
     {
         "message": {
@@ -62,7 +62,7 @@ Same thing but with a ``accept-language`` header to specify French as prefered l
     Content-Length: 130
     Content-Type: application/json
     Date: Mon, 02 May 2016 10:05:20 GMT
-    Server: Werkzeug/0.14.1 Python/3.5.3
+    Server: Werkzeug/0.11.5 Python/3.4.3
 
     {
         "message": {
diff --git a/examples/flask/app.py b/examples/flask/app.py
index fc2cfc3..2f46fbb 100644
--- a/examples/flask/app.py
+++ b/examples/flask/app.py
@@ -1,9 +1,9 @@
 from datetime import datetime
-
 from flask import Flask, abort, jsonify, request
-from flask_babel import Babel, gettext
+from flask.ext.babel import Babel, gettext
 from bson import ObjectId
 from pymongo import MongoClient
+import marshmallow
 
 from umongo import Instance, Document, fields, ValidationError, set_gettext
 from umongo.marshmallow_bonus import SchemaFromUmongo
diff --git a/setup.py b/setup.py
index 2790741..92e744a 100755
--- a/setup.py
+++ b/setup.py
@@ -45,6 +45,7 @@ setup(
         'License :: OSI Approved :: MIT License',
         'Natural Language :: English',
         'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
     ],
diff --git a/tox.ini b/tox.ini
index 968fdaa..c003e3b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = {py35,py36}-{motor,pymongo,txmongo}
+envlist = {py34,py35,py36}-{motor,pymongo,txmongo}
 
 [testenv]
 usedevelop = True
diff --git a/umongo/fields.py b/umongo/fields.py
index b147305..bb97035 100644
--- a/umongo/fields.py
+++ b/umongo/fields.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+import datetime as dt
 
 from bson import DBRef, ObjectId, Decimal128
 from marshmallow import ValidationError, missing
@@ -168,7 +168,7 @@ class FloatField(BaseField, ma_fields.Float):
 class DateTimeField(BaseField, ma_fields.DateTime):
 
     def _deserialize(self, value, attr, data):
-        if isinstance(value, datetime):
+        if isinstance(value, dt.datetime):
             ret = value
         else:
             ret = super()._deserialize(value, attr, data)
@@ -180,7 +180,7 @@ class DateTimeField(BaseField, ma_fields.DateTime):
 class LocalDateTimeField(BaseField, ma_fields.LocalDateTime):
 
     def _deserialize(self, value, attr, data):
-        if isinstance(value, datetime):
+        if isinstance(value, dt.datetime):
             ret = value
         else:
             ret = super()._deserialize(value, attr, data)
@@ -193,8 +193,19 @@ class LocalDateTimeField(BaseField, ma_fields.LocalDateTime):
 #     pass
 
 
-# class DateField(BaseField, ma_fields.Date):
-#     pass
+class DateField(BaseField, ma_fields.Date):
+    """Convert date to datetime to store as BSON Date"""
+
+    def _deserialize(self, value, attr, data):
+        if isinstance(value, dt.date):
+            return value
+        return super()._deserialize(value, attr, data)
+
+    def _serialize_to_mongo(self, obj):
+        return dt.datetime(obj.year, obj.month, obj.day)
+
+    def _deserialize_from_mongo(self, value):
+        return value.date()
 
 
 # class TimeDeltaField(BaseField, ma_fields.TimeDelta):
@@ -225,7 +236,7 @@ IntField = IntegerField
 class StrictDateTimeField(BaseField, ma_bonus_fields.StrictDateTime):
 
     def _deserialize(self, value, attr, data):
-        if isinstance(value, datetime):
+        if isinstance(value, dt.datetime):
             ret = self._set_tz_awareness(value)
         else:
             ret = super()._deserialize(value, attr, data)


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 1dd9acc..7933f36 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1,5 +1,5 @@
 from decimal import Decimal
-from datetime import datetime
+import datetime as dt
 from uuid import UUID
 
 import pytest
@@ -25,7 +25,7 @@ class TestRequired(BaseTest):
             name = fields.StrField(required=True)
             birthday = fields.DateTimeField()
 
-        person = Person(birthday=datetime(1968, 6, 9))
+        person = Person(birthday=dt.datetime(1968, 6, 9))
 
         # required should be called during commit
         with pytest.raises(ValidationError) as exc:
@@ -154,20 +154,20 @@ class TestFields(BaseTest):
             b = fields.LocalDateTimeField()
 
         s = MySchema(strict=True)
-        data, _ = s.load({'a': datetime(2016, 8, 6)})
-        assert data['a'] == datetime(2016, 8, 6)
+        data, _ = s.load({'a': dt.datetime(2016, 8, 6)})
+        assert data['a'] == dt.datetime(2016, 8, 6)
         data, _ = s.load({'a': "2016-08-06T00:00:00Z"})
-        assert data['a'] == datetime(2016, 8, 6, tzinfo=tzutc())
+        assert data['a'] == dt.datetime(2016, 8, 6, tzinfo=tzutc())
         data, _ = s.load({'a': "2016-08-06T00:00:00"})
-        assert data['a'] == datetime(2016, 8, 6)
+        assert data['a'] == dt.datetime(2016, 8, 6)
         with pytest.raises(ValidationError):
             s.load({'a': "dummy"})
 
         # Test DateTimeField and LocalDateTimeField round to milliseconds
         s = MySchema()
         data, _ = s.load({
-            'a': datetime(2016, 8, 6, 12, 30, 30, 123456),
-            'b': datetime(2016, 8, 6, 12, 30, 30, 123456),
+            'a': dt.datetime(2016, 8, 6, 12, 30, 30, 123456),
+            'b': dt.datetime(2016, 8, 6, 12, 30, 30, 123456),
         })
         assert data['a'].microsecond == 123000
         assert data['b'].microsecond == 123000
@@ -183,24 +183,24 @@ class TestFields(BaseTest):
         s = MySchema(strict=True)
 
         for date in (
-            datetime(2016, 8, 6),
-            datetime(2016, 8, 6, tzinfo=tzutc()),
+            dt.datetime(2016, 8, 6),
+            dt.datetime(2016, 8, 6, tzinfo=tzutc()),
             "2016-08-06T00:00:00Z",
             "2016-08-06T00:00:00",
         ):
             data, _ = s.load({'a': date, 'b': date, 'c': date})
-            assert data['a'] == datetime(2016, 8, 6)
-            assert data['b'] == datetime(2016, 8, 6)
-            assert data['c'] == datetime(2016, 8, 6, tzinfo=tzutc())
+            assert data['a'] == dt.datetime(2016, 8, 6)
+            assert data['b'] == dt.datetime(2016, 8, 6)
+            assert data['c'] == dt.datetime(2016, 8, 6, tzinfo=tzutc())
 
         for date in (
             "2016-08-06T00:00:00+02:00",
-            datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200)),
+            dt.datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200)),
         ):
             data, _ = s.load({'a': date, 'b': date, 'c': date})
-            assert data['a'] == datetime(2016, 8, 5, 22, 0)
-            assert data['b'] == datetime(2016, 8, 5, 22, 0)
-            assert data['c'] == datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200))
+            assert data['a'] == dt.datetime(2016, 8, 5, 22, 0)
+            assert data['b'] == dt.datetime(2016, 8, 5, 22, 0)
+            assert data['c'] == dt.datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200))
 
         with pytest.raises(ValidationError):
             s.load({'a': "dummy"})
@@ -210,27 +210,48 @@ class TestFields(BaseTest):
         d = MyDataProxy()
 
         for date in (
-            datetime(2016, 8, 6),
-            datetime(2016, 8, 6, tzinfo=tzutc()),
+            dt.datetime(2016, 8, 6),
+            dt.datetime(2016, 8, 6, tzinfo=tzutc()),
         ):
             d.from_mongo({'a': date, 'b': date, 'c': date})
-            assert d.get('a') == datetime(2016, 8, 6)
-            assert d.get('b') == datetime(2016, 8, 6)
-            assert d.get('c') == datetime(2016, 8, 6, tzinfo=tzutc())
+            assert d.get('a') == dt.datetime(2016, 8, 6)
+            assert d.get('b') == dt.datetime(2016, 8, 6)
+            assert d.get('c') == dt.datetime(2016, 8, 6, tzinfo=tzutc())
 
         for date in (
-            datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200)),
+            dt.datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200)),
         ):
             d.from_mongo({'a': date, 'b': date, 'c': date})
-            assert d.get('a') == datetime(2016, 8, 5, 22, 0)
-            assert d.get('b') == datetime(2016, 8, 5, 22, 0)
-            assert d.get('c') == datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200))
+            assert d.get('a') == dt.datetime(2016, 8, 5, 22, 0)
+            assert d.get('b') == dt.datetime(2016, 8, 5, 22, 0)
+            assert d.get('c') == dt.datetime(2016, 8, 6, tzinfo=tzoffset(None, 7200))
 
         # Test StrictDateTimeField rounds to milliseconds
         s = MySchema()
-        data, _ = s.load({'a': datetime(2016, 8, 6, 12, 30, 30, 123456)})
+        data, _ = s.load({'a': dt.datetime(2016, 8, 6, 12, 30, 30, 123456)})
         assert data['a'].microsecond == 123000
 
+    def test_date(self):
+
+        class MySchema(EmbeddedSchema):
+            a = fields.DateField()
+
+        s = MySchema(strict=True)
+        data, _ = s.load({'a': dt.date(2016, 8, 6)})
+        assert data['a'] == dt.date(2016, 8, 6)
+        data, _ = s.load({'a': "2016-08-06"})
+        assert data['a'] == dt.date(2016, 8, 6)
+        with pytest.raises(ValidationError):
+            s.load({'a': "dummy"})
+
+        # Test _serialize_to_mongo / _deserialize_from_mongo
+        MyDataProxy = data_proxy_factory('My', MySchema())
+        d = MyDataProxy()
+
+        d.from_mongo({'a': dt.datetime(2019, 8, 6)})
+        assert d.get('a') == dt.date(2019, 8, 6)
+        assert d.to_mongo() == {'a': dt.datetime(2019, 8, 6)}
+
     def test_dict(self):
 
         class MySchema(Schema):