diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 658a095d14..1f671a2670 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -338,6 +338,10 @@ class CourseFields(object): ]) info_sidebar_name = String(scope=Scope.settings, default='Course Handouts') show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True) + due_date_display_format = String( + help="Format supported by strftime for displaying due dates. Takes precedence over show_timezone.", + scope=Scope.settings, default=None + ) enrollment_domain = String(help="External login method associated with user accounts allowed to register in course", scope=Scope.settings) course_image = String( diff --git a/common/lib/xmodule/xmodule/tests/test_date_utils.py b/common/lib/xmodule/xmodule/tests/test_date_utils.py index 37f30e1b56..f6c1097ea6 100644 --- a/common/lib/xmodule/xmodule/tests/test_date_utils.py +++ b/common/lib/xmodule/xmodule/tests/test_date_utils.py @@ -1,7 +1,7 @@ """Tests for xmodule.util.date_utils""" from nose.tools import assert_equals, assert_false # pylint: disable=E0611 -from xmodule.util.date_utils import get_default_time_display, almost_same_datetime +from xmodule.util.date_utils import get_default_time_display, get_time_display, almost_same_datetime from datetime import datetime, timedelta, tzinfo from pytz import UTC @@ -33,6 +33,28 @@ def test_get_default_time_display_notz(): get_default_time_display(test_time, False)) +def test_get_time_display_return_empty(): + assert_equals("", get_time_display(None)) + test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC) + assert_equals("", get_time_display(test_time, "")) + + +def test_get_time_display(): + test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC) + assert_equals("dummy text", get_time_display(test_time, 'dummy text')) + assert_equals("Mar 12 1992", get_time_display(test_time, '%b %d %Y', True)) + assert_equals("Mar 12 1992 UTC", get_time_display(test_time, '%b %d %Y %Z', False)) + assert_equals("Mar 12 15:03", get_time_display(test_time, '%b %d %H:%M', False)) + + +def test_get_time_pass_through(): + test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC) + assert_equals("Mar 12, 1992 at 15:03 UTC", get_time_display(test_time)) + assert_equals("Mar 12, 1992 at 15:03", get_time_display(test_time, None, False)) + assert_equals("Mar 12, 1992 at 15:03", get_time_display(test_time, "%", False)) + assert_equals("Mar 12, 1992 at 15:03 UTC", get_time_display(test_time, "%", True)) + + # pylint: disable=W0232 class NamelessTZ(tzinfo): """Static timezone for testing""" diff --git a/common/lib/xmodule/xmodule/util/date_utils.py b/common/lib/xmodule/xmodule/util/date_utils.py index 8baa59558b..e032bd1685 100644 --- a/common/lib/xmodule/xmodule/util/date_utils.py +++ b/common/lib/xmodule/xmodule/util/date_utils.py @@ -30,6 +30,25 @@ def get_default_time_display(dt, show_timezone=True): at=_(u"at"), tz=timezone).strip() +def get_time_display(dt, format_string=None, show_timezone=True): + """ + Converts a datetime to a string representation. + + If None is passed in for dt, an empty string will be returned. + If the format_string is None, or if format_string is improperly + formatted, this method will return the value from `get_default_time_display` + (passing in the show_timezone argument). + If the format_string is specified, show_timezone is ignored. + format_string should be a unicode string that is a valid argument for datetime's strftime method. + """ + if dt is None or format_string is None: + return get_default_time_display(dt, show_timezone) + try: + return unicode(dt.strftime(format_string)) + except ValueError: + return get_default_time_display(dt, show_timezone) + + def almost_same_datetime(dt1, dt2, allowed_delta=timedelta(minutes=1)): """ Returns true if these are w/in a minute of each other. (in case secs saved to db diff --git a/common/test/data/due_date/about/overview.html b/common/test/data/due_date/about/overview.html new file mode 100644 index 0000000000..961786b8f4 --- /dev/null +++ b/common/test/data/due_date/about/overview.html @@ -0,0 +1,47 @@ +
+

About This Course

+

Include your long course description here. The long course description should contain 150-400 words.

+ +

This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.

+
+ +
+

Prerequisites

+

Add information about course prerequisites here.

+
+ +
+

Course Staff

+
+
+ +
+ +

Staff Member #1

+

Biography of instructor/staff member #1

+
+ +
+
+ +
+ +

Staff Member #2

+

Biography of instructor/staff member #2

+
+
+ +
+
+

Frequently Asked Questions

+
+

Do I need to buy a textbook?

+

No, a free online version of Chemistry: Principles, Patterns, and Applications, First Edition by Bruce Averill and Patricia Eldredge will be available, though you can purchase a printed version (published by FlatWorld Knowledge) if you’d like.

+
+ +
+

Question #2

+

Your answer would be displayed here.

+
+
+
diff --git a/common/test/data/due_date/chapter/c8ee0db7e5a84c85bac80b7013cf6512.xml b/common/test/data/due_date/chapter/c8ee0db7e5a84c85bac80b7013cf6512.xml new file mode 100644 index 0000000000..cdc5d294f8 --- /dev/null +++ b/common/test/data/due_date/chapter/c8ee0db7e5a84c85bac80b7013cf6512.xml @@ -0,0 +1,3 @@ + + + diff --git a/common/test/data/due_date/course.xml b/common/test/data/due_date/course.xml new file mode 100644 index 0000000000..e355f924b4 --- /dev/null +++ b/common/test/data/due_date/course.xml @@ -0,0 +1 @@ + diff --git a/common/test/data/due_date/course/2013_fall.xml b/common/test/data/due_date/course/2013_fall.xml new file mode 100644 index 0000000000..97ad3760ae --- /dev/null +++ b/common/test/data/due_date/course/2013_fall.xml @@ -0,0 +1,3 @@ + + + diff --git a/common/test/data/due_date/policies/2013_fall/grading_policy.json b/common/test/data/due_date/policies/2013_fall/grading_policy.json new file mode 100644 index 0000000000..272cb4fec6 --- /dev/null +++ b/common/test/data/due_date/policies/2013_fall/grading_policy.json @@ -0,0 +1 @@ +{"GRADER": [{"short_label": "HW", "min_count": 12, "type": "Homework", "drop_count": 2, "weight": 0.15}, {"min_count": 12, "type": "Lab", "drop_count": 2, "weight": 0.15}, {"short_label": "Midterm", "min_count": 1, "type": "Midterm Exam", "drop_count": 0, "weight": 0.3}, {"short_label": "Final", "min_count": 1, "type": "Final Exam", "drop_count": 0, "weight": 0.4}], "GRADE_CUTOFFS": {"Pass": 0.5}} \ No newline at end of file diff --git a/common/test/data/due_date/policies/2013_fall/policy.json b/common/test/data/due_date/policies/2013_fall/policy.json new file mode 100644 index 0000000000..c85f8b35fa --- /dev/null +++ b/common/test/data/due_date/policies/2013_fall/policy.json @@ -0,0 +1 @@ +{"course/2013_fall": {"tabs": [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "textbooks"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}], "display_name": "due_date", "discussion_topics": {"General": {"id": "i4x-edX-due_date-course-2013_fall"}}}} \ No newline at end of file diff --git a/common/test/data/due_date/problem/d392c80f5c044e45a4a5f2d62f94efc5.xml b/common/test/data/due_date/problem/d392c80f5c044e45a4a5f2d62f94efc5.xml new file mode 100644 index 0000000000..4e50ac396b --- /dev/null +++ b/common/test/data/due_date/problem/d392c80f5c044e45a4a5f2d62f94efc5.xml @@ -0,0 +1,23 @@ + +

