diff --git a/common/djangoapps/track/backends/django.py b/common/djangoapps/track/backends/django.py index 24669dfac2..332a97cae2 100644 --- a/common/djangoapps/track/backends/django.py +++ b/common/djangoapps/track/backends/django.py @@ -11,8 +11,6 @@ from __future__ import absolute_import import logging -import dateutil - from django.db import models from track.backends import BaseBackend @@ -77,7 +75,6 @@ class DjangoBackend(BaseBackend): self.name = name def send(self, event): - event['time'] = dateutil.parser.parse(event['time']) field_values = {x: event.get(x, '') for x in LOGFIELDS} tldat = TrackingLog(**field_values) try: diff --git a/common/djangoapps/track/backends/logger.py b/common/djangoapps/track/backends/logger.py index 56261b1567..511d3019bf 100644 --- a/common/djangoapps/track/backends/logger.py +++ b/common/djangoapps/track/backends/logger.py @@ -8,7 +8,7 @@ import json from django.conf import settings from track.backends import BaseBackend - +from track.utils import DateTimeJSONEncoder log = logging.getLogger('track.backends.logger') @@ -33,7 +33,7 @@ class LoggerBackend(BaseBackend): self.event_logger = logging.getLogger(name) def send(self, event): - event_str = json.dumps(event) + event_str = json.dumps(event, default=DateTimeJSONEncoder) # TODO: remove trucation of the serialized event, either at a # higher level during the emittion of the event, or by diff --git a/common/djangoapps/track/tests/test_util.py b/common/djangoapps/track/tests/test_util.py new file mode 100644 index 0000000000..123dbfbfb0 --- /dev/null +++ b/common/djangoapps/track/tests/test_util.py @@ -0,0 +1,37 @@ +from datetime import datetime +import json + +from pytz import UTC + +from django.test import TestCase + +from track.utils import DateTimeJSONEncoder + + +class TestDateTimeJSONEncoder(TestCase): + def test_datetime_encoding(self): + a_naive_datetime = datetime(2012, 05, 01, 07, 27, 10, 20000) + a_tz_datetime = datetime(2012, 05, 01, 07, 27, 10, 20000, tzinfo=UTC) + a_date = a_naive_datetime.date() + an_iso_datetime = '2012-05-01T07:27:10.020000+00:00' + an_iso_date = '2012-05-01' + + obj = { + 'number': 100, + 'string': 'hello', + 'object': {'a': 1}, + 'a_datetime': a_naive_datetime, + 'a_tz_datetime': a_tz_datetime, + 'a_date': a_date, + } + + to_json = json.dumps(obj, cls=DateTimeJSONEncoder) + from_json = json.loads(to_json) + + self.assertEqual(from_json['number'], 100) + self.assertEqual(from_json['string'], 'hello') + self.assertEqual(from_json['object'], {'a': 1}) + + self.assertEqual(from_json['a_datetime'], an_iso_datetime) + self.assertEqual(from_json['a_tz_datetime'], an_iso_datetime) + self.assertEqual(from_json['a_date'], an_iso_date) diff --git a/common/djangoapps/track/utils.py b/common/djangoapps/track/utils.py new file mode 100644 index 0000000000..05b95ecea3 --- /dev/null +++ b/common/djangoapps/track/utils.py @@ -0,0 +1,30 @@ +"""Utility functions and classes for track backends""" + +from datetime import datetime, date +import json + +from pytz import UTC + + +class DateTimeJSONEncoder(json.JSONEncoder): + """JSON encoder aware of datetime.datetime and datetime.date objects""" + + def default(self, obj): # pylint: disable=method-hidden + """ + Serialize datetime and date objects of iso format. + + datatime objects are converted to UTC. + """ + + if isinstance(obj, datetime): + if obj.tzinfo is None: + # Localize to UTC naive datetime objects + obj = UTC.localize(obj) + else: + # Convert to UTC datetime objects from other timezones + obj = obj.astimezone(UTC) + return obj.isoformat() + elif isinstance(obj, date): + return obj.isoformat() + + return super(DateTimeJSONEncoder, self).default(obj) diff --git a/common/djangoapps/track/views.py b/common/djangoapps/track/views.py index f979b65336..9fe04c7602 100644 --- a/common/djangoapps/track/views.py +++ b/common/djangoapps/track/views.py @@ -1,15 +1,11 @@ -import json -import logging import datetime -import dateutil.parser import pytz from pytz import UTC from django.contrib.auth.decorators import login_required from django.http import HttpResponse from django.shortcuts import redirect -from django.conf import settings from django_future.csrf import ensure_csrf_cookie @@ -56,7 +52,7 @@ def user_track(request): "event": request.REQUEST['event'], "agent": agent, "page": request.REQUEST['page'], - "time": datetime.datetime.now(UTC).isoformat(), + "time": datetime.datetime.now(UTC), "host": request.META['SERVER_NAME'], } @@ -85,7 +81,7 @@ def server_track(request, event_type, event, page=None): "event": event, "agent": agent, "page": page, - "time": datetime.datetime.now(UTC).isoformat(), + "time": datetime.datetime.now(UTC), "host": request.META['SERVER_NAME'], } @@ -130,7 +126,7 @@ def task_track(request_info, task_info, event_type, event, page=None): "event": full_event, "agent": request_info.get('agent', 'unknown'), "page": page, - "time": datetime.datetime.utcnow().isoformat(), + "time": datetime.datetime.now(UTC), "host": request_info.get('host', 'unknown') }