'
- ]
- for test in test_responses:
- self.assertContains(response, test)
@attr(shard=1)
@@ -950,8 +960,7 @@ class BaseDueDateTests(ModuleStoreTestCase):
section = ItemFactory.create(
category='sequential',
parent_location=chapter.location,
- due=datetime(2013, 9, 18, 11, 30, 00),
- format='homework'
+ due=datetime(2013, 9, 18, 11, 30, 00)
)
vertical = ItemFactory.create(category='vertical', parent_location=section.location)
ItemFactory.create(category='problem', parent_location=vertical.location)
@@ -966,7 +975,8 @@ class BaseDueDateTests(ModuleStoreTestCase):
self.user = UserFactory.create()
self.assertTrue(self.client.login(username=self.user.username, password='test'))
- self.time_with_tz = "2013-09-18 11:30:00+00:00"
+ self.time_with_tz = "due Sep 18, 2013 at 11:30 UTC"
+ self.time_without_tz = "due Sep 18, 2013 at 11:30"
def test_backwards_compatability(self):
# The test course being used has show_timezone = False in the policy file
@@ -975,7 +985,8 @@ class BaseDueDateTests(ModuleStoreTestCase):
# remove the timezone.
course = self.set_up_course(due_date_display_format=None, show_timezone=False)
response = self.get_response(course)
- self.assertContains(response, self.time_with_tz)
+ self.assertContains(response, self.time_without_tz)
+ self.assertNotContains(response, self.time_with_tz)
# Test that show_timezone has been cleared (which means you get the default value of True).
self.assertTrue(course.show_timezone)
@@ -990,11 +1001,25 @@ class BaseDueDateTests(ModuleStoreTestCase):
response = self.get_response(course)
self.assertContains(response, self.time_with_tz)
+ def test_format_plain_text(self):
+ # plain text due date
+ course = self.set_up_course(due_date_display_format="foobar")
+ response = self.get_response(course)
+ self.assertNotContains(response, self.time_with_tz)
+ self.assertContains(response, "due foobar")
+
def test_format_date(self):
# due date with no time
course = self.set_up_course(due_date_display_format=u"%b %d %y")
response = self.get_response(course)
- self.assertContains(response, self.time_with_tz)
+ self.assertNotContains(response, self.time_with_tz)
+ self.assertContains(response, "due Sep 18 13")
+
+ def test_format_hidden(self):
+ # hide due date completely
+ course = self.set_up_course(due_date_display_format=u"")
+ response = self.get_response(course)
+ self.assertNotContains(response, "due ")
def test_format_invalid(self):
# improperly formatted due_date_display_format falls through to default
@@ -1024,10 +1049,7 @@ class TestAccordionDueDate(BaseDueDateTests):
def get_response(self, course):
""" Returns the HTML for the accordion """
- return self.client.get(
- reverse('courseware', args=[unicode(course.id)]),
- follow=True
- )
+ return self.client.get(reverse('courseware', args=[unicode(course.id)]), follow=True)
@attr(shard=1)
@@ -1067,17 +1089,14 @@ class StartDateTests(ModuleStoreTestCase):
course = self.set_up_course()
response = self.get_about_response(course.id)
# The start date is set in the set_up_course function above.
- # This should return in the format '%Y-%m-%dT%H:%M:%S%z'
- self.assertContains(response, "2013-09-16T07:17:28+0000")
+ self.assertContains(response, "2013-SEPTEMBER-16")
- @patch(
- 'util.date_utils.pgettext',
- fake_pgettext(translations={("abbreviated month name", "Jul"): "JULY", })
- )
- @patch(
- 'util.date_utils.ugettext',
- fake_ugettext(translations={"SHORT_DATE_FORMAT": "%Y-%b-%d", })
- )
+ @patch('util.date_utils.pgettext', fake_pgettext(translations={
+ ("abbreviated month name", "Jul"): "JULY",
+ }))
+ @patch('util.date_utils.ugettext', fake_ugettext(translations={
+ "SHORT_DATE_FORMAT": "%Y-%b-%d",
+ }))
@unittest.skip
def test_format_localized_in_xml_course(self):
response = self.get_about_response(SlashSeparatedCourseKey('edX', 'toy', 'TT_2012_Fall'))
@@ -1326,17 +1345,17 @@ class ProgressPageTests(ModuleStoreTestCase):
"""Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration(enabled=self_paced_enabled).save()
self.setup_course(self_paced=self_paced)
- with self.assertNumQueries(38), check_mongo_calls(4):
+ with self.assertNumQueries(35), check_mongo_calls(4):
self._get_progress_page()
def test_progress_queries(self):
self.setup_course()
- with self.assertNumQueries(38), check_mongo_calls(4):
+ with self.assertNumQueries(35), check_mongo_calls(4):
self._get_progress_page()
# subsequent accesses to the progress page require fewer queries.
for _ in range(2):
- with self.assertNumQueries(24), check_mongo_calls(4):
+ with self.assertNumQueries(21), check_mongo_calls(4):
self._get_progress_page()
@patch(
diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py
index 337ebb57a5..d3fe5938ef 100644
--- a/lms/djangoapps/courseware/views/index.py
+++ b/lms/djangoapps/courseware/views/index.py
@@ -24,6 +24,7 @@ import urllib
from xblock.fragment import Fragment
from opaque_keys.edx.keys import CourseKey
+from openedx.core.lib.time_zone_utils import get_user_time_zone
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from shoppingcart.models import CourseRegistrationCode
@@ -401,6 +402,7 @@ class CoursewareIndex(View):
self.request,
self.course,
table_of_contents['chapters'],
+ courseware_context['language_preference'],
)
# entrance exam data
@@ -497,7 +499,7 @@ class CoursewareIndex(View):
raise
-def render_accordion(request, course, table_of_contents):
+def render_accordion(request, course, table_of_contents, language_preference):
"""
Returns the HTML that renders the navigation for the given course.
Expects the table_of_contents to have data on each chapter and section,
@@ -509,6 +511,9 @@ def render_accordion(request, course, table_of_contents):
('course_id', unicode(course.id)),
('csrf', csrf(request)['csrf_token']),
('due_date_display_format', course.due_date_display_format),
+ ('time_zone', request.user.preferences.model.get_value(request.user, "time_zone", None)),
+ ('language', language_preference),
+
] + TEMPLATE_IMPORTS.items()
)
return render_to_string('courseware/accordion.html', context)
diff --git a/lms/envs/common.py b/lms/envs/common.py
index d6a4c52346..52a9349f7c 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -518,9 +518,6 @@ TEMPLATES = [
# Shoppingcart processor (detects if request.user has a cart)
'shoppingcart.context_processor.user_has_cart_context_processor',
- # Timezone processor (sends language and time_zone preference)
- 'courseware.context_processor.user_timezone_locale_prefs',
-
# Allows the open edX footer to be leveraged in Django Templates.
'edxmako.shortcuts.footer_context_processor',
diff --git a/lms/static/js/dateutil_factory.js b/lms/static/js/dateutil_factory.js
index d0b5b6c6a5..8638d3279a 100644
--- a/lms/static/js/dateutil_factory.js
+++ b/lms/static/js/dateutil_factory.js
@@ -36,8 +36,8 @@
context = {
datetime: $(this).data('datetime'),
timezone: $(this).data('timezone'),
- language: $(this).data('language'),
- format: DateUtils.dateFormatEnum[$(this).data('format')]
+ language: $(this).attr('lang'),
+ format: $(this).data('format')
};
displayDatetime = stringHandler(
localizedTime(context),
@@ -45,11 +45,6 @@
$(this).data('datetoken')
);
$(this).text(displayDatetime);
- } else {
- displayDatetime = stringHandler(
- $(this).data('string')
- );
- $(this).text(displayDatetime);
}
});
};
diff --git a/lms/static/js/spec/dateutil_factory_spec.js b/lms/static/js/spec/dateutil_factory_spec.js
index 9e09e7bd30..852ee51a68 100644
--- a/lms/static/js/spec/dateutil_factory_spec.js
+++ b/lms/static/js/spec/dateutil_factory_spec.js
@@ -35,7 +35,7 @@ define(['../dateutil_factory.js'], function(DateUtilIterator) {
'data-string="Due {date}">'
);
Object.keys(testLangs).forEach(function(key) {
- $form.attr('data-language', String(key));
+ $form.attr('lang', String(key));
$(document.body).append($form);
DateUtilIterator.transform(iterationKey);
diff --git a/lms/static/js/student_account/views/account_settings_factory.js b/lms/static/js/student_account/views/account_settings_factory.js
index c03ac83aba..86fca0e190 100644
--- a/lms/static/js/student_account/views/account_settings_factory.js
+++ b/lms/static/js/student_account/views/account_settings_factory.js
@@ -116,10 +116,9 @@
title: gettext('Time Zone'),
valueAttribute: 'time_zone',
helpMessage: gettext(
- 'Select the time zone for displaying course dates. ' +
- 'If you do not specify a time zone, course dates, ' +
- 'including assignment deadlines, will be displayed ' +
- 'in your browser\'s local time zone.'
+ 'Select the time zone for displaying course dates. If you do not specify a ' +
+ 'time zone here, course dates, including assignment deadlines, are displayed in ' +
+ 'Coordinated Universal Time (UTC).'
),
groupOptions: [{
groupTitle: gettext('All Time Zones'),
diff --git a/lms/templates/course.html b/lms/templates/course.html
index 5ae2774d79..dfc53ef0ba 100644
--- a/lms/templates/course.html
+++ b/lms/templates/course.html
@@ -19,31 +19,14 @@ from django.core.urlresolvers import reverse
${course.display_number_with_default}${course.display_name_with_default}
- <%
- if course.start is not None:
- course_date_string = course.start.strftime('%Y-%m-%dT%H:%M:%S%z')
- else:
- course_date_string = ''
- %>
- % if isinstance(course_start_date, str):
-
${_("Starts")}: ${course_start_date}
- % else:
-
- % endif
-
+
${_("Starts")}: ${course.start_datetime_text()}
+
${course.display_org_with_default}
${course.display_number_with_default}
- %if isinstance(course_start_date, str):
-
${_("Starts")}:
- %else:
-
${_("Starts")}:
- %endif
-
+
${_("Starts")}:
+
-<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
- DateUtilFactory.transform(iterationKey=".localized_datetime");
-%static:require_module_async>
diff --git a/lms/templates/courseware/accordion.html b/lms/templates/courseware/accordion.html
index 2327f028de..621ee43842 100644
--- a/lms/templates/courseware/accordion.html
+++ b/lms/templates/courseware/accordion.html
@@ -32,19 +32,21 @@ else:
span_start=HTML(''),
span_end=HTML(''),
) if 'active' in section and section['active'] else ''}
+ <%
+ if section.get('due') is None:
+ due_date = ''
+ else:
+ formatted_string = get_time_display(section['due'], due_date_display_format, coerce_tz=time_zone)
+ due_date = '' if len(formatted_string)==0 else _('due {date}').format(date=formatted_string)
+ %>
- ## There are behavior differences between
+ ## There is behavior differences between
## rendering of sections which have proctoring/timed examinations
## and those that do not.
##
## Proctoring exposes a exam status message field as well as
## a status icon
- <%
- if section.get('due') is None:
- data_string = ''
- else:
- data_string = _('due {date}')
- %>
+
% if section['format'] or due_date or 'proctoring' in section:
% if 'proctoring' in section:
@@ -55,12 +57,12 @@ else:
## completed proctored exam statuses should not show the due date
## since the exam has already been submitted by the user
% if not section['proctoring'].get('in_completed_state', False):
-
+ ${due_date}
% endif
% else:
## non-proctored section, we just show the exam format and the due date
## this is the standard case in edx-platform
-
+ ${section['format']} ${due_date}
% if 'graded' in section and section['graded']:
@@ -85,8 +87,4 @@ else:
<%static:require_module_async module_name="js/courseware/accordion_events" class_name="AccordionEvents">
AccordionEvents();
%static:require_module_async>
-
-<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
- DateUtilFactory.transform(iterationKey=".localized-datetime");
-%static:require_module_async>
% endif
diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html
index 8d6b0ae5e8..bfa62e86ee 100644
--- a/lms/templates/courseware/course_about.html
+++ b/lms/templates/courseware/course_about.html
@@ -223,40 +223,21 @@ from openedx.core.lib.courses import course_image_url
${_("Course Number")}
${course.display_number_with_default | h}
% if not course.start_date_is_still_default:
- <%
- course_start_date = course.start
- %>
-
% endif
## We plan to ditch end_date (which is not stored in course metadata),
## but for backwards compatibility, show about/end_date blob if it exists.
% if get_course_about_section(request, course, "end_date") or course.end:
- <%
- course_end_date = course.end
- %>
-
@@ -455,7 +439,3 @@ from student.helpers import (
}
});
-
-<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
- DateUtilFactory.transform(iterationKey=".localized-datetime");
-%static:require_module_async>
diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py
index d9cede9f58..4b15b17125 100644
--- a/openedx/core/djangoapps/content/course_overviews/models.py
+++ b/openedx/core/djangoapps/content/course_overviews/models.py
@@ -357,6 +357,7 @@ class CourseOverview(TimeStampedModel):
"""
Returns True if the course starts with-in given number of days otherwise returns False.
"""
+
return course_metadata_utils.course_starts_within(self.start, days)
def start_datetime_text(self, format_string="SHORT_DATE", time_zone=utc):
@@ -388,7 +389,6 @@ class CourseOverview(TimeStampedModel):
def end_datetime_text(self, format_string="SHORT_DATE", time_zone=utc):
"""
Returns the end date or datetime for the course formatted as a string.
-
"""
return course_metadata_utils.course_end_datetime_text(
self.end,
diff --git a/openedx/core/lib/tests/test_time_zone_utils.py b/openedx/core/lib/tests/test_time_zone_utils.py
index 082b46cadd..f1ad4be79a 100644
--- a/openedx/core/lib/tests/test_time_zone_utils.py
+++ b/openedx/core/lib/tests/test_time_zone_utils.py
@@ -1,10 +1,12 @@
"""Tests covering time zone utilities."""
from freezegun import freeze_time
from student.tests.factories import UserFactory
+from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.core.lib.time_zone_utils import (
get_display_time_zone,
get_time_zone_abbr,
get_time_zone_offset,
+ get_user_time_zone,
)
from pytz import timezone, utc
from unittest import TestCase
@@ -23,6 +25,20 @@ class TestTimeZoneUtils(TestCase):
self.user = UserFactory.build()
self.user.save()
+ def test_get_user_time_zone(self):
+ """
+ Test to ensure get_user_time_zone() returns the correct time zone
+ or UTC if user has not specified time zone.
+ """
+ # User time zone should be UTC when no time zone has been chosen
+ user_tz = get_user_time_zone(self.user)
+ self.assertEqual(user_tz, utc)
+
+ # User time zone should change when user specifies time zone
+ set_user_preference(self.user, 'time_zone', 'Asia/Tokyo')
+ user_tz = get_user_time_zone(self.user)
+ self.assertEqual(user_tz, timezone('Asia/Tokyo'))
+
def _display_time_zone_helper(self, time_zone_string):
"""
Helper function to return all info from get_display_time_zone()
diff --git a/openedx/core/lib/time_zone_utils.py b/openedx/core/lib/time_zone_utils.py
index 27bad00626..442d5f7d60 100644
--- a/openedx/core/lib/time_zone_utils.py
+++ b/openedx/core/lib/time_zone_utils.py
@@ -2,9 +2,19 @@
Utilities related to timezones
"""
from datetime import datetime
+
from pytz import common_timezones, timezone, utc
+def get_user_time_zone(user):
+ """
+ Returns pytz time zone object of the user's time zone if available or UTC time zone if unavailable
+ """
+ #TODO: exception for unknown timezones?
+ time_zone = user.preferences.model.get_value(user, "time_zone", 'utc')
+ return timezone(time_zone)
+
+
def _format_time_zone_string(time_zone, date_time, format_string):
"""
Returns a string, specified by format string, of the current date/time of the time zone.