Commit 1c5706fb by Don Mitchell

Add JSON encoder for datetimes to xml export

During policy export, we want to write any python datetime objects found
as iso timestamps, rather than throwing exceptions.
parent 2ef4d829
...@@ -3,7 +3,24 @@ from xmodule.modulestore import Location ...@@ -3,7 +3,24 @@ from xmodule.modulestore import Location
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
from fs.osfs import OSFS from fs.osfs import OSFS
from json import dumps from json import dumps
import json
from json.encoder import JSONEncoder
import datetime
class EdxJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Location):
return obj.url()
elif isinstance(obj, datetime.datetime):
if obj.tzinfo is not None:
if obj.utcoffset() is None:
return obj.isoformat() + 'Z'
else:
return obj.isoformat()
else:
return obj.isoformat()
else:
return super(EdxJSONEncoder, self).default(obj)
def export_to_xml(modulestore, contentstore, course_location, root_dir, course_dir, draft_modulestore=None): def export_to_xml(modulestore, contentstore, course_location, root_dir, course_dir, draft_modulestore=None):
...@@ -35,12 +52,12 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d ...@@ -35,12 +52,12 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
policies_dir = export_fs.makeopendir('policies') policies_dir = export_fs.makeopendir('policies')
course_run_policy_dir = policies_dir.makeopendir(course.location.name) course_run_policy_dir = policies_dir.makeopendir(course.location.name)
with course_run_policy_dir.open('grading_policy.json', 'w') as grading_policy: with course_run_policy_dir.open('grading_policy.json', 'w') as grading_policy:
grading_policy.write(dumps(course.grading_policy)) grading_policy.write(dumps(course.grading_policy, cls=EdxJSONEncoder))
# export all of the course metadata in policy.json # export all of the course metadata in policy.json
with course_run_policy_dir.open('policy.json', 'w') as course_policy: with course_run_policy_dir.open('policy.json', 'w') as course_policy:
policy = {'course/' + course.location.name: own_metadata(course)} policy = {'course/' + course.location.name: own_metadata(course)}
course_policy.write(dumps(policy)) course_policy.write(dumps(policy, cls=EdxJSONEncoder))
# export draft content # export draft content
# NOTE: this code assumes that verticals are the top most draftable container # NOTE: this code assumes that verticals are the top most draftable container
......
import unittest import unittest
import pytz
from datetime import datetime, timedelta, tzinfo
from fs.osfs import OSFS from fs.osfs import OSFS
from mock import Mock
from path import path from path import path
from tempfile import mkdtemp from tempfile import mkdtemp
import shutil import shutil
from xmodule.modulestore.xml import XMLModuleStore from xmodule.modulestore.xml import XMLModuleStore
from xmodule.modulestore.xml_exporter import EdxJSONEncoder
from xmodule.modulestore import Location
# from ~/mitx_all/mitx/common/lib/xmodule/xmodule/tests/ # from ~/mitx_all/mitx/common/lib/xmodule/xmodule/tests/
# to ~/mitx_all/mitx/common/test # to ~/mitx_all/mitx/common/test
...@@ -127,3 +133,58 @@ class RoundTripTestCase(unittest.TestCase): ...@@ -127,3 +133,58 @@ class RoundTripTestCase(unittest.TestCase):
def test_word_cloud_roundtrip(self): def test_word_cloud_roundtrip(self):
self.check_export_roundtrip(DATA_DIR, "word_cloud") self.check_export_roundtrip(DATA_DIR, "word_cloud")
class TestEdxJsonEncoder(unittest.TestCase):
def setUp(self):
self.encoder = EdxJSONEncoder()
class OffsetTZ(tzinfo):
"""A timezone with non-None utcoffset"""
def utcoffset(self, dt):
return timedelta(hours=4)
self.offset_tz = OffsetTZ()
class NullTZ(tzinfo):
"""A timezone with None as its utcoffset"""
def utcoffset(self, dt):
return None
self.null_utc_tz = NullTZ()
def test_encode_location(self):
loc = Location('i4x', 'org', 'course', 'category', 'name')
self.assertEqual(loc.url(), self.encoder.default(loc))
loc = Location('i4x', 'org', 'course', 'category', 'name', 'version')
self.assertEqual(loc.url(), self.encoder.default(loc))
def test_encode_naive_datetime(self):
self.assertEqual(
"2013-05-03T10:20:30.000100",
self.encoder.default(datetime(2013, 5, 3, 10, 20, 30, 100))
)
self.assertEqual(
"2013-05-03T10:20:30",
self.encoder.default(datetime(2013, 5, 3, 10, 20, 30))
)
def test_encode_utc_datetime(self):
self.assertEqual(
"2013-05-03T10:20:30+00:00",
self.encoder.default(datetime(2013, 5, 3, 10, 20, 30, 0, pytz.UTC))
)
self.assertEqual(
"2013-05-03T10:20:30+04:00",
self.encoder.default(datetime(2013, 5, 3, 10, 20, 30, 0, self.offset_tz))
)
self.assertEqual(
"2013-05-03T10:20:30Z",
self.encoder.default(datetime(2013, 5, 3, 10, 20, 30, 0, self.null_utc_tz))
)
def test_other_classes(self):
with self.assertRaises(TypeError):
self.encoder.default(None)
...@@ -10,6 +10,7 @@ from xblock.core import Dict, Scope ...@@ -10,6 +10,7 @@ from xblock.core import Dict, Scope
from xmodule.x_module import (XModuleDescriptor, policy_key) from xmodule.x_module import (XModuleDescriptor, policy_key)
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
from xmodule.modulestore.xml_exporter import EdxJSONEncoder
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -84,7 +85,7 @@ def serialize_field(value): ...@@ -84,7 +85,7 @@ def serialize_field(value):
By default, this is the result of calling json.dumps on the input value. By default, this is the result of calling json.dumps on the input value.
""" """
return json.dumps(value) return json.dumps(value, cls=EdxJSONEncoder)
def deserialize_field(field, value): def deserialize_field(field, value):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment