Skip to content

Commit

Permalink
Change the serialization of the datetime objects, and store them as o…
Browse files Browse the repository at this point in the history
…ffset-aware UTC-based objects
  • Loading branch information
sneridagh committed Aug 11, 2017
1 parent 5b4435e commit d6f3edd
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 8 deletions.
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ New Features:

- Translate titles in @workflow.
[csenger]

- Add skipped tests from @breadcrumbs and @navigation now that the expansion is in place
[sneridagh]

- Add endpoints for locking/unlocking.
[buchi]

- The datetime objects are now stored as offset-aware UTC-based objects
[sneridagh]


1.0a20 (2017-07-24)
-------------------
Expand Down
6 changes: 5 additions & 1 deletion docs/source/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Python JSON
``DateTime('2015/11/23 19:45:55')`` ``'2015-11-23T19:45:55'``
======================================= ======================================

.. warning::
All datetimes objects will be serialized adding the proper time zone information, storing an offset-aware object on it.
In case of using zope.schema date validators you should also use a datetime object that also contains offset-aware object as the validator value.


RichText fields
---------------
Expand Down Expand Up @@ -155,4 +159,4 @@ UID ``'9b6a4eadb9074dde97d86171bb332ae9'``
IntId ``123456``
Path ``'/plone/doc1'``
URL ``'http://localhost:8080/plone/doc1'``
======================================= ======================================
======================================= ======================================
3 changes: 1 addition & 2 deletions src/plone/restapi/deserializer/dxfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ def __call__(self, value):
try:
# Parse ISO 8601 string with Zope's DateTime module
# and convert to a timezone naive datetime in local time
value = DateTime(value).toZone(DateTime().localZone()).asdatetime(
).replace(tzinfo=None)
value = DateTime(value).toZone('UTC').asdatetime()
except (SyntaxError, DateTimeError) as e:
raise ValueError(e.message)

Expand Down
5 changes: 3 additions & 2 deletions src/plone/restapi/tests/dxtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary

import pytz

INDEXES = (
("test_int_field", "FieldIndex"),
Expand Down Expand Up @@ -92,8 +93,8 @@ class IDXTestDocumentSchema(model.Schema):
test_maxlength_field = schema.TextLine(required=False, max_length=10)
test_constraint_field = schema.TextLine(required=False,
constraint=lambda x: u'00' in x)
test_datetime_min_field = schema.Datetime(required=False,
min=datetime(2000, 1, 1))
test_datetime_min_field = schema.Datetime(
required=False, min=datetime(2000, 1, 1, 0, 0, 0, 0, pytz.UTC))
test_time_min_field = schema.Time(required=False, min=time(1))
test_timedelta_min_field = schema.Timedelta(required=False,
min=timedelta(100))
Expand Down
53 changes: 53 additions & 0 deletions src/plone/restapi/tests/test_content_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
from plone.app.testing import login
from plone.app.testing import setRoles
from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING
from plone.restapi.testing import RelativeSession
from DateTime import DateTime

import datetime
import requests
import transaction
import unittest
Expand All @@ -22,8 +25,14 @@ class TestContentPatch(unittest.TestCase):
def setUp(self):
self.app = self.layer['app']
self.portal = self.layer['portal']
self.portal_url = self.portal.absolute_url()
setRoles(self.portal, TEST_USER_ID, ['Member'])
login(self.portal, SITE_OWNER_NAME)

self.api_session = RelativeSession(self.portal_url)
self.api_session.headers.update({'Accept': 'application/json'})
self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)

self.portal.invokeFactory(
'Document',
id='doc1',
Expand Down Expand Up @@ -78,3 +87,47 @@ def test_patch_document_returns_401_unauthorized(self):
data='{"title": "Patched Document"}',
)
self.assertEqual(401, response.status_code)

def test_patch_feed_event_with_get_contents(self):
start_date = DateTime(datetime.datetime.today() +
datetime.timedelta(days=1)).ISO8601()
end_date = DateTime(datetime.datetime.today() +
datetime.timedelta(days=1, hours=1)).ISO8601()
response = self.api_session.post(
'/',
json={
"title": "An Event",
"@type": "Event",
"start": start_date,
"end": end_date,
"timezone": "Europe/Vienna"
},
)