+A multiple choice problem presents radio buttons for student +input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.

+

One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make. +

+ +

What Apple device competed with the portable CD player?

+ + + The iPad + Napster + The iPod + The vegetable peeler + + + +
+

Explanation

+

The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.

+
+
+
diff --git a/common/test/data/due_date/sequential/c804fa32227142a1bd9d5bc183d4a20d.xml b/common/test/data/due_date/sequential/c804fa32227142a1bd9d5bc183d4a20d.xml new file mode 100644 index 0000000000..a26ed3789f --- /dev/null +++ b/common/test/data/due_date/sequential/c804fa32227142a1bd9d5bc183d4a20d.xml @@ -0,0 +1,3 @@ + + + diff --git a/common/test/data/due_date/vertical/45640305a210424ebcc6f8e045fad0be.xml b/common/test/data/due_date/vertical/45640305a210424ebcc6f8e045fad0be.xml new file mode 100644 index 0000000000..66b6fc546b --- /dev/null +++ b/common/test/data/due_date/vertical/45640305a210424ebcc6f8e045fad0be.xml @@ -0,0 +1,3 @@ + + + diff --git a/lms/djangoapps/courseware/tests/modulestore_config.py b/lms/djangoapps/courseware/tests/modulestore_config.py index 74fd3da57f..9e887855c3 100644 --- a/lms/djangoapps/courseware/tests/modulestore_config.py +++ b/lms/djangoapps/courseware/tests/modulestore_config.py @@ -22,5 +22,6 @@ MAPPINGS = { 'edX/test_about_blob_end_date/2012_Fall': 'xml', 'edX/graded/2012_Fall': 'xml', 'edX/open_ended/2012_Fall': 'xml', + 'edX/due_date/2013_fall': 'xml' } TEST_DATA_MIXED_MODULESTORE = mixed_store_config(TEST_DATA_DIR, MAPPINGS) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index b4e835d2d9..eeb24635d5 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -31,9 +31,8 @@ class TestJumpTo(TestCase): def setUp(self): - # Load toy course from XML + # Use toy course from XML self.course_name = 'edX/toy/2012_Fall' - self.toy_course = modulestore().get_course(self.course_name) def test_jumpto_invalid_location(self): location = Location('i4x', 'edX', 'toy', 'NoSuchPlace', None) @@ -62,7 +61,9 @@ class TestJumpTo(TestCase): self.assertEqual(response.status_code, 404) +@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) class ViewsTestCase(TestCase): + """ Tests for views.py methods. """ def setUp(self): self.user = User.objects.create(username='dummy', password='123456', email='test@mit.edu') @@ -73,8 +74,6 @@ class ViewsTestCase(TestCase): self.enrollment.save() self.location = ['tag', 'org', 'course', 'category', 'name'] - # This is a CourseDescriptor object - self.toy_course = modulestore().get_course('edX/toy/2012_Fall') self.request_factory = RequestFactory() chapter = 'Overview' self.chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) @@ -222,3 +221,85 @@ class ViewsTestCase(TestCase): }) response = self.client.get(url) self.assertFalse(' @@ -69,8 +69,12 @@ ${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph", ${section['format']} %if section.get('due') is not None: + <% + formatted_string = get_time_display(section['due'], course.due_date_display_format, course.show_timezone) + due_date = '' if len(formatted_string)==0 else _('due {date}'.format(date=formatted_string)) + %> - due ${get_default_time_display(section['due'], course.show_timezone)} + ${due_date} %endif