Commit f0cd29f0 by Gregory Martin

Revert "Merge pull request #13915 from edx/revert-13794-yro_implement-dateutil"

This reverts commit d59ab18b, reversing
changes made to 0ebab35e.
parent 8ffc9197
...@@ -10,8 +10,8 @@ from common.test.acceptance.fixtures.course import CourseFixture ...@@ -10,8 +10,8 @@ from common.test.acceptance.fixtures.course import CourseFixture
from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage
from common.test.acceptance.pages.lms.dashboard import DashboardPage from common.test.acceptance.pages.lms.dashboard import DashboardPage
DEFAULT_SHORT_DATE_FORMAT = "%b %d, %Y" DEFAULT_SHORT_DATE_FORMAT = '{dt:%b} {dt.day}, {dt.year}'
DEFAULT_DAY_AND_TIME_FORMAT = "%A at %-I%P" TEST_DATE_FORMAT = '{dt:%b} {dt.day}, {dt.year} {dt.hour}:{dt.minute:02}'
class BaseLmsDashboardTest(UniqueCourseTest): class BaseLmsDashboardTest(UniqueCourseTest):
...@@ -193,7 +193,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest): ...@@ -193,7 +193,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
}) })
self.course_fixture.configure_course() self.course_fixture.configure_course()
end_date = course_end_date.strftime(DEFAULT_SHORT_DATE_FORMAT) end_date = DEFAULT_SHORT_DATE_FORMAT.format(dt=course_end_date)
expected_course_date = "Ended - {end_date}".format(end_date=end_date) expected_course_date = "Ended - {end_date}".format(end_date=end_date)
# reload the page for changes to course date changes to appear in dashboard # reload the page for changes to course date changes to appear in dashboard
...@@ -226,7 +226,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest): ...@@ -226,7 +226,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
}) })
self.course_fixture.configure_course() self.course_fixture.configure_course()
start_date = course_start_date.strftime(DEFAULT_SHORT_DATE_FORMAT) start_date = DEFAULT_SHORT_DATE_FORMAT.format(dt=course_start_date)
expected_course_date = "Started - {start_date}".format(start_date=start_date) expected_course_date = "Started - {start_date}".format(start_date=start_date)
# reload the page for changes to course date changes to appear in dashboard # reload the page for changes to course date changes to appear in dashboard
...@@ -259,7 +259,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest): ...@@ -259,7 +259,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
}) })
self.course_fixture.configure_course() self.course_fixture.configure_course()
start_date = course_start_date.strftime(DEFAULT_SHORT_DATE_FORMAT) start_date = DEFAULT_SHORT_DATE_FORMAT.format(dt=course_start_date)
expected_course_date = "Starts - {start_date}".format(start_date=start_date) expected_course_date = "Starts - {start_date}".format(start_date=start_date)
# reload the page for changes to course date changes to appear in dashboard # reload the page for changes to course date changes to appear in dashboard
...@@ -293,8 +293,8 @@ class LmsDashboardPageTest(BaseLmsDashboardTest): ...@@ -293,8 +293,8 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
}) })
self.course_fixture.configure_course() self.course_fixture.configure_course()
start_date = course_start_date.strftime(DEFAULT_DAY_AND_TIME_FORMAT) start_date = TEST_DATE_FORMAT.format(dt=course_start_date)
expected_course_date = "Starts - {start_date} UTC".format(start_date=start_date) expected_course_date = "Starts - {start_date} GMT".format(start_date=start_date)
# reload the page for changes to course date changes to appear in dashboard # reload the page for changes to course date changes to appear in dashboard
self.dashboard_page.visit() self.dashboard_page.visit()
......
...@@ -229,18 +229,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): ...@@ -229,18 +229,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of sql queries to default, # # of sql queries to default,
# # of mongo queries, # # of mongo queries,
# ) # )
('no_overrides', 1, True, False): (18, 6), ('no_overrides', 1, True, False): (21, 6),
('no_overrides', 2, True, False): (18, 6), ('no_overrides', 2, True, False): (21, 6),
('no_overrides', 3, True, False): (18, 6), ('no_overrides', 3, True, False): (21, 6),
('ccx', 1, True, False): (18, 6), ('ccx', 1, True, False): (21, 6),
('ccx', 2, True, False): (18, 6), ('ccx', 2, True, False): (21, 6),
('ccx', 3, True, False): (18, 6), ('ccx', 3, True, False): (21, 6),
('no_overrides', 1, False, False): (18, 6), ('no_overrides', 1, False, False): (21, 6),
('no_overrides', 2, False, False): (18, 6), ('no_overrides', 2, False, False): (21, 6),
('no_overrides', 3, False, False): (18, 6), ('no_overrides', 3, False, False): (21, 6),
('ccx', 1, False, False): (18, 6), ('ccx', 1, False, False): (21, 6),
('ccx', 2, False, False): (18, 6), ('ccx', 2, False, False): (21, 6),
('ccx', 3, False, False): (18, 6), ('ccx', 3, False, False): (21, 6),
} }
...@@ -252,19 +252,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): ...@@ -252,19 +252,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__ = True __test__ = True
TEST_DATA = { TEST_DATA = {
('no_overrides', 1, True, False): (18, 3), ('no_overrides', 1, True, False): (21, 3),
('no_overrides', 2, True, False): (18, 3), ('no_overrides', 2, True, False): (21, 3),
('no_overrides', 3, True, False): (18, 3), ('no_overrides', 3, True, False): (21, 3),
('ccx', 1, True, False): (18, 3), ('ccx', 1, True, False): (21, 3),
('ccx', 2, True, False): (18, 3), ('ccx', 2, True, False): (21, 3),
('ccx', 3, True, False): (18, 3), ('ccx', 3, True, False): (21, 3),
('ccx', 1, True, True): (19, 3), ('ccx', 1, True, True): (22, 3),
('ccx', 2, True, True): (19, 3), ('ccx', 2, True, True): (22, 3),
('ccx', 3, True, True): (19, 3), ('ccx', 3, True, True): (22, 3),
('no_overrides', 1, False, False): (18, 3), ('no_overrides', 1, False, False): (21, 3),
('no_overrides', 2, False, False): (18, 3), ('no_overrides', 2, False, False): (21, 3),
('no_overrides', 3, False, False): (18, 3), ('no_overrides', 3, False, False): (21, 3),
('ccx', 1, False, False): (18, 3), ('ccx', 1, False, False): (21, 3),
('ccx', 2, False, False): (18, 3), ('ccx', 2, False, False): (21, 3),
('ccx', 3, False, False): (18, 3), ('ccx', 3, False, False): (21, 3),
} }
"""
This is the courseware context_processor module.
This is meant to simplify the process of sending user preferences (espec. time_zone and pref-lang)
to the templates without having to append every view file.
"""
from openedx.core.djangoapps.user_api.errors import UserNotFound, UserAPIInternalError
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
import request_cache
RETRIEVABLE_PREFERENCES = {
'user_timezone': 'time_zone',
'user_language': 'pref-lang'
}
CACHE_NAME = "context_processor.user_timezone_preferences"
def user_timezone_locale_prefs(request):
"""
Checks if request has an authenticated user.
If so, sends set (or none if unset) time_zone and language prefs.
This interacts with the DateUtils to either display preferred or attempt to determine
system/browser set time_zones and languages
"""
cached_value = request_cache.get_cache(CACHE_NAME)
if not cached_value:
user_prefs = {
'user_timezone': None,
'user_language': None,
}
if hasattr(request, 'user') and request.user.is_authenticated():
try:
user_preferences = get_user_preferences(request.user)
except (UserNotFound, UserAPIInternalError):
cached_value.update(user_prefs)
else:
user_prefs = {
key: user_preferences.get(pref_name, None)
for key, pref_name in RETRIEVABLE_PREFERENCES.iteritems()
}
cached_value.update(user_prefs)
return cached_value
...@@ -4,6 +4,7 @@ page. Each block gives information about a particular ...@@ -4,6 +4,7 @@ page. Each block gives information about a particular
course-run-specific date which will be displayed to the user. course-run-specific date which will be displayed to the user.
""" """
from datetime import datetime from datetime import datetime
from pytz import timezone, utc
from babel.dates import format_timedelta from babel.dates import format_timedelta
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -12,13 +13,12 @@ from django.utils.translation import ugettext_lazy ...@@ -12,13 +13,12 @@ from django.utils.translation import ugettext_lazy
from django.utils.translation import to_locale, get_language from django.utils.translation import to_locale, get_language
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from lazy import lazy from lazy import lazy
from pytz import utc
from course_modes.models import CourseMode from course_modes.models import CourseMode
from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.verify_student.models import VerificationDeadline, SoftwareSecurePhotoVerification from lms.djangoapps.verify_student.models import VerificationDeadline, SoftwareSecurePhotoVerification
from openedx.core.lib.time_zone_utils import get_time_zone_abbr, get_user_time_zone
from student.models import CourseEnrollment from student.models import CourseEnrollment
from openedx.core.lib.time_zone_utils import get_time_zone_abbr
class DateSummary(object): class DateSummary(object):
...@@ -67,8 +67,12 @@ class DateSummary(object): ...@@ -67,8 +67,12 @@ class DateSummary(object):
@property @property
def time_zone(self): def time_zone(self):
"""The time zone to display in""" """
return get_user_time_zone(self.user) The time zone in which to display -- defaults to UTC
"""
return timezone(
self.user.preferences.model.get_value(self.user, "time_zone", "UTC")
)
def __init__(self, course, user): def __init__(self, course, user):
self.course = course self.course = course
......
"""
Unit tests for courseware context_processor
"""
from django.contrib.auth.models import AnonymousUser
from mock import Mock
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from courseware.context_processor import user_timezone_locale_prefs
class UserPrefContextProcessorUnitTest(ModuleStoreTestCase):
"""
Unit test for courseware context_processor
"""
def setUp(self):
super(UserPrefContextProcessorUnitTest, self).setUp()
self.user = UserFactory.create()
self.request = Mock()
self.request.user = self.user
def test_anonymous_user(self):
self.request.user = AnonymousUser()
context = user_timezone_locale_prefs(self.request)
self.assertIsNone(context['user_timezone'])
self.assertIsNone(context['user_language'])
def test_no_timezone_preference(self):
set_user_preference(self.user, 'pref-lang', 'en')
context = user_timezone_locale_prefs(self.request)
self.assertIsNone(context['user_timezone'])
self.assertIsNotNone(context['user_language'])
self.assertEqual(context['user_language'], 'en')
def test_no_language_preference(self):
set_user_preference(self.user, 'time_zone', 'Asia/Tokyo')
context = user_timezone_locale_prefs(self.request)
self.assertIsNone(context['user_language'])
self.assertIsNotNone(context['user_timezone'])
self.assertEqual(context['user_timezone'], 'Asia/Tokyo')
...@@ -316,7 +316,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest ...@@ -316,7 +316,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
def test_num_queries_instructor_paced(self): def test_num_queries_instructor_paced(self):
self.fetch_course_info_with_queries(self.instructor_paced_course, 18, 4) self.fetch_course_info_with_queries(self.instructor_paced_course, 21, 4)
def test_num_queries_self_paced(self): def test_num_queries_self_paced(self):
self.fetch_course_info_with_queries(self.self_paced_course, 18, 4) self.fetch_course_info_with_queries(self.self_paced_course, 21, 4)
...@@ -14,7 +14,6 @@ from django.core.urlresolvers import reverse ...@@ -14,7 +14,6 @@ from django.core.urlresolvers import reverse
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory
from util.date_utils import get_time_display, DEFAULT_SHORT_DATE_FORMAT
from course_modes.models import CourseMode from course_modes.models import CourseMode
from openedx.core.djangoapps.credit import api as credit_api from openedx.core.djangoapps.credit import api as credit_api
...@@ -122,7 +121,8 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase): ...@@ -122,7 +121,8 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
response, response,
"{}, you have met the requirements for credit in this course.".format(self.USER_FULL_NAME) "{}, you have met the requirements for credit in this course.".format(self.USER_FULL_NAME)
) )
self.assertContains(response, "Completed by {date}".format(date=self._now_formatted_date())) self.assertContains(response, "Completed by {date}")
self.assertContains(response, datetime.datetime.now(UTC).strftime('%Y-%m-%d %H'))
self.assertNotContains(response, "95%") self.assertNotContains(response, "95%")
def test_credit_requirements_not_eligible(self): def test_credit_requirements_not_eligible(self):
...@@ -172,11 +172,3 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase): ...@@ -172,11 +172,3 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
"""Load the progress page for the course the user is enrolled in. """ """Load the progress page for the course the user is enrolled in. """
url = reverse("progress", kwargs={"course_id": unicode(self.course.id)}) url = reverse("progress", kwargs={"course_id": unicode(self.course.id)})
return self.client.get(url) return self.client.get(url)
def _now_formatted_date(self):
"""Retrieve the formatted current date. """
return get_time_display(
datetime.datetime.now(UTC),
DEFAULT_SHORT_DATE_FORMAT,
settings.TIME_ZONE
)
...@@ -209,6 +209,7 @@ class ViewsTestCase(ModuleStoreTestCase): ...@@ -209,6 +209,7 @@ class ViewsTestCase(ModuleStoreTestCase):
parent_location=self.chapter.location, parent_location=self.chapter.location,
due=datetime(2013, 9, 18, 11, 30, 00), due=datetime(2013, 9, 18, 11, 30, 00),
display_name='Sequential 1', display_name='Sequential 1',
format='Homework'
) )
self.vertical = ItemFactory.create( self.vertical = ItemFactory.create(
category='vertical', category='vertical',
...@@ -907,33 +908,22 @@ class ViewsTestCase(ModuleStoreTestCase): ...@@ -907,33 +908,22 @@ class ViewsTestCase(ModuleStoreTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_accordion(self): def test_accordion(self):
request = RequestFactory().get('foo') """
request.user = self.user This needs a response_context, which is not included in the render_accordion's main method
table_of_contents = toc_for_course( returning a render_to_string, so we will render via the courseware URL in order to include
request.user, the needed context
request, """
self.course,
unicode(self.course.get_children()[0].scope_ids.usage_id),
None,
None
)
# removes newlines and whitespace from the returned view string
view = ''.join(render_accordion(request, self.course, table_of_contents['chapters'], 'en').split())
# the course id unicode is re-encoded here because the quote function does not accept unicode
course_id = quote(unicode(self.course.id).encode("utf-8")) course_id = quote(unicode(self.course.id).encode("utf-8"))
response = self.client.get(
self.assertIn( reverse('courseware', args=[unicode(course_id)]),
u'href="/courses/{}/courseware/Chapter_1/Sequential_1/"><pclass="accordion-display-name">Sequential1</p>' follow=True
.format(course_id.decode("utf-8")),
view
)
self.assertIn(
u'href="/courses/{}/courseware/Chapter_1/Sequential_2/"><pclass="accordion-display-name">Sequential2</p>'
.format(course_id.decode("utf-8")),
view
) )
test_responses = [
'<p class="accordion-display-name">Sequential 1 <span class="sr">current section</span></p>',
'<p class="accordion-display-name">Sequential 2 </p>'
]
for test in test_responses:
self.assertContains(response, test)
@attr(shard=1) @attr(shard=1)
...@@ -960,7 +950,8 @@ class BaseDueDateTests(ModuleStoreTestCase): ...@@ -960,7 +950,8 @@ class BaseDueDateTests(ModuleStoreTestCase):
section = ItemFactory.create( section = ItemFactory.create(
category='sequential', category='sequential',
parent_location=chapter.location, parent_location=chapter.location,
due=datetime(2013, 9, 18, 11, 30, 00) due=datetime(2013, 9, 18, 11, 30, 00),
format='homework'
) )
vertical = ItemFactory.create(category='vertical', parent_location=section.location) vertical = ItemFactory.create(category='vertical', parent_location=section.location)
ItemFactory.create(category='problem', parent_location=vertical.location) ItemFactory.create(category='problem', parent_location=vertical.location)
...@@ -975,8 +966,7 @@ class BaseDueDateTests(ModuleStoreTestCase): ...@@ -975,8 +966,7 @@ class BaseDueDateTests(ModuleStoreTestCase):
self.user = UserFactory.create() self.user = UserFactory.create()
self.assertTrue(self.client.login(username=self.user.username, password='test')) self.assertTrue(self.client.login(username=self.user.username, password='test'))
self.time_with_tz = "due Sep 18, 2013 at 11:30 UTC" self.time_with_tz = "2013-09-18 11:30:00+00:00"
self.time_without_tz = "due Sep 18, 2013 at 11:30"
def test_backwards_compatability(self): def test_backwards_compatability(self):
# The test course being used has show_timezone = False in the policy file # The test course being used has show_timezone = False in the policy file
...@@ -985,8 +975,7 @@ class BaseDueDateTests(ModuleStoreTestCase): ...@@ -985,8 +975,7 @@ class BaseDueDateTests(ModuleStoreTestCase):
# remove the timezone. # remove the timezone.
course = self.set_up_course(due_date_display_format=None, show_timezone=False) course = self.set_up_course(due_date_display_format=None, show_timezone=False)
response = self.get_response(course) response = self.get_response(course)
self.assertContains(response, self.time_without_tz) self.assertContains(response, self.time_with_tz)
self.assertNotContains(response, self.time_with_tz)
# Test that show_timezone has been cleared (which means you get the default value of True). # Test that show_timezone has been cleared (which means you get the default value of True).
self.assertTrue(course.show_timezone) self.assertTrue(course.show_timezone)
...@@ -1001,25 +990,11 @@ class BaseDueDateTests(ModuleStoreTestCase): ...@@ -1001,25 +990,11 @@ class BaseDueDateTests(ModuleStoreTestCase):
response = self.get_response(course) response = self.get_response(course)
self.assertContains(response, self.time_with_tz) 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): def test_format_date(self):
# due date with no time # due date with no time
course = self.set_up_course(due_date_display_format=u"%b %d %y") course = self.set_up_course(due_date_display_format=u"%b %d %y")
response = self.get_response(course) response = self.get_response(course)
self.assertNotContains(response, self.time_with_tz) self.assertContains(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): def test_format_invalid(self):
# improperly formatted due_date_display_format falls through to default # improperly formatted due_date_display_format falls through to default
...@@ -1049,7 +1024,10 @@ class TestAccordionDueDate(BaseDueDateTests): ...@@ -1049,7 +1024,10 @@ class TestAccordionDueDate(BaseDueDateTests):
def get_response(self, course): def get_response(self, course):
""" Returns the HTML for the accordion """ """ 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) @attr(shard=1)
...@@ -1089,14 +1067,17 @@ class StartDateTests(ModuleStoreTestCase): ...@@ -1089,14 +1067,17 @@ class StartDateTests(ModuleStoreTestCase):
course = self.set_up_course() course = self.set_up_course()
response = self.get_about_response(course.id) response = self.get_about_response(course.id)
# The start date is set in the set_up_course function above. # The start date is set in the set_up_course function above.
self.assertContains(response, "2013-SEPTEMBER-16") # This should return in the format '%Y-%m-%dT%H:%M:%S%z'
self.assertContains(response, "2013-09-16T07:17:28+0000")
@patch('util.date_utils.pgettext', fake_pgettext(translations={ @patch(
("abbreviated month name", "Jul"): "JULY", '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.ugettext',
fake_ugettext(translations={"SHORT_DATE_FORMAT": "%Y-%b-%d", })
)
@unittest.skip @unittest.skip
def test_format_localized_in_xml_course(self): def test_format_localized_in_xml_course(self):
response = self.get_about_response(SlashSeparatedCourseKey('edX', 'toy', 'TT_2012_Fall')) response = self.get_about_response(SlashSeparatedCourseKey('edX', 'toy', 'TT_2012_Fall'))
...@@ -1345,17 +1326,17 @@ class ProgressPageTests(ModuleStoreTestCase): ...@@ -1345,17 +1326,17 @@ class ProgressPageTests(ModuleStoreTestCase):
"""Test that query counts remain the same for self-paced and instructor-paced courses.""" """Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration(enabled=self_paced_enabled).save() SelfPacedConfiguration(enabled=self_paced_enabled).save()
self.setup_course(self_paced=self_paced) self.setup_course(self_paced=self_paced)
with self.assertNumQueries(35), check_mongo_calls(4): with self.assertNumQueries(38), check_mongo_calls(4):
self._get_progress_page() self._get_progress_page()
def test_progress_queries(self): def test_progress_queries(self):
self.setup_course() self.setup_course()
with self.assertNumQueries(35), check_mongo_calls(4): with self.assertNumQueries(38), check_mongo_calls(4):
self._get_progress_page() self._get_progress_page()
# subsequent accesses to the progress page require fewer queries. # subsequent accesses to the progress page require fewer queries.
for _ in range(2): for _ in range(2):
with self.assertNumQueries(21), check_mongo_calls(4): with self.assertNumQueries(24), check_mongo_calls(4):
self._get_progress_page() self._get_progress_page()
@patch( @patch(
......
...@@ -24,7 +24,6 @@ import urllib ...@@ -24,7 +24,6 @@ import urllib
from xblock.fragment import Fragment from xblock.fragment import Fragment
from opaque_keys.edx.keys import CourseKey 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.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from shoppingcart.models import CourseRegistrationCode from shoppingcart.models import CourseRegistrationCode
...@@ -402,7 +401,6 @@ class CoursewareIndex(View): ...@@ -402,7 +401,6 @@ class CoursewareIndex(View):
self.request, self.request,
self.course, self.course,
table_of_contents['chapters'], table_of_contents['chapters'],
courseware_context['language_preference'],
) )
# entrance exam data # entrance exam data
...@@ -499,7 +497,7 @@ class CoursewareIndex(View): ...@@ -499,7 +497,7 @@ class CoursewareIndex(View):
raise raise
def render_accordion(request, course, table_of_contents, language_preference): def render_accordion(request, course, table_of_contents):
""" """
Returns the HTML that renders the navigation for the given course. Returns the HTML that renders the navigation for the given course.
Expects the table_of_contents to have data on each chapter and section, Expects the table_of_contents to have data on each chapter and section,
...@@ -511,9 +509,6 @@ def render_accordion(request, course, table_of_contents, language_preference): ...@@ -511,9 +509,6 @@ def render_accordion(request, course, table_of_contents, language_preference):
('course_id', unicode(course.id)), ('course_id', unicode(course.id)),
('csrf', csrf(request)['csrf_token']), ('csrf', csrf(request)['csrf_token']),
('due_date_display_format', course.due_date_display_format), ('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() ] + TEMPLATE_IMPORTS.items()
) )
return render_to_string('courseware/accordion.html', context) return render_to_string('courseware/accordion.html', context)
......
...@@ -521,6 +521,9 @@ TEMPLATES = [ ...@@ -521,6 +521,9 @@ TEMPLATES = [
# Shoppingcart processor (detects if request.user has a cart) # Shoppingcart processor (detects if request.user has a cart)
'shoppingcart.context_processor.user_has_cart_context_processor', '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. # Allows the open edX footer to be leveraged in Django Templates.
'edxmako.shortcuts.footer_context_processor', 'edxmako.shortcuts.footer_context_processor',
......
...@@ -36,8 +36,8 @@ ...@@ -36,8 +36,8 @@
context = { context = {
datetime: $(this).data('datetime'), datetime: $(this).data('datetime'),
timezone: $(this).data('timezone'), timezone: $(this).data('timezone'),
language: $(this).attr('lang'), language: $(this).data('language'),
format: $(this).data('format') format: DateUtils.dateFormatEnum[$(this).data('format')]
}; };
displayDatetime = stringHandler( displayDatetime = stringHandler(
localizedTime(context), localizedTime(context),
...@@ -45,6 +45,11 @@ ...@@ -45,6 +45,11 @@
$(this).data('datetoken') $(this).data('datetoken')
); );
$(this).text(displayDatetime); $(this).text(displayDatetime);
} else {
displayDatetime = stringHandler(
$(this).data('string')
);
$(this).text(displayDatetime);
} }
}); });
}; };
......
...@@ -35,7 +35,7 @@ define(['../dateutil_factory.js'], function(DateUtilIterator) { ...@@ -35,7 +35,7 @@ define(['../dateutil_factory.js'], function(DateUtilIterator) {
'data-string="Due {date}"></span>' 'data-string="Due {date}"></span>'
); );
Object.keys(testLangs).forEach(function(key) { Object.keys(testLangs).forEach(function(key) {
$form.attr('lang', String(key)); $form.attr('data-language', String(key));
$(document.body).append($form); $(document.body).append($form);
DateUtilIterator.transform(iterationKey); DateUtilIterator.transform(iterationKey);
......
...@@ -116,9 +116,10 @@ ...@@ -116,9 +116,10 @@
title: gettext('Time Zone'), title: gettext('Time Zone'),
valueAttribute: 'time_zone', valueAttribute: 'time_zone',
helpMessage: gettext( helpMessage: gettext(
'Select the time zone for displaying course dates. If you do not specify a ' + 'Select the time zone for displaying course dates. ' +
'time zone here, course dates, including assignment deadlines, are displayed in ' + 'If you do not specify a time zone, course dates, ' +
'Coordinated Universal Time (UTC).' 'including assignment deadlines, will be displayed ' +
'in your browser\'s local time zone.'
), ),
groupOptions: [{ groupOptions: [{
groupTitle: gettext('All Time Zones'), groupTitle: gettext('All Time Zones'),
......
...@@ -19,14 +19,31 @@ from django.core.urlresolvers import reverse ...@@ -19,14 +19,31 @@ from django.core.urlresolvers import reverse
<span class="course-code">${course.display_number_with_default}</span> <span class="course-code">${course.display_number_with_default}</span>
<span class="course-title">${course.display_name_with_default}</span> <span class="course-title">${course.display_name_with_default}</span>
</h2> </h2>
<div class="course-date" aria-hidden="true">${_("Starts")}: ${course.start_datetime_text()}</div> <%
</div> 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):
<div class="course-date" aria-hidden="true">${_("Starts")}: ${course_start_date}</div>
% else:
<div class="course-date localized_datetime" aria-hidden="true" data-format="shortDate" data-datetime="${course_date_string}" data-string="${_("Starts: {date}")}"></div>
% endif
</div>
<div class="sr"> <div class="sr">
<ul> <ul>
<li>${course.display_org_with_default}</li> <li>${course.display_org_with_default}</li>
<li>${course.display_number_with_default}</li> <li>${course.display_number_with_default}</li>
<li>${_("Starts")}: <time itemprop="startDate" datetime="${course.start_datetime_text()}">${course.start_datetime_text()}</time></li> %if isinstance(course_start_date, str):
</ul> <li>${_("Starts")}: <time itemprop="startDate" datetime="${course_start_date}">${course_start_date}</time></li>
%else:
<li>${_("Starts")}: <time class="localized_datetime" itemprop="startDate" data-format="shortDate" data-datetime="${course_date_string}"></time></li>
%endif
</ul>
</div> </div>
</a> </a>
</article> </article>
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform(iterationKey=".localized_datetime");
</%static:require_module_async>
...@@ -32,21 +32,19 @@ else: ...@@ -32,21 +32,19 @@ else:
span_start=HTML('<span class="sr">'), span_start=HTML('<span class="sr">'),
span_end=HTML('</span>'), span_end=HTML('</span>'),
) if 'active' in section and section['active'] else ''}</p> ) if 'active' in section and section['active'] else ''}</p>
<%
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 is behavior differences between ## There are behavior differences between
## rendering of sections which have proctoring/timed examinations ## rendering of sections which have proctoring/timed examinations
## and those that do not. ## and those that do not.
## ##
## Proctoring exposes a exam status message field as well as ## Proctoring exposes a exam status message field as well as
## a status icon ## 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 section['format'] or due_date or 'proctoring' in section:
<p class="subtitle"> <p class="subtitle">
% if 'proctoring' in section: % if 'proctoring' in section:
...@@ -57,12 +55,12 @@ else: ...@@ -57,12 +55,12 @@ else:
## completed proctored exam statuses should not show the due date ## completed proctored exam statuses should not show the due date
## since the exam has already been submitted by the user ## since the exam has already been submitted by the user
% if not section['proctoring'].get('in_completed_state', False): % if not section['proctoring'].get('in_completed_state', False):
<span class="subtitle-name">${due_date}</span> <span class="localized-datetime subtitle-name" data-datetime="${section['due']}" data-string="${data_string}" data-timezone="${user_timezone}" data-language="${user_language}"></span>
% endif % endif
% else: % else:
## non-proctored section, we just show the exam format and the due date ## non-proctored section, we just show the exam format and the due date
## this is the standard case in edx-platform ## this is the standard case in edx-platform
<span class="subtitle-name">${section['format']} ${due_date}</span> <span class="localized-datetime subtitle-name" data-datetime="${section['due']}" data-string="${data_string}" data-timezone="${user_timezone}" data-language="${user_language}"></span>
% if 'graded' in section and section['graded']: % if 'graded' in section and section['graded']:
<span class="menu-icon icon fa fa-pencil-square-o" aria-hidden="true"></span> <span class="menu-icon icon fa fa-pencil-square-o" aria-hidden="true"></span>
...@@ -87,4 +85,8 @@ else: ...@@ -87,4 +85,8 @@ else:
<%static:require_module_async module_name="js/courseware/accordion_events" class_name="AccordionEvents"> <%static:require_module_async module_name="js/courseware/accordion_events" class_name="AccordionEvents">
AccordionEvents(); AccordionEvents();
</%static:require_module_async> </%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 % endif
...@@ -223,21 +223,40 @@ from openedx.core.lib.courses import course_image_url ...@@ -223,21 +223,40 @@ from openedx.core.lib.courses import course_image_url
<ol class="important-dates"> <ol class="important-dates">
<li class="important-dates-item"><span class="icon fa fa-info-circle" aria-hidden="true"></span><p class="important-dates-item-title">${_("Course Number")}</p><span class="important-dates-item-text course-number">${course.display_number_with_default | h}</span></li> <li class="important-dates-item"><span class="icon fa fa-info-circle" aria-hidden="true"></span><p class="important-dates-item-title">${_("Course Number")}</p><span class="important-dates-item-text course-number">${course.display_number_with_default | h}</span></li>
% if not course.start_date_is_still_default: % if not course.start_date_is_still_default:
<li class="important-dates-item"><span class="icon fa fa-calendar" aria-hidden="true"></span><p class="important-dates-item-title">${_("Classes Start")}</p><span class="important-dates-item-text start-date">${course.start_datetime_text()}</span></li> <%
course_start_date = course.start
%>
<li class="important-dates-item">
<span class="icon fa fa-calendar" aria-hidden="true"></span>
<p class="important-dates-item-title">${_("Classes Start")}</p>
% if isinstance(course_start_date, str):
<span class="important-dates-item-text start-date">${course_start_date}</span>
% else:
<%
course_date_string = course_start_date.strftime('%Y-%m-%dT%H:%M:%S%z')
%>
<span class="important-dates-item-text start-date localized_datetime" data-format="shortDate" data-datetime="${course_date_string}"></span>
% endif
</li>
% endif % endif
## We plan to ditch end_date (which is not stored in course metadata), ## 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. ## but for backwards compatibility, show about/end_date blob if it exists.
% if get_course_about_section(request, course, "end_date") or course.end: % if get_course_about_section(request, course, "end_date") or course.end:
<%
course_end_date = course.end
%>
<li class="important-dates-item"> <li class="important-dates-item">
<span class="icon fa fa-calendar" aria-hidden="true"></span> <span class="icon fa fa-calendar" aria-hidden="true"></span>
<p class="important-dates-item-title">${_("Classes End")}</p> <p class="important-dates-item-title">${_("Classes End")}</p>
<span class="important-dates-item-text final-date"> % if isinstance(course_end_date, str):
% if get_course_about_section(request, course, "end_date"): <span class="important-dates-item-text final-date">${course_end_date}</span>
${get_course_about_section(request, course, "end_date")} % else:
% else: <%
${course.end_datetime_text()} course_date_string = course_end_date.strftime('%Y-%m-%dT%H:%M:%S%z')
% endif %>
</span> <span class="important-dates-item-text final-date localized_datetime" data-format="shortDate" data-datetime="${course_date_string}"></span>
% endif
</li> </li>
% endif % endif
...@@ -323,3 +342,7 @@ from openedx.core.lib.courses import course_image_url ...@@ -323,3 +342,7 @@ from openedx.core.lib.courses import course_image_url
%endif %endif
<%include file="../video_modal.html" /> <%include file="../video_modal.html" />
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform(iterationKey=".localized_datetime");
</%static:require_module_async>
...@@ -6,7 +6,6 @@ from course_modes.models import CourseMode ...@@ -6,7 +6,6 @@ from course_modes.models import CourseMode
from certificates.models import CertificateStatuses from certificates.models import CertificateStatuses
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from util.date_utils import get_time_display, DEFAULT_SHORT_DATE_FORMAT
from django.conf import settings from django.conf import settings
from django.utils.http import urlquote_plus from django.utils.http import urlquote_plus
%> %>
...@@ -121,7 +120,7 @@ from django.utils.http import urlquote_plus ...@@ -121,7 +120,7 @@ from django.utils.http import urlquote_plus
<span>${_("Verification Declined" )}</span> <span>${_("Verification Declined" )}</span>
%elif requirement['status'] == 'satisfied': %elif requirement['status'] == 'satisfied':
<span class="fa fa-check" aria-hidden="true"></span> <span class="fa fa-check" aria-hidden="true"></span>
<span>${_("Completed by")} ${get_time_display(requirement['status_date'], DEFAULT_SHORT_DATE_FORMAT, settings.TIME_ZONE)}</span> <span class="localized-datetime" data-datetime="${requirement['status_date']}" data-string="${_('Completed by {date}')}" data-timezone="${user_timezone}" data-language="${user_language}"></span>
%endif %endif
%else: %else:
<span class="not-achieve">${_("Upcoming")}</span> <span class="not-achieve">${_("Upcoming")}</span>
...@@ -168,11 +167,7 @@ from django.utils.http import urlquote_plus ...@@ -168,11 +167,7 @@ from django.utils.http import urlquote_plus
</h5> </h5>
%if section.due is not None: %if section.due is not None:
<p> <p>
<% <em class="localized-datetime" data-datetime="${section.due}" data-string="${_('due {date}')}" data-timezone="${user_timezone}" data-language="${user_language}"></em>
formatted_string = get_time_display(section.due, course.due_date_display_format, coerce_tz=settings.TIME_ZONE_DISPLAYED_FOR_DEADLINES)
due_date = '' if len(formatted_string)==0 else _(u'due {date}').format(date=formatted_string)
%>
<em>${due_date | h}</em>
</p> </p>
%endif %endif
%if len(section.scores) > 0: %if len(section.scores) > 0:
...@@ -197,3 +192,6 @@ from django.utils.http import urlquote_plus ...@@ -197,3 +192,6 @@ from django.utils.http import urlquote_plus
</div> </div>
</div> </div>
</main> </main>
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform(iterationKey=".localized-datetime");
</%static:require_module_async>
...@@ -10,7 +10,6 @@ from course_modes.models import CourseMode ...@@ -10,7 +10,6 @@ from course_modes.models import CourseMode
from course_modes.helpers import enrollment_mode_display from course_modes.helpers import enrollment_mode_display
from openedx.core.djangolib.js_utils import dump_js_escaped_json from openedx.core.djangolib.js_utils import dump_js_escaped_json
from openedx.core.djangolib.markup import HTML, Text from openedx.core.djangolib.markup import HTML, Text
from openedx.core.lib.time_zone_utils import get_user_time_zone
from student.helpers import ( from student.helpers import (
VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_NEED_TO_VERIFY,
VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_SUBMITTED,
...@@ -99,20 +98,37 @@ from student.helpers import ( ...@@ -99,20 +98,37 @@ from student.helpers import (
<div class="course-info"> <div class="course-info">
<span class="info-university">${course_overview.display_org_with_default} - </span> <span class="info-university">${course_overview.display_org_with_default} - </span>
<span class="info-course-id">${course_overview.display_number_with_default}</span> <span class="info-course-id">${course_overview.display_number_with_default}</span>
<span class="info-date-block" data-tooltip="Hi"> <%
<% time_zone = get_user_time_zone(user) %> if course_overview.start_date_is_still_default:
% if course_overview.has_ended(): container_string = _("Coming Soon")
${_("Ended - {end_date}").format(end_date=course_overview.end_datetime_text("SHORT_DATE", time_zone))} course_date = None
% elif course_overview.has_started(): else:
${_("Started - {start_date}").format(start_date=course_overview.start_datetime_text("SHORT_DATE", time_zone))} format = 'shortDate'
% elif course_overview.start_date_is_still_default: # Course start date TBD if course_overview.has_ended():
${_("Coming Soon")} container_string = _("Ended - {date}")
% elif course_overview.starts_within(days=5): # hasn't started yet course_date = course_overview.end
${_("Starts - {start_date}").format(start_date=course_overview.start_datetime_text("DAY_AND_TIME", time_zone))} elif course_overview.has_started():
% else: # hasn't started yet container_string = _("Started - {date}")
${_("Starts - {start_date}").format(start_date=course_overview.start_datetime_text("SHORT_DATE", time_zone))} course_date = course_overview.start
% endif elif course_overview.starts_within(days=5):
</span> container_string = _("Starts - {date}")
course_date = course_overview.start
format = 'defaultFormat'
else: ## hasn't started yet
container_string = _("Starts - {date}")
course_date = course_overview.start
endif
endif
%>
% if isinstance(course_date, str):
<span class="info-date-block" data-tooltip="Hi">${_(container_string).format(date=course_date)}</span>
% elif course_date is not None:
<%
course_date_string = course_date.strftime('%Y-%m-%dT%H:%M:%S%z')
%>
<span class="info-date-block localized-datetime" data-language="${user_language}" data-tooltip="Hi" data-timezone="${user_timezone}" data-datetime="${course_date_string}" data-format=${format} data-string="${container_string}"></span>
% endif
</div> </div>
<div class="wrapper-course-actions"> <div class="wrapper-course-actions">
<div class="course-actions"> <div class="course-actions">
...@@ -439,3 +455,7 @@ from student.helpers import ( ...@@ -439,3 +455,7 @@ from student.helpers import (
} }
}); });
</script> </script>
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform(iterationKey=".localized-datetime");
</%static:require_module_async>
...@@ -357,7 +357,6 @@ class CourseOverview(TimeStampedModel): ...@@ -357,7 +357,6 @@ class CourseOverview(TimeStampedModel):
""" """
Returns True if the course starts with-in given number of days otherwise returns False. 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) return course_metadata_utils.course_starts_within(self.start, days)
def start_datetime_text(self, format_string="SHORT_DATE", time_zone=utc): def start_datetime_text(self, format_string="SHORT_DATE", time_zone=utc):
...@@ -389,6 +388,7 @@ class CourseOverview(TimeStampedModel): ...@@ -389,6 +388,7 @@ class CourseOverview(TimeStampedModel):
def end_datetime_text(self, format_string="SHORT_DATE", time_zone=utc): 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. Returns the end date or datetime for the course formatted as a string.
""" """
return course_metadata_utils.course_end_datetime_text( return course_metadata_utils.course_end_datetime_text(
self.end, self.end,
......
"""Tests covering time zone utilities.""" """Tests covering time zone utilities."""
from freezegun import freeze_time from freezegun import freeze_time
from student.tests.factories import UserFactory 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 ( from openedx.core.lib.time_zone_utils import (
get_display_time_zone, get_display_time_zone,
get_time_zone_abbr, get_time_zone_abbr,
get_time_zone_offset, get_time_zone_offset,
get_user_time_zone,
) )
from pytz import timezone, utc from pytz import timezone, utc
from unittest import TestCase from unittest import TestCase
...@@ -25,20 +23,6 @@ class TestTimeZoneUtils(TestCase): ...@@ -25,20 +23,6 @@ class TestTimeZoneUtils(TestCase):
self.user = UserFactory.build() self.user = UserFactory.build()
self.user.save() 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): def _display_time_zone_helper(self, time_zone_string):
""" """
Helper function to return all info from get_display_time_zone() Helper function to return all info from get_display_time_zone()
......
...@@ -2,19 +2,9 @@ ...@@ -2,19 +2,9 @@
Utilities related to timezones Utilities related to timezones
""" """
from datetime import datetime from datetime import datetime
from pytz import common_timezones, timezone, utc 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): 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. Returns a string, specified by format string, of the current date/time of the time zone.
......
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