From 2fab94534063daf8fe7f8166908b2dadc517413c Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Thu, 13 Jun 2013 10:20:43 -0400 Subject: [PATCH 01/10] Cherry pick change to url matching regular expression. Fixes LMS-37 --- requirements/edx/github.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 8f3d4594ac..641258329d 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -3,7 +3,7 @@ # Third-party: -e git://github.com/edx/django-staticfiles.git@6d2504e5c8#egg=django-staticfiles -e git://github.com/edx/django-pipeline.git#egg=django-pipeline --e git://github.com/edx/django-wiki.git@e2e84558#egg=django-wiki +-e git://github.com/edx/django-wiki.git@ac906abe#egg=django-wiki -e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev -e git://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk From 48ee275799bf8266620f12b5a8cb0825e6a38438 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Mon, 17 Jun 2013 09:37:43 -0400 Subject: [PATCH 02/10] Fix a few remaining tz naive dates And remove redundant but != date parsing methods. In process make the general parsing function less lenient (don't default date nor month) --- CHANGELOG.rst | 3 ++ common/lib/xmodule/xmodule/course_module.py | 16 ++++--- common/lib/xmodule/xmodule/fields.py | 21 +++++++--- common/lib/xmodule/xmodule/timeinfo.py | 6 +-- common/lib/xmodule/xmodule/timeparse.py | 46 --------------------- 5 files changed, 32 insertions(+), 60 deletions(-) delete mode 100644 common/lib/xmodule/xmodule/timeparse.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbbb023527..d78d1c4cb6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -34,6 +34,9 @@ Blades: Staff debug info is now accessible for Graphical Slider Tool problems. Blades: For Video Alpha the events ready, play, pause, seek, and speed change are logged on the server (in the logs). +Common: all dates and times are not time zone aware datetimes. No code should create or use struct_times nor naive +datetimes. + Common: Developers can now have private Django settings files. Common: Safety code added to prevent anything above the vertical level in the diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index d0333cbe36..5dff0e1a20 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -10,7 +10,6 @@ import dateutil.parser from xmodule.modulestore import Location from xmodule.seq_module import SequenceDescriptor, SequenceModule -from xmodule.timeparse import parse_time from xmodule.util.decorators import lazyproperty from xmodule.graders import grader_from_conf import json @@ -645,8 +644,11 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): def start_date_text(self): def try_parse_iso_8601(text): try: - result = datetime.strptime(text, "%Y-%m-%dT%H:%M") - result = result.strftime("%b %d, %Y") + result = Date().from_json(text) + if result is None: + result = text.title() + else: + result = result.strftime("%b %d, %Y") except ValueError: result = text.title() @@ -670,8 +672,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): @property def forum_posts_allowed(self): + datestandin = Date() try: - blackout_periods = [(parse_time(start), parse_time(end)) + blackout_periods = [(datestandin.from_json(start), + datestandin.from_json(end)) for start, end in self.discussion_blackouts] now = datetime.now(UTC()) @@ -701,7 +705,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): if self.last_eligible_appointment_date is None: raise ValueError("Last appointment date must be specified") self.registration_start_date = (self._try_parse_time('Registration_Start_Date') or - datetime.utcfromtimestamp(0)) + datetime.fromtimestamp(0, UTC())) self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date # do validation within the exam info: if self.registration_start_date > self.registration_end_date: @@ -720,7 +724,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): """ if key in self.exam_info: try: - return parse_time(self.exam_info[key]) + return Date().from_json(self.exam_info[key]) except ValueError as e: msg = "Exam {0} in course {1} loaded with a bad exam_info key '{2}': '{3}'".format(self.exam_name, self.course_id, self.exam_info[key], e) log.warning(msg) diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index 963b70204e..6dcab120e4 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -6,7 +6,7 @@ from xblock.core import ModelType import datetime import dateutil.parser -from django.utils.timezone import UTC +from pytz import UTC log = logging.getLogger(__name__) @@ -15,6 +15,10 @@ class Date(ModelType): ''' Date fields know how to parse and produce json (iso) compatible formats. Converts to tz aware datetimes. ''' + # See note below about not defaulting these + CURRENT_YEAR = datetime.datetime.now(UTC).year + DEFAULT_DATE0 = datetime.datetime(CURRENT_YEAR, 1, 1, tzinfo=UTC) + DEFAULT_DATE1 = datetime.datetime(CURRENT_YEAR, 2, 2, tzinfo=UTC) def from_json(self, field): """ Parse an optional metadata key containing a time: if present, complain @@ -26,14 +30,21 @@ class Date(ModelType): elif field is "": return None elif isinstance(field, basestring): - result = dateutil.parser.parse(field) + # It's not trivial to replace dateutil b/c parsing timezones as Z, +03:30, -400 is hard in python + # however, we don't want dateutil to default the month or day (but some tests at least expect + # us to default year); so, we'll see if dateutil uses the defaults for these the hard way + result = dateutil.parser.parse(field, default=self.DEFAULT_DATE0) + result_other = dateutil.parser.parse(field, default=self.DEFAULT_DATE1) + if result != result_other: + log.warning("Field {0} is missing year, month, or day".format(self._name, field)) + return None if result.tzinfo is None: - result = result.replace(tzinfo=UTC()) + result = result.replace(tzinfo=UTC) return result elif isinstance(field, (int, long, float)): - return datetime.datetime.fromtimestamp(field / 1000, UTC()) + return datetime.datetime.fromtimestamp(field / 1000, UTC) elif isinstance(field, time.struct_time): - return datetime.datetime.fromtimestamp(time.mktime(field), UTC()) + return datetime.datetime.fromtimestamp(time.mktime(field), UTC) elif isinstance(field, datetime.datetime): return field else: diff --git a/common/lib/xmodule/xmodule/timeinfo.py b/common/lib/xmodule/xmodule/timeinfo.py index 9a63c0477d..bd29c938e3 100644 --- a/common/lib/xmodule/xmodule/timeinfo.py +++ b/common/lib/xmodule/xmodule/timeinfo.py @@ -1,6 +1,5 @@ -from .timeparse import parse_timedelta - import logging +from xmodule.fields import Date log = logging.getLogger(__name__) class TimeInfo(object): @@ -14,6 +13,7 @@ class TimeInfo(object): self.close_date - the real due date """ + _date_standin = Date() def __init__(self, due_date, grace_period_string): if due_date is not None: self.display_due_date = due_date @@ -23,7 +23,7 @@ class TimeInfo(object): if grace_period_string is not None and self.display_due_date: try: - self.grace_period = parse_timedelta(grace_period_string) + self.grace_period = self._date_standin.from_json(grace_period_string) self.close_date = self.display_due_date + self.grace_period except: log.error("Error parsing the grace period {0}".format(grace_period_string)) diff --git a/common/lib/xmodule/xmodule/timeparse.py b/common/lib/xmodule/xmodule/timeparse.py deleted file mode 100644 index b189262761..0000000000 --- a/common/lib/xmodule/xmodule/timeparse.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Helper functions for handling time in the format we like. -""" -import re -from datetime import timedelta, datetime - -TIME_FORMAT = "%Y-%m-%dT%H:%M" - -TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) hour(?:s?))?(\s)?((?P\d+?) minute(?:s)?)?(\s)?((?P\d+?) second(?:s)?)?$') - -def parse_time(time_str): - """ - Takes a time string in TIME_FORMAT - - Returns it as a time_struct. - - Raises ValueError if the string is not in the right format. - """ - return datetime.strptime(time_str, TIME_FORMAT) - - -def stringify_time(dt): - """ - Convert a datetime struct to a string - """ - return dt.isoformat() - -def parse_timedelta(time_str): - """ - time_str: A string with the following components: - day[s] (optional) - hour[s] (optional) - minute[s] (optional) - second[s] (optional) - - Returns a datetime.timedelta parsed from the string - """ - parts = TIMEDELTA_REGEX.match(time_str) - if not parts: - return - parts = parts.groupdict() - time_params = {} - for (name, param) in parts.iteritems(): - if param: - time_params[name] = int(param) - return timedelta(**time_params) From cb40e9ff88b294ccdeb60e0598fd81ad469b5ef1 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Mon, 17 Jun 2013 09:37:43 -0400 Subject: [PATCH 03/10] Fix a few remaining tz naive dates And remove redundant but != date parsing methods. In process make the general parsing function less lenient (don't default date nor month) --- CHANGELOG.rst | 3 ++ common/lib/xmodule/xmodule/course_module.py | 16 ++++--- common/lib/xmodule/xmodule/fields.py | 21 +++++++-- .../lib/xmodule/xmodule/tests/test_fields.py | 11 +++++ common/lib/xmodule/xmodule/timeinfo.py | 6 +-- common/lib/xmodule/xmodule/timeparse.py | 46 ------------------- 6 files changed, 43 insertions(+), 60 deletions(-) delete mode 100644 common/lib/xmodule/xmodule/timeparse.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbbb023527..d78d1c4cb6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -34,6 +34,9 @@ Blades: Staff debug info is now accessible for Graphical Slider Tool problems. Blades: For Video Alpha the events ready, play, pause, seek, and speed change are logged on the server (in the logs). +Common: all dates and times are not time zone aware datetimes. No code should create or use struct_times nor naive +datetimes. + Common: Developers can now have private Django settings files. Common: Safety code added to prevent anything above the vertical level in the diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index d0333cbe36..5dff0e1a20 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -10,7 +10,6 @@ import dateutil.parser from xmodule.modulestore import Location from xmodule.seq_module import SequenceDescriptor, SequenceModule -from xmodule.timeparse import parse_time from xmodule.util.decorators import lazyproperty from xmodule.graders import grader_from_conf import json @@ -645,8 +644,11 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): def start_date_text(self): def try_parse_iso_8601(text): try: - result = datetime.strptime(text, "%Y-%m-%dT%H:%M") - result = result.strftime("%b %d, %Y") + result = Date().from_json(text) + if result is None: + result = text.title() + else: + result = result.strftime("%b %d, %Y") except ValueError: result = text.title() @@ -670,8 +672,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): @property def forum_posts_allowed(self): + datestandin = Date() try: - blackout_periods = [(parse_time(start), parse_time(end)) + blackout_periods = [(datestandin.from_json(start), + datestandin.from_json(end)) for start, end in self.discussion_blackouts] now = datetime.now(UTC()) @@ -701,7 +705,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): if self.last_eligible_appointment_date is None: raise ValueError("Last appointment date must be specified") self.registration_start_date = (self._try_parse_time('Registration_Start_Date') or - datetime.utcfromtimestamp(0)) + datetime.fromtimestamp(0, UTC())) self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date # do validation within the exam info: if self.registration_start_date > self.registration_end_date: @@ -720,7 +724,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): """ if key in self.exam_info: try: - return parse_time(self.exam_info[key]) + return Date().from_json(self.exam_info[key]) except ValueError as e: msg = "Exam {0} in course {1} loaded with a bad exam_info key '{2}': '{3}'".format(self.exam_name, self.course_id, self.exam_info[key], e) log.warning(msg) diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index 963b70204e..e4e0a19d34 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -6,7 +6,7 @@ from xblock.core import ModelType import datetime import dateutil.parser -from django.utils.timezone import UTC +from pytz import UTC log = logging.getLogger(__name__) @@ -15,6 +15,10 @@ class Date(ModelType): ''' Date fields know how to parse and produce json (iso) compatible formats. Converts to tz aware datetimes. ''' + # See note below about not defaulting these + CURRENT_YEAR = datetime.datetime.now(UTC).year + DEFAULT_DATE0 = datetime.datetime(CURRENT_YEAR, 1, 1, tzinfo=UTC) + DEFAULT_DATE1 = datetime.datetime(CURRENT_YEAR, 2, 2, tzinfo=UTC) def from_json(self, field): """ Parse an optional metadata key containing a time: if present, complain @@ -26,14 +30,21 @@ class Date(ModelType): elif field is "": return None elif isinstance(field, basestring): - result = dateutil.parser.parse(field) + # It's not trivial to replace dateutil b/c parsing timezones as Z, +03:30, -400 is hard in python + # however, we don't want dateutil to default the month or day (but some tests at least expect + # us to default year); so, we'll see if dateutil uses the defaults for these the hard way + result = dateutil.parser.parse(field, default=self.DEFAULT_DATE0) + result_other = dateutil.parser.parse(field, default=self.DEFAULT_DATE1) + if result != result_other: + log.warning("Field {0} is missing month or day".format(self._name, field)) + return None if result.tzinfo is None: - result = result.replace(tzinfo=UTC()) + result = result.replace(tzinfo=UTC) return result elif isinstance(field, (int, long, float)): - return datetime.datetime.fromtimestamp(field / 1000, UTC()) + return datetime.datetime.fromtimestamp(field / 1000, UTC) elif isinstance(field, time.struct_time): - return datetime.datetime.fromtimestamp(time.mktime(field), UTC()) + return datetime.datetime.fromtimestamp(time.mktime(field), UTC) elif isinstance(field, datetime.datetime): return field else: diff --git a/common/lib/xmodule/xmodule/tests/test_fields.py b/common/lib/xmodule/xmodule/tests/test_fields.py index 884f218d6d..ae3a15ab88 100644 --- a/common/lib/xmodule/xmodule/tests/test_fields.py +++ b/common/lib/xmodule/xmodule/tests/test_fields.py @@ -3,6 +3,7 @@ import datetime import unittest from django.utils.timezone import UTC from xmodule.fields import Date, Timedelta +from xmodule.timeinfo import TimeInfo class DateTest(unittest.TestCase): @@ -52,6 +53,7 @@ class DateTest(unittest.TestCase): self.assertEqual( datetime.datetime(current.year, 12, 4, 16, 30, tzinfo=UTC()), DateTest.date.from_json("December 4 16:30")) + self.assertIsNone(DateTest.date.from_json("12 12:00")) def test_to_json(self): ''' @@ -90,3 +92,12 @@ class TimedeltaTest(unittest.TestCase): '1 days 46799 seconds', TimedeltaTest.delta.to_json(datetime.timedelta(days=1, hours=12, minutes=59, seconds=59)) ) + + +class TimeInfoTest(unittest.TestCase): + def test_time_info(self): + due_date = datetime.datetime(2000, 4, 14, 10, tzinfo=UTC()) + grace_pd_string = '1 day 12 hours 59 minutes 59 seconds' + timeinfo = TimeInfo(due_date, grace_pd_string) + self.assertEqual(timeinfo.close_date, + due_date + Timedelta().from_json(grace_pd_string)) diff --git a/common/lib/xmodule/xmodule/timeinfo.py b/common/lib/xmodule/xmodule/timeinfo.py index 9a63c0477d..8f4d99506a 100644 --- a/common/lib/xmodule/xmodule/timeinfo.py +++ b/common/lib/xmodule/xmodule/timeinfo.py @@ -1,6 +1,5 @@ -from .timeparse import parse_timedelta - import logging +from xmodule.fields import Timedelta log = logging.getLogger(__name__) class TimeInfo(object): @@ -14,6 +13,7 @@ class TimeInfo(object): self.close_date - the real due date """ + _delta_standin = Timedelta() def __init__(self, due_date, grace_period_string): if due_date is not None: self.display_due_date = due_date @@ -23,7 +23,7 @@ class TimeInfo(object): if grace_period_string is not None and self.display_due_date: try: - self.grace_period = parse_timedelta(grace_period_string) + self.grace_period = TimeInfo._delta_standin.from_json(grace_period_string) self.close_date = self.display_due_date + self.grace_period except: log.error("Error parsing the grace period {0}".format(grace_period_string)) diff --git a/common/lib/xmodule/xmodule/timeparse.py b/common/lib/xmodule/xmodule/timeparse.py deleted file mode 100644 index b189262761..0000000000 --- a/common/lib/xmodule/xmodule/timeparse.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Helper functions for handling time in the format we like. -""" -import re -from datetime import timedelta, datetime - -TIME_FORMAT = "%Y-%m-%dT%H:%M" - -TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) hour(?:s?))?(\s)?((?P\d+?) minute(?:s)?)?(\s)?((?P\d+?) second(?:s)?)?$') - -def parse_time(time_str): - """ - Takes a time string in TIME_FORMAT - - Returns it as a time_struct. - - Raises ValueError if the string is not in the right format. - """ - return datetime.strptime(time_str, TIME_FORMAT) - - -def stringify_time(dt): - """ - Convert a datetime struct to a string - """ - return dt.isoformat() - -def parse_timedelta(time_str): - """ - time_str: A string with the following components: - day[s] (optional) - hour[s] (optional) - minute[s] (optional) - second[s] (optional) - - Returns a datetime.timedelta parsed from the string - """ - parts = TIMEDELTA_REGEX.match(time_str) - if not parts: - return - parts = parts.groupdict() - time_params = {} - for (name, param) in parts.iteritems(): - if param: - time_params[name] = int(param) - return timedelta(**time_params) From 9d464701f90dd3089bf78f9c7a7856ea90d139bb Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Mon, 17 Jun 2013 14:04:12 -0400 Subject: [PATCH 04/10] timezone conversion tests --- common/lib/xmodule/xmodule/tests/test_fields.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/lib/xmodule/xmodule/tests/test_fields.py b/common/lib/xmodule/xmodule/tests/test_fields.py index ae3a15ab88..facf60147e 100644 --- a/common/lib/xmodule/xmodule/tests/test_fields.py +++ b/common/lib/xmodule/xmodule/tests/test_fields.py @@ -4,6 +4,7 @@ import unittest from django.utils.timezone import UTC from xmodule.fields import Date, Timedelta from xmodule.timeinfo import TimeInfo +import time class DateTest(unittest.TestCase): @@ -55,6 +56,14 @@ class DateTest(unittest.TestCase): DateTest.date.from_json("December 4 16:30")) self.assertIsNone(DateTest.date.from_json("12 12:00")) + def test_odd_from_json(self): + now = datetime.datetime.now(UTC()) + delta = now - datetime.datetime.fromtimestamp(0, UTC()) + self.assertEqual(DateTest.date.from_json(delta.total_seconds() * 1000), + now) + yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=-1) + self.assertEqual(DateTest.date.from_json(yesterday), yesterday) + def test_to_json(self): ''' Test converting time reprs to iso dates From 085a590bdd94c8473bd10139dc587cc6d50e70e0 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Mon, 17 Jun 2013 14:48:18 -0400 Subject: [PATCH 05/10] timezone conversion: more unit tests and code reorganization --- cms/djangoapps/contentstore/tests/tests.py | 22 ++++++++++++++++ common/lib/xmodule/xmodule/fields.py | 29 ++++++++++++++-------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index f769652493..f7f330f91e 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -3,6 +3,10 @@ from django.core.urlresolvers import reverse from .utils import parse_json, user, registration from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from contentstore.tests.test_course_settings import CourseTestCase +from xmodule.modulestore.tests.factories import CourseFactory +import datetime +from pytz import UTC class ContentStoreTestCase(ModuleStoreTestCase): @@ -162,3 +166,21 @@ class AuthTestCase(ContentStoreTestCase): self.assertEqual(resp.status_code, 302) # Logged in should work. + + +class ForumTestCase(CourseTestCase): + def setUp(self): + """ Creates the test course. """ + super(ForumTestCase, self).setUp() + self.course = CourseFactory.create(org='testX', number='727', display_name='Forum Course') + + def test_blackouts(self): + now = datetime.datetime.now(UTC) + self.course.discussion_blackouts = [(t.isoformat(), t2.isoformat()) for t, t2 in + [(now - datetime.timedelta(days=14), now - datetime.timedelta(days=11)), + (now + datetime.timedelta(days=24), now + datetime.timedelta(days=30))]] + self.assertTrue(self.course.forum_posts_allowed) + self.course.discussion_blackouts = [(t.isoformat(), t2.isoformat()) for t, t2 in + [(now - datetime.timedelta(days=14), now + datetime.timedelta(days=2)), + (now + datetime.timedelta(days=24), now + datetime.timedelta(days=30))]] + self.assertFalse(self.course.forum_posts_allowed) diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index e4e0a19d34..a36934a24d 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -19,6 +19,23 @@ class Date(ModelType): CURRENT_YEAR = datetime.datetime.now(UTC).year DEFAULT_DATE0 = datetime.datetime(CURRENT_YEAR, 1, 1, tzinfo=UTC) DEFAULT_DATE1 = datetime.datetime(CURRENT_YEAR, 2, 2, tzinfo=UTC) + def _parse_date_wo_default_month_day(self, field): + """ + Parse the field as an iso string but prevent dateutils from defaulting the day or month while + allowing it to default the other fields. + """ + # It's not trivial to replace dateutil b/c parsing timezones as Z, +03:30, -400 is hard in python + # however, we don't want dateutil to default the month or day (but some tests at least expect + # us to default year); so, we'll see if dateutil uses the defaults for these the hard way + result = dateutil.parser.parse(field, default=self.DEFAULT_DATE0) + result_other = dateutil.parser.parse(field, default=self.DEFAULT_DATE1) + if result != result_other: + log.warning("Field {0} is missing month or day".format(self._name, field)) + return None + if result.tzinfo is None: + result = result.replace(tzinfo=UTC) + return result + def from_json(self, field): """ Parse an optional metadata key containing a time: if present, complain @@ -30,17 +47,7 @@ class Date(ModelType): elif field is "": return None elif isinstance(field, basestring): - # It's not trivial to replace dateutil b/c parsing timezones as Z, +03:30, -400 is hard in python - # however, we don't want dateutil to default the month or day (but some tests at least expect - # us to default year); so, we'll see if dateutil uses the defaults for these the hard way - result = dateutil.parser.parse(field, default=self.DEFAULT_DATE0) - result_other = dateutil.parser.parse(field, default=self.DEFAULT_DATE1) - if result != result_other: - log.warning("Field {0} is missing month or day".format(self._name, field)) - return None - if result.tzinfo is None: - result = result.replace(tzinfo=UTC) - return result + return self._parse_date_wo_default_month_day(field) elif isinstance(field, (int, long, float)): return datetime.datetime.fromtimestamp(field / 1000, UTC) elif isinstance(field, time.struct_time): From c1dc2628a011a236db7c9925cd320cb342199593 Mon Sep 17 00:00:00 2001 From: pdehaye Date: Tue, 18 Jun 2013 00:52:23 +0200 Subject: [PATCH 06/10] need to import os --- cms/envs/dev.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cms/envs/dev.py b/cms/envs/dev.py index 6327d442d2..07630bdf31 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -172,6 +172,7 @@ MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True # If there's an environment variable set, grab it and turn on Segment.io # Note that this is the Studio key. There is a separate key for the LMS. +import os SEGMENT_IO_KEY = os.environ.get('SEGMENT_IO_KEY') if SEGMENT_IO_KEY: MITX_FEATURES['SEGMENT_IO'] = True From c129fa4a776817d19f60ed3b0a6fee878f7220ca Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Tue, 18 Jun 2013 11:15:05 -0400 Subject: [PATCH 07/10] remove assets.js from the PIPELINE_JS since it seems to cause that file to be loaded twice --- cms/envs/common.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 8551a56c41..7b9c0c52e4 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -238,8 +238,7 @@ PIPELINE_JS = { ) + ['js/hesitate.js', 'js/base.js', 'js/models/feedback.js', 'js/views/feedback.js', 'js/models/section.js', 'js/views/section.js', - 'js/models/metadata_model.js', 'js/views/metadata_editor_view.js', - 'js/views/assets.js'], + 'js/models/metadata_model.js', 'js/views/metadata_editor_view.js'], 'output_filename': 'js/cms-application.js', 'test_order': 0 }, From 54cb64a2c6708db61aaa1f05621860f60d709a9b Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Tue, 18 Jun 2013 11:37:50 -0400 Subject: [PATCH 08/10] actually, seems like if we remove it from PIPELINE_JS then it doesn't work in production --- cms/envs/common.py | 3 ++- cms/templates/asset_index.html | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 7b9c0c52e4..8551a56c41 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -238,7 +238,8 @@ PIPELINE_JS = { ) + ['js/hesitate.js', 'js/base.js', 'js/models/feedback.js', 'js/views/feedback.js', 'js/models/section.js', 'js/views/section.js', - 'js/models/metadata_model.js', 'js/views/metadata_editor_view.js'], + 'js/models/metadata_model.js', 'js/views/metadata_editor_view.js', + 'js/views/assets.js'], 'output_filename': 'js/cms-application.js', 'test_order': 0 }, diff --git a/cms/templates/asset_index.html b/cms/templates/asset_index.html index e8dc523ba7..0006d29d38 100644 --- a/cms/templates/asset_index.html +++ b/cms/templates/asset_index.html @@ -8,7 +8,6 @@ <%block name="jsextra"> -