Commit 08c5a358 by Gregory Martin Committed by GitHub

Merge pull request #13923 from edx/yro_fix-PR-13794

Yro fix PR 13794
parents f29a508d e05cb31b
......@@ -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.dashboard import DashboardPage
DEFAULT_SHORT_DATE_FORMAT = "%b %d, %Y"
DEFAULT_DAY_AND_TIME_FORMAT = "%A at %-I%P"
DEFAULT_SHORT_DATE_FORMAT = '{dt:%b} {dt.day}, {dt.year}'
TEST_DATE_FORMAT = '{dt:%b} {dt.day}, {dt.year} {dt.hour:02}:{dt.minute:02}'
class BaseLmsDashboardTest(UniqueCourseTest):
......@@ -193,7 +193,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
})
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)
# reload the page for changes to course date changes to appear in dashboard
......@@ -226,7 +226,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
})
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)
# reload the page for changes to course date changes to appear in dashboard
......@@ -259,7 +259,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
})
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)
# reload the page for changes to course date changes to appear in dashboard
......@@ -293,8 +293,8 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
})
self.course_fixture.configure_course()
start_date = course_start_date.strftime(DEFAULT_DAY_AND_TIME_FORMAT)
expected_course_date = "Starts - {start_date} UTC".format(start_date=start_date)
start_date = TEST_DATE_FORMAT.format(dt=course_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
self.dashboard_page.visit()
......
......@@ -229,18 +229,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of sql queries to default,
# # of mongo queries,
# )
('no_overrides', 1, True, False): (18, 6),
('no_overrides', 2, True, False): (18, 6),
('no_overrides', 3, True, False): (18, 6),
('ccx', 1, True, False): (18, 6),
('ccx', 2, True, False): (18, 6),
('ccx', 3, True, False): (18, 6),
('no_overrides', 1, False, False): (18, 6),
('no_overrides', 2, False, False): (18, 6),
('no_overrides', 3, False, False): (18, 6),
('ccx', 1, False, False): (18, 6),
('ccx', 2, False, False): (18, 6),
('ccx', 3, False, False): (18, 6),
('no_overrides', 1, True, False): (21, 6),
('no_overrides', 2, True, False): (21, 6),
('no_overrides', 3, True, False): (21, 6),
('ccx', 1, True, False): (21, 6),
('ccx', 2, True, False): (21, 6),
('ccx', 3, True, False): (21, 6),
('no_overrides', 1, False, False): (21, 6),
('no_overrides', 2, False, False): (21, 6),
('no_overrides', 3, False, False): (21, 6),
('ccx', 1, False, False): (21, 6),
('ccx', 2, False, False): (21, 6),
('ccx', 3, False, False): (21, 6),
}
......@@ -252,19 +252,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__ = True
TEST_DATA = {
('no_overrides', 1, True, False): (18, 3),
('no_overrides', 2, True, False): (18, 3),
('no_overrides', 3, True, False): (18, 3),
('ccx', 1, True, False): (18, 3),
('ccx', 2, True, False): (18, 3),
('ccx', 3, True, False): (18, 3),
('ccx', 1, True, True): (19, 3),
('ccx', 2, True, True): (19, 3),
('ccx', 3, True, True): (19, 3),
('no_overrides', 1, False, False): (18, 3),
('no_overrides', 2, False, False): (18, 3),
('no_overrides', 3, False, False): (18, 3),
('ccx', 1, False, False): (18, 3),
('ccx', 2, False, False): (18, 3),
('ccx', 3, False, False): (18, 3),
('no_overrides', 1, True, False): (21, 3),
('no_overrides', 2, True, False): (21, 3),
('no_overrides', 3, True, False): (21, 3),
('ccx', 1, True, False): (21, 3),
('ccx', 2, True, False): (21, 3),
('ccx', 3, True, False): (21, 3),
('ccx', 1, True, True): (22, 3),
('ccx', 2, True, True): (22, 3),
('ccx', 3, True, True): (22, 3),
('no_overrides', 1, False, False): (21, 3),
('no_overrides', 2, False, False): (21, 3),
('no_overrides', 3, False, False): (21, 3),
('ccx', 1, False, False): (21, 3),
('ccx', 2, False, False): (21, 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
course-run-specific date which will be displayed to the user.
"""
from datetime import datetime
from pytz import timezone, utc
from babel.dates import format_timedelta
from django.core.urlresolvers import reverse
......@@ -12,13 +13,12 @@ from django.utils.translation import ugettext_lazy
from django.utils.translation import to_locale, get_language
from edxmako.shortcuts import render_to_string
from lazy import lazy
from pytz import utc
from course_modes.models import CourseMode
from lms.djangoapps.commerce.utils import EcommerceService
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 openedx.core.lib.time_zone_utils import get_time_zone_abbr
class DateSummary(object):
......@@ -67,8 +67,12 @@ class DateSummary(object):
@property
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):
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
self.assertEqual(resp.status_code, 200)
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):
self.fetch_course_info_with_queries(self.self_paced_course, 18, 4)
self.fetch_course_info_with_queries(self.self_paced_course, 21, 4)
......@@ -2,11 +2,8 @@
Tests for credit requirement display on the progress page.
"""
import datetime
import ddt
from mock import patch
from pytz import UTC
from django.conf import settings
from django.core.urlresolvers import reverse
......@@ -14,7 +11,6 @@ from django.core.urlresolvers import reverse
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
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 openedx.core.djangoapps.credit import api as credit_api
......@@ -122,7 +118,11 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
response,
"{}, 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}")
credit_requirements = credit_api.get_credit_requirement_status(self.course.id, self.user.username)
for requirement in credit_requirements:
self.assertContains(response, requirement['status_date'].strftime('%Y-%m-%d %H:%M'))
self.assertNotContains(response, "95%")
def test_credit_requirements_not_eligible(self):
......@@ -172,11 +172,3 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
"""Load the progress page for the course the user is enrolled in. """
url = reverse("progress", kwargs={"course_id": unicode(self.course.id)})
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):
parent_location=self.chapter.location,
due=datetime(2013, 9, 18, 11, 30, 00),
display_name='Sequential 1',
format='Homework'
)
self.vertical = ItemFactory.create(
category='vertical',
......@@ -907,33 +908,22 @@ class ViewsTestCase(ModuleStoreTestCase):
self.assertEqual(response.status_code, 200)
def test_accordion(self):
request = RequestFactory().get('foo')
request.user = self.user
table_of_contents = toc_for_course(
request.user,
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
"""
This needs a response_context, which is not included in the render_accordion's main method
returning a render_to_string, so we will render via the courseware URL in order to include
the needed context
"""
course_id = quote(unicode(self.course.id).encode("utf-8"))
self.assertIn(
u'href="/courses/{}/courseware/Chapter_1/Sequential_1/"><pclass="accordion-display-name">Sequential1</p>'
.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
response = self.client.get(
reverse('courseware', args=[unicode(course_id)]),
follow=True
)
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)
......@@ -960,7 +950,8 @@ class BaseDueDateTests(ModuleStoreTestCase):
section = ItemFactory.create(
category='sequential',
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)
ItemFactory.create(category='problem', parent_location=vertical.location)
......@@ -975,8 +966,7 @@ class BaseDueDateTests(ModuleStoreTestCase):
self.user = UserFactory.create()
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_without_tz = "due Sep 18, 2013 at 11:30"
self.time_with_tz = "2013-09-18 11:30:00+00:00"
def test_backwards_compatability(self):
# The test course being used has show_timezone = False in the policy file
......@@ -985,8 +975,7 @@ 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_without_tz)
self.assertNotContains(response, self.time_with_tz)
self.assertContains(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)
......@@ -1001,25 +990,11 @@ 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.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 ")
self.assertContains(response, self.time_with_tz)
def test_format_invalid(self):
# improperly formatted due_date_display_format falls through to default
......@@ -1049,7 +1024,10 @@ 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)
......@@ -1089,14 +1067,17 @@ 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.
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={
("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'))
......@@ -1345,17 +1326,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(35), check_mongo_calls(4):
with self.assertNumQueries(38), check_mongo_calls(4):
self._get_progress_page()
def test_progress_queries(self):
self.setup_course()
with self.assertNumQueries(35), check_mongo_calls(4):
with self.assertNumQueries(38), check_mongo_calls(4):
self._get_progress_page()
# subsequent accesses to the progress page require fewer queries.
for _ in range(2):
with self.assertNumQueries(21), check_mongo_calls(4):
with self.assertNumQueries(24), check_mongo_calls(4):
self._get_progress_page()
@patch(
......
......@@ -24,7 +24,6 @@ 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
......@@ -402,7 +401,6 @@ class CoursewareIndex(View):
self.request,
self.course,
table_of_contents['chapters'],
courseware_context['language_preference'],
)
# entrance exam data
......@@ -499,7 +497,7 @@ class CoursewareIndex(View):
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.
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):
('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)
......
......@@ -521,6 +521,9 @@ 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',
......
......@@ -36,8 +36,8 @@
context = {
datetime: $(this).data('datetime'),
timezone: $(this).data('timezone'),
language: $(this).attr('lang'),
format: $(this).data('format')
language: $(this).data('language'),
format: DateUtils.dateFormatEnum[$(this).data('format')]
};
displayDatetime = stringHandler(
localizedTime(context),
......@@ -45,6 +45,11 @@
$(this).data('datetoken')
);
$(this).text(displayDatetime);
} else {
displayDatetime = stringHandler(
$(this).data('string')
);
$(this).text(displayDatetime);
}
});
};
......
......@@ -35,7 +35,7 @@ define(['../dateutil_factory.js'], function(DateUtilIterator) {
'data-string="Due {date}"></span>'
);
Object.keys(testLangs).forEach(function(key) {
$form.attr('lang', String(key));
$form.attr('data-language', String(key));
$(document.body).append($form);
DateUtilIterator.transform(iterationKey);
......
......@@ -116,9 +116,10 @@
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 here, course dates, including assignment deadlines, are displayed in ' +
'Coordinated Universal Time (UTC).'
'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.'
),
groupOptions: [{
groupTitle: gettext('All Time Zones'),
......
......@@ -19,14 +19,31 @@ from django.core.urlresolvers import reverse
<span class="course-code">${course.display_number_with_default}</span>
<span class="course-title">${course.display_name_with_default}</span>
</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">
<ul>
<li>${course.display_org_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>
</ul>
%if isinstance(course_start_date, str):
<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>
</a>
</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:
span_start=HTML('<span class="sr">'),
span_end=HTML('</span>'),
) 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
## 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:
<p class="subtitle">
% if 'proctoring' in section:
......@@ -57,12 +55,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):
<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
% else:
## non-proctored section, we just show the exam format and the due date
## 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']:
<span class="menu-icon icon fa fa-pencil-square-o" aria-hidden="true"></span>
......@@ -87,4 +85,8 @@ 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
......@@ -223,21 +223,40 @@ from openedx.core.lib.courses import course_image_url
<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>
% 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
## 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
%>
<li class="important-dates-item">
<span class="icon fa fa-calendar" aria-hidden="true"></span>
<p class="important-dates-item-title">${_("Classes End")}</p>
<span class="important-dates-item-text final-date">
% if get_course_about_section(request, course, "end_date"):
${get_course_about_section(request, course, "end_date")}
% else:
${course.end_datetime_text()}
% endif
</span>
% if isinstance(course_end_date, str):
<span class="important-dates-item-text final-date">${course_end_date}</span>
% else:
<%
course_date_string = course_end_date.strftime('%Y-%m-%dT%H:%M:%S%z')
%>
<span class="important-dates-item-text final-date localized_datetime" data-format="shortDate" data-datetime="${course_date_string}"></span>
% endif
</li>
% endif
......@@ -323,3 +342,7 @@ from openedx.core.lib.courses import course_image_url
%endif
<%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
from certificates.models import CertificateStatuses
from django.utils.translation import ugettext as _
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.utils.http import urlquote_plus
%>
......@@ -121,7 +120,7 @@ from django.utils.http import urlquote_plus
<span>${_("Verification Declined" )}</span>
%elif requirement['status'] == 'satisfied':
<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
%else:
<span class="not-achieve">${_("Upcoming")}</span>
......@@ -168,11 +167,7 @@ from django.utils.http import urlquote_plus
</h5>
%if section.due is not None:
<p>
<%
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>
<em class="localized-datetime" data-datetime="${section.due}" data-string="${_('due {date}')}" data-timezone="${user_timezone}" data-language="${user_language}"></em>
</p>
%endif
%if len(section.scores) > 0:
......@@ -197,3 +192,6 @@ from django.utils.http import urlquote_plus
</div>
</div>
</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
from course_modes.helpers import enrollment_mode_display
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from openedx.core.djangolib.markup import HTML, Text
from openedx.core.lib.time_zone_utils import get_user_time_zone
from student.helpers import (
VERIFY_STATUS_NEED_TO_VERIFY,
VERIFY_STATUS_SUBMITTED,
......@@ -99,20 +98,37 @@ from student.helpers import (
<div class="course-info">
<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-date-block" data-tooltip="Hi">
<% time_zone = get_user_time_zone(user) %>
% if course_overview.has_ended():
${_("Ended - {end_date}").format(end_date=course_overview.end_datetime_text("SHORT_DATE", time_zone))}
% elif course_overview.has_started():
${_("Started - {start_date}").format(start_date=course_overview.start_datetime_text("SHORT_DATE", time_zone))}
% elif course_overview.start_date_is_still_default: # Course start date TBD
${_("Coming Soon")}
% elif course_overview.starts_within(days=5): # hasn't started yet
${_("Starts - {start_date}").format(start_date=course_overview.start_datetime_text("DAY_AND_TIME", time_zone))}
% else: # hasn't started yet
${_("Starts - {start_date}").format(start_date=course_overview.start_datetime_text("SHORT_DATE", time_zone))}
% endif
</span>
<%
if course_overview.start_date_is_still_default:
container_string = _("Coming Soon")
course_date = None
else:
format = 'shortDate'
if course_overview.has_ended():
container_string = _("Ended - {date}")
course_date = course_overview.end
elif course_overview.has_started():
container_string = _("Started - {date}")
course_date = course_overview.start
elif course_overview.starts_within(days=5):
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 class="wrapper-course-actions">
<div class="course-actions">
......@@ -439,3 +455,7 @@ from student.helpers import (
}
});
</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):
"""
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):
......@@ -389,6 +388,7 @@ 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,
......
"""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
......@@ -25,20 +23,6 @@ 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()
......
......@@ -2,19 +2,9 @@
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.
......
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