Repository: fossasia/open-event-server

Issue:
Fix POST API testing of event and session model by using custom DateTime field
Both Event and session model includes a datetime field which currently can't be tested because it is not possible to write datetime fields as string from SQLAlchemy in a SQLite database (used in Testing). 

However, this issue can be fixed by a using a custom field for DateTime that returns the DateTime object on POST requests. Using a custom DateTime field will also give more control over the datetime 'format'. 

I am currently think about 'YYYY-MM-DD HH:MM:SS' as the DateTime format to be used in the API . 
@niranjan94 @SaptakS Is the format ok w.r.t UI ?

EDIT - Just found out restplus doesn't actually validate the (inbuilt) DateTime fields. It lets it go and psycopg throws the exception. (https://bpaste.net/show/e332f0095399)



Discussion:

Resolving patch:
diff --git a/open_event/api/custom_fields.py b/open_event/api/custom_fields.py
--- a/open_event/api/custom_fields.py
+++ b/open_event/api/custom_fields.py
@@ -1,6 +1,7 @@
 import re
-from flask.ext.restplus.fields import Raw
 import colour
+from datetime import datetime
+from flask.ext.restplus.fields import Raw
 
 
 EMAIL_REGEX = re.compile(r'\S+@\S+\.\S+')
@@ -99,3 +100,34 @@ def validate(self, value):
         except Exception:
             return False
         return True
+
+
+class DateTimeField(CustomField):
+    """
+    Custom DateTime field
+    """
+    __schema_type__ = 'string'
+    __schema_format__ = 'date-time'
+    __schema_example__ = '2016-06-06 11:22:33'
+    dt_format = '%Y-%m-%d %H:%M:%S'
+
+    def to_str(self, value):
+        return unicode(value.strftime(self.dt_format))
+
+    def from_str(self, value):
+        return datetime.strptime(value, self.dt_format)
+
+    def format(self, value):
+        return self.to_str(value)
+
+    def validate(self, value):
+        if not value:
+            return self.validate_empty()
+        try:
+            if value.__class__.__name__ in ['unicode', 'str']:
+                self.from_str(value)
+            else:
+                self.to_str(value)
+        except Exception:
+            return False
+        return True
diff --git a/open_event/api/events.py b/open_event/api/events.py
--- a/open_event/api/events.py
+++ b/open_event/api/events.py
@@ -1,12 +1,14 @@
 from flask.ext.restplus import Resource, Namespace, fields
 from flask import g
 
-from custom_fields import EmailField, ColorField, UriField, ImageUriField
+from custom_fields import EmailField, ColorField, UriField, ImageUriField,\
+    DateTimeField
 from open_event.models.event import Event as EventModel, EventsUsers
 from open_event.models.user import ADMIN, SUPERADMIN
 from .helpers import get_object_list, get_object_or_404, get_paginated_list,\
     requires_auth
-from utils import PAGINATED_MODEL, PaginatedResourceBase, PAGE_PARAMS, POST_RESPONSES
+from utils import PAGINATED_MODEL, PaginatedResourceBase, PAGE_PARAMS,\
+    POST_RESPONSES
 from open_event.helpers.data import save_to_db, update_version, delete_from_db
 
 api = Namespace('events', description='Events')
@@ -17,8 +19,8 @@
     'email': EmailField(),
     'color': ColorField(),
     'logo': ImageUriField(),
-    'start_time': fields.DateTime,
-    'end_time': fields.DateTime,
+    'start_time': DateTimeField(),
+    'end_time': DateTimeField(),
     'latitude': fields.Float,
     'longitude': fields.Float,
     'event_url': UriField(),
@@ -72,6 +74,10 @@ def get(self):
     def post(self):
         """Create an event"""
         new_event = EventModel(**self.api.payload)
+        # convert date-time values from string to datetime
+        new_event.start_time = EVENT_POST['start_time'].from_str(new_event.start_time)
+        new_event.end_time = EVENT_POST['end_time'].from_str(new_event.end_time)
+        # set user (owner)
         a = EventsUsers()
         a.user = g.user
         a.editor = True
diff --git a/open_event/api/sessions.py b/open_event/api/sessions.py
--- a/open_event/api/sessions.py
+++ b/open_event/api/sessions.py
@@ -9,6 +9,7 @@
 from open_event.models.speaker import Speaker as SpeakerModel
 
 from .helpers import get_paginated_list, requires_auth, save_db_model
+from custom_fields import DateTimeField
 from utils import PAGINATED_MODEL, PaginatedResourceBase, ServiceDAO, \
     PAGE_PARAMS, POST_RESPONSES
 