self.assertEqual(201, response.status_code)

response = response.json()
event_id = response['id']
two_days_ahead = DateTime(datetime.datetime.today() +
datetime.timedelta(days=2))
response = self.api_session.patch(
'/{}'.format(event_id),
json={
"start": response['start'],
"end": two_days_ahead.ISO8601()
}
)

self.assertEqual(204, response.status_code)

response = self.api_session.get('/{}'.format(event_id))
response = response.json()

self.assertEquals(
DateTime(response['end']).day(),
two_days_ahead.day()
)
self.assertEquals(
DateTime(response['end']).hour(),
two_days_ahead.hour()
)
55 changes: 55 additions & 0 deletions src/plone/restapi/tests/test_content_post.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from Products.CMFCore.utils import getToolByName
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import SITE_OWNER_PASSWORD
Expand Down Expand Up @@ -196,3 +197,57 @@ def test_id_from_filename(self):
self.assertEqual(201, response.status_code)
transaction.begin()
self.assertIn('test.txt', self.portal.folder1)


class TestEventCT(unittest.TestCase):

layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING

def setUp(self):
self.app = self.layer['app']
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Member'])
login(self.portal, SITE_OWNER_NAME)
self.portal.invokeFactory(
'Folder',
id='folder1',
title='My Folder'
)
wftool = getToolByName(self.portal, 'portal_workflow')
wftool.doActionFor(self.portal.folder1, 'publish')
transaction.commit()

def test_post_to_folder_creates_event_with_TZ(self):
response = requests.post(
self.portal.folder1.absolute_url(),
headers={'Accept': 'application/json'},
auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
json={
"@type": "Event",
"id": "myevent",
"title": "My Event",
"start": datetime(2013, 1, 1, 10, 0).isoformat(),
"end": datetime(2013, 1, 1, 12, 0).isoformat(),
"timezone": 'Europe/Madrid'
},
)
self.assertEqual(201, response.status_code)
self.assertEqual(
response.json()['start'], u'2013-01-01T10:00:00+01:00')

response = requests.post(
self.portal.folder1.absolute_url(),
headers={'Accept': 'application/json'},
auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD),
json={
"@type": "Event",
"id": "myevent2",
"title": "My Event",
"start": datetime(2018, 1, 1, 10, 0).isoformat(),
"end": datetime(2018, 1, 1, 12, 0).isoformat(),
"timezone": 'Asia/Saigon'
},
)
self.assertEqual(201, response.status_code)
self.assertEqual(
response.json()['start'], u'2018-01-01T10:00:00+07:00')
7 changes: 5 additions & 2 deletions src/plone/restapi/tests/test_dxfield_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from zope.component import getMultiAdapter
from zope.schema.interfaces import ValidationError

import pytz
import unittest


Expand Down Expand Up @@ -79,12 +80,14 @@ def test_datetime_deserialization_returns_datetime(self):
value = self.deserialize('test_datetime_field',
u'2015-12-20T10:39:54.361Z')
self.assertTrue(isinstance(value, datetime), 'Not a <datetime>')
self.assertEqual(datetime(2015, 12, 20, 10, 39, 54, 361000), value)
self.assertEqual(
datetime(2015, 12, 20, 10, 39, 54, 361000, pytz.UTC), value)

def test_datetime_deserialization_handles_timezone(self):
value = self.deserialize('test_datetime_field',
u'2015-12-20T10:39:54.361+01')
self.assertEqual(datetime(2015, 12, 20, 9, 39, 54, 361000), value)
self.assertEqual(
datetime(2015, 12, 20, 9, 39, 54, 361000, pytz.UTC), value)

def test_decimal_deserialization_returns_decimal(self):
value = self.deserialize('test_decimal_field', u'1.1')
Expand Down

0 comments on commit d6f3edd

Please sign in to comment.