@@ -52,8 +53,8 @@
     'subtitle': fields.String,
     'abstract': fields.String,
     'description': fields.String,
-    'start_time': fields.DateTime,
-    'end_time': fields.DateTime,
+    'start_time': DateTimeField(),
+    'end_time': DateTimeField(),
     'track': fields.Nested(SESSION_TRACK),
     'speakers': fields.List(fields.Nested(SESSION_SPEAKER)),
     'level': fields.Nested(SESSION_LEVEL),
@@ -92,6 +93,8 @@ def create(self, event_id, data):
         data['format'] = FormatModel.query.get(data['format_id'])
         data['microlocation'] = MicrolocationModel.query.get(data['microlocation_id'])
         data['event_id'] = event_id
+        data['start_time'] = SESSION_POST['start_time'].from_str(data['start_time'])
+        data['end_time'] = SESSION_POST['end_time'].from_str(data['end_time'])
         speakers = data['speaker_ids']
         del data['speaker_ids']
         del data['track_id']


Test patch:
diff --git a/tests/api/test_post_api.py b/tests/api/test_post_api.py
--- a/tests/api/test_post_api.py
+++ b/tests/api/test_post_api.py
@@ -39,10 +39,6 @@ def _test_model(self, name, data):
             headers={'content-type': 'application/json'}
         )
         self.assertEqual(401, response.status_code, msg=response.data)
-        # TODO: has some issues with datetime and sqlite
-        # so return in event and session
-        if name in ['event', 'session']:
-            return
         # login and send the request again
         self._login_user()
         response = self.app.post(
@@ -72,7 +68,6 @@ def test_format_api(self):
     def test_language_api(self):
         self._test_model('language', POST_LANGUAGE_DATA)
 
-    # TODO: has some issues with datetime and sqlite
     def test_session_api(self):
         self._test_model('session', POST_SESSION_DATA)
 
diff --git a/tests/api/test_post_api_auth.py b/tests/api/test_post_api_auth.py
--- a/tests/api/test_post_api_auth.py
+++ b/tests/api/test_post_api_auth.py
@@ -36,8 +36,8 @@ def _test_model(self, name, data):
         self.assertEqual(response.status_code, 200)
         self.assertIn('Test' + str(name).title(), response.data)
 
-    # def test_event_api(self):
-    #     self._test_model('event', POST_EVENT_DATA)
+    def test_event_api(self):
+        self._test_model('event', POST_EVENT_DATA)
 
     def test_track_api(self):
         self._test_model('track', POST_TRACK_DATA)
@@ -54,9 +54,8 @@ def test_format_api(self):
     def test_language_api(self):
         self._test_model('language', POST_LANGUAGE_DATA)
 
-    # TODO: has some issues with datetime and sqlite
-    # def test_session_api(self):
-    #     self._test_model('session', POST_SESSION_DATA)
+    def test_session_api(self):
+        self._test_model('session', POST_SESSION_DATA)
 
     def test_speaker_api(self):
         self._test_model('speaker', POST_SPEAKER_DATA)
diff --git a/tests/api/utils_post_data.py b/tests/api/utils_post_data.py
--- a/tests/api/utils_post_data.py
+++ b/tests/api/utils_post_data.py
@@ -5,7 +5,7 @@
 POST_EVENT_DATA = {
     "color": "red",
     "email": "event@gmail.com",
-    "end_time": "2016-05-30T12:12:43.891Z",
+    "end_time": "2016-05-30 12:12:43",
     "latitude": 0,
     "location_name": "TestEvent",
     "logo": "http://imgur.com/image.png",
@@ -14,7 +14,7 @@
     'event_url': "http://site.com",
     'background_url': "http://imgur.com/image.png",
     'description': "blah blah",
-    "start_time": "2016-05-25T12:12:43.891Z",
+    "start_time": "2016-05-25 12:12:43",
 }
 
 POST_FORMAT_DATA = {
@@ -44,13 +44,13 @@
 POST_SESSION_DATA = {
     "abstract": "TestSession",
     "description": "TestSession",
-    "end_time": "2016-05-30T08:47:37.410Z",
+    "end_time": "2016-05-30 09:47:37",
     "format_id": 1,
     "language_id": 1,
     "level_id": 1,
     "microlocation_id": 1,
-    "speaker_ids": [1],
-    "start_time": "2016-05-25T08:47:37.410Z",
+    "speaker_ids": [],
+    "start_time": "2016-05-30 08:47:37",
     "subtitle": "TestSession",
     "title": "TestSession",
     "track_id": 1