From 1b2fbbd61d57eb84095efcb620cdf2a3c2b8b438 Mon Sep 17 00:00:00 2001 From: Gregory Martin Date: Wed, 30 Nov 2016 11:30:21 -0500 Subject: [PATCH] delete datetext functions, implement dateutils --- .../xmodule/xmodule/course_metadata_utils.py | 80 ---------------- common/lib/xmodule/xmodule/course_module.py | 33 +------ .../tests/test_course_metadata_utils.py | 84 +---------------- .../xmodule/tests/test_course_module.py | 62 +------------ lms/djangoapps/ccx/models.py | 31 ------- lms/djangoapps/ccx/tests/test_models.py | 93 +------------------ lms/djangoapps/learner_dashboard/views.py | 4 +- lms/djangoapps/shoppingcart/models.py | 19 ++-- .../verify_student/tests/test_views.py | 5 +- lms/static/js/discovery/discovery_factory.js | 10 +- lms/static/js/discovery/views/course_card.js | 49 ++++++---- .../js/discovery/views/courses_listing.js | 3 + .../models/course_card_model.js | 45 +++++++-- .../views/program_details_view.js | 3 +- .../spec/discovery/views/course_card_spec.js | 2 +- .../course_card_view_spec.js | 14 ++- .../js/verify_student/pay_and_verify.js | 2 - .../enrollment_confirmation_step_view.js | 1 - .../views/payment_confirmation_step_view.js | 1 - lms/templates/courseware/courses.html | 4 +- .../learner_dashboard/program_details.html | 1 + lms/templates/shoppingcart/receipt.html | 20 ---- .../registration_code_receipt.html | 6 -- .../registration_code_redemption.html | 5 - lms/templates/shoppingcart/shopping_cart.html | 4 - .../enrollment_confirmation_step.underscore | 6 +- .../verify_student/pay_and_verify.html | 4 - .../content/course_overviews/models.py | 30 ------ .../content/course_overviews/tests.py | 4 - .../djangoapps/programs/tests/test_utils.py | 22 +++-- openedx/core/djangoapps/programs/utils.py | 20 ++-- 31 files changed, 143 insertions(+), 524 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_metadata_utils.py b/common/lib/xmodule/xmodule/course_metadata_utils.py index 726104ff54..e0a2e996ed 100644 --- a/common/lib/xmodule/xmodule/course_metadata_utils.py +++ b/common/lib/xmodule/xmodule/course_metadata_utils.py @@ -10,11 +10,8 @@ from datetime import datetime, timedelta import dateutil.parser from math import exp -from openedx.core.lib.time_zone_utils import get_time_zone_abbr from pytz import utc -from .fields import Date - DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=utc) @@ -96,83 +93,6 @@ def course_start_date_is_default(start, advertised_start): return advertised_start is None and start == DEFAULT_START_DATE -def _datetime_to_string(date_time, format_string, time_zone, strftime_localized): - """ - Formats the given datetime with the given function and format string. - - Adds time zone abbreviation to the resulting string if the format is DATE_TIME or TIME. - - Arguments: - date_time (datetime): the datetime to be formatted - format_string (str): the date format type, as passed to strftime - time_zone (pytz time zone): the time zone to convert to - strftime_localized ((datetime, str) -> str): a nm localized string - formatting function - """ - result = strftime_localized(date_time.astimezone(time_zone), format_string) - abbr = get_time_zone_abbr(time_zone, date_time) - return ( - result + ' ' + abbr if format_string in ['DATE_TIME', 'TIME', 'DAY_AND_TIME'] - else result - ) - - -def course_start_datetime_text(start_date, advertised_start, format_string, time_zone, ugettext, strftime_localized): - """ - Calculates text to be shown to user regarding a course's start - datetime in specified time zone. - - Prefers .advertised_start, then falls back to .start. - - Arguments: - start_date (datetime): the course's start datetime - advertised_start (str): the course's advertised start date - format_string (str): the date format type, as passed to strftime - time_zone (pytz time zone): the time zone to convert to - ugettext ((str) -> str): a text localization function - strftime_localized ((datetime, str) -> str): a localized string - formatting function - """ - if advertised_start is not None: - # TODO: This will return an empty string if advertised_start == ""... consider changing this behavior? - try: - # from_json either returns a Date, returns None, or raises a ValueError - parsed_advertised_start = Date().from_json(advertised_start) - if parsed_advertised_start is not None: - # In the Django implementation of strftime_localized, if - # the year is <1900, _datetime_to_string will raise a ValueError. - return _datetime_to_string(parsed_advertised_start, format_string, time_zone, strftime_localized) - except ValueError: - pass - return advertised_start.title() - elif start_date != DEFAULT_START_DATE: - return _datetime_to_string(start_date, format_string, time_zone, strftime_localized) - else: - _ = ugettext - # Translators: TBD stands for 'To Be Determined' and is used when a course - # does not yet have an announced start date. - return _('TBD') - - -def course_end_datetime_text(end_date, format_string, time_zone, strftime_localized): - """ - Returns a formatted string for a course's end date or datetime. - - If end_date is None, an empty string will be returned. - - Arguments: - end_date (datetime): the end datetime of a course - format_string (str): the date format type, as passed to strftime - time_zone (pytz time zone): the time zone to convert to - strftime_localized ((datetime, str) -> str): a localized string - formatting function - """ - return ( - _datetime_to_string(end_date, format_string, time_zone, strftime_localized) if end_date is not None - else '' - ) - - def may_certify_for_course(certificates_display_behavior, certificates_show_before_end, has_ended): """ Returns whether it is acceptable to show the student a certificate download diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 6bcbeef621..95af81a9b9 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -197,10 +197,11 @@ class CourseFields(object): scope=Scope.settings, ) advertised_start = String( - display_name=_("Course Advertised Start Date"), + display_name=_("Course Advertised Start"), help=_( - "Enter the date you want to advertise as the course start date, if this date is different from the set " - "start date. To advertise the set start date, enter null." + "Enter the text that you want to use as the advertised starting time frame for the course, " + "such as \"Winter 2018\". If you enter null for this value, the start date that you have set " + "for this course is used." ), scope=Scope.settings ) @@ -1211,21 +1212,6 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin): """Return the course_id for this course""" return self.location.course_key - def start_datetime_text(self, format_string="SHORT_DATE", time_zone=utc): - """ - Returns the desired text corresponding the course's start date and time in specified time zone, defaulted - to UTC. Prefers .advertised_start, then falls back to .start - """ - i18n = self.runtime.service(self, "i18n") - return course_metadata_utils.course_start_datetime_text( - self.start, - self.advertised_start, - format_string, - time_zone, - i18n.ugettext, - i18n.strftime - ) - @property def start_date_is_still_default(self): """ @@ -1237,17 +1223,6 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin): self.advertised_start ) - def end_datetime_text(self, format_string="SHORT_DATE", time_zone=utc): - """ - Returns the end date or date_time for the course formatted as a string. - """ - return course_metadata_utils.course_end_datetime_text( - self.end, - format_string, - time_zone, - self.runtime.service(self, "i18n").strftime - ) - def get_discussion_blackout_datetimes(self): """ Get a list of dicts with start and end fields with datetime values from diff --git a/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py b/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py index 05df588a7e..06014a9bd7 100644 --- a/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py +++ b/common/lib/xmodule/xmodule/tests/test_course_metadata_utils.py @@ -5,7 +5,7 @@ from collections import namedtuple from datetime import timedelta, datetime from unittest import TestCase -from pytz import timezone, utc +from pytz import utc from xmodule.block_metadata_utils import ( url_name_for_block, display_name_with_default, @@ -18,11 +18,8 @@ from xmodule.course_metadata_utils import ( has_course_ended, DEFAULT_START_DATE, course_start_date_is_default, - course_start_datetime_text, - course_end_datetime_text, may_certify_for_course, ) -from xmodule.fields import Date from xmodule.modulestore.tests.utils import ( MongoModulestoreBuilder, VersioningModulestoreBuilder, @@ -112,12 +109,6 @@ class CourseMetadataUtilsTestCase(TestCase): test_datetime = datetime(1945, 2, 6, 4, 20, 00, tzinfo=utc) advertised_start_parsable = "2038-01-19 03:14:07" - advertised_start_bad_date = "215-01-01 10:10:10" - advertised_start_unparsable = "This coming fall" - time_zone_normal_parsable = "2016-03-27 00:59:00" - time_zone_normal_datetime = datetime(2016, 3, 27, 00, 59, 00, tzinfo=utc) - time_zone_daylight_parsable = "2016-03-27 01:00:00" - time_zone_daylight_datetime = datetime(2016, 3, 27, 1, 00, 00, tzinfo=utc) FunctionTest = namedtuple('FunctionTest', 'function scenarios') # pylint: disable=invalid-name TestScenario = namedtuple('TestScenario', 'arguments expected_return') # pylint: disable=invalid-name @@ -169,79 +160,6 @@ class CourseMetadataUtilsTestCase(TestCase): TestScenario((DEFAULT_START_DATE, advertised_start_parsable), False), TestScenario((DEFAULT_START_DATE, None), True), ]), - FunctionTest(course_start_datetime_text, [ - # Test parsable advertised start date. - # Expect start datetime to be parsed and formatted back into a string. - TestScenario( - (DEFAULT_START_DATE, advertised_start_parsable, 'DATE_TIME', - utc, noop_gettext, mock_strftime_localized), - mock_strftime_localized(Date().from_json(advertised_start_parsable), 'DATE_TIME') + " UTC" - ), - # Test un-parsable advertised start date. - # Expect date parsing to throw a ValueError, and the advertised - # start to be returned in Title Case. - TestScenario( - (test_datetime, advertised_start_unparsable, 'DATE_TIME', - utc, noop_gettext, mock_strftime_localized), - advertised_start_unparsable.title() - ), - # Test parsable advertised start date from before January 1, 1900. - # Expect mock_strftime_localized to throw a ValueError, and the - # advertised start to be returned in Title Case. - TestScenario( - (test_datetime, advertised_start_bad_date, 'DATE_TIME', - utc, noop_gettext, mock_strftime_localized), - advertised_start_bad_date.title() - ), - # Test without advertised start date, but with a set start datetime. - # Expect formatted datetime to be returned. - TestScenario( - (test_datetime, None, 'SHORT_DATE', utc, noop_gettext, mock_strftime_localized), - mock_strftime_localized(test_datetime, 'SHORT_DATE') - ), - # Test without advertised start date and with default start datetime. - # Expect TBD to be returned. - TestScenario( - (DEFAULT_START_DATE, None, 'SHORT_DATE', utc, noop_gettext, mock_strftime_localized), - 'TBD' - ), - # Test correctly formatted start datetime is returned during normal daylight hours - TestScenario( - (DEFAULT_START_DATE, time_zone_normal_parsable, 'DATE_TIME', - timezone('Europe/Paris'), noop_gettext, mock_strftime_localized), - "DATE_TIME " + "2016-03-27 01:59:00 CET" - ), - # Test correctly formatted start datetime is returned during daylight savings hours - TestScenario( - (DEFAULT_START_DATE, time_zone_daylight_parsable, 'DATE_TIME', - timezone('Europe/Paris'), noop_gettext, mock_strftime_localized), - "DATE_TIME " + "2016-03-27 03:00:00 CEST" - ) - ]), - FunctionTest(course_end_datetime_text, [ - # Test with a set end datetime. - # Expect formatted datetime to be returned. - TestScenario( - (test_datetime, 'TIME', utc, mock_strftime_localized), - mock_strftime_localized(test_datetime, 'TIME') + " UTC" - ), - # Test with default end datetime. - # Expect empty string to be returned. - TestScenario( - (None, 'TIME', utc, mock_strftime_localized), - "" - ), - # Test correctly formatted end datetime is returned during normal daylight hours - TestScenario( - (time_zone_normal_datetime, 'TIME', timezone('Europe/Paris'), mock_strftime_localized), - "TIME " + "2016-03-27 01:59:00 CET" - ), - # Test correctly formatted end datetime is returned during daylight savings hours - TestScenario( - (time_zone_daylight_datetime, 'TIME', timezone('Europe/Paris'), mock_strftime_localized), - "TIME " + "2016-03-27 03:00:00 CEST" - ) - ]), FunctionTest(may_certify_for_course, [ TestScenario(('early_with_info', True, True), True), TestScenario(('early_no_info', False, False), True), diff --git a/common/lib/xmodule/xmodule/tests/test_course_module.py b/common/lib/xmodule/xmodule/tests/test_course_module.py index 72a4d8d176..35c9322017 100644 --- a/common/lib/xmodule/xmodule/tests/test_course_module.py +++ b/common/lib/xmodule/xmodule/tests/test_course_module.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta import itertools from fs.memoryfs import MemoryFS from mock import Mock, patch -from pytz import timezone, utc +from pytz import utc from xblock.runtime import KvsFieldData, DictKeyValueStore import xmodule.course_module @@ -209,36 +209,6 @@ class IsNewCourseTestCase(unittest.TestCase): (xmodule.course_module.CourseFields.start.default, 'January 2014', 'January 2014', False, 'January 2014'), ] - @patch('xmodule.course_metadata_utils.datetime.now') - def test_start_date_text(self, gmtime_mock): - gmtime_mock.return_value = NOW - for s in self.start_advertised_settings: - d = get_dummy_course(start=s[0], advertised_start=s[1]) - print "Checking start=%s advertised=%s" % (s[0], s[1]) - self.assertEqual(d.start_datetime_text(), s[2]) - - @patch('xmodule.course_metadata_utils.datetime.now') - def test_start_date_time_text(self, gmtime_mock): - gmtime_mock.return_value = NOW - for setting in self.start_advertised_settings: - course = get_dummy_course(start=setting[0], advertised_start=setting[1]) - print "Checking start=%s advertised=%s" % (setting[0], setting[1]) - self.assertEqual(course.start_datetime_text("DATE_TIME"), setting[4]) - - @ddt.data(("2015-11-01T08:59", 'Nov 01, 2015', u'Nov 01, 2015 at 01:59 PDT'), - ("2015-11-01T09:00", 'Nov 01, 2015', u'Nov 01, 2015 at 01:00 PST')) - @ddt.unpack - def test_start_date_time_zone(self, course_date, expected_short_date, expected_date_time): - """ - Test that start datetime text correctly formats datetimes - for normal daylight hours and daylight savings hours - """ - time_zone = timezone('America/Los_Angeles') - - course = get_dummy_course(start=course_date, advertised_start=course_date) - self.assertEqual(course.start_datetime_text(time_zone=time_zone), expected_short_date) - self.assertEqual(course.start_datetime_text("DATE_TIME", time_zone), expected_date_time) - def test_start_date_is_default(self): for s in self.start_advertised_settings: d = get_dummy_course(start=s[0], advertised_start=s[1]) @@ -276,36 +246,6 @@ class IsNewCourseTestCase(unittest.TestCase): descriptor = get_dummy_course(start='2012-12-31T12:00') assert descriptor.is_newish is True - def test_end_date_text(self): - # No end date set, returns empty string. - d = get_dummy_course('2012-12-02T12:00') - self.assertEqual('', d.end_datetime_text()) - - d = get_dummy_course('2012-12-02T12:00', end='2014-9-04T12:00') - self.assertEqual('Sep 04, 2014', d.end_datetime_text()) - - def test_end_date_time_text(self): - # No end date set, returns empty string. - course = get_dummy_course('2012-12-02T12:00') - self.assertEqual('', course.end_datetime_text("DATE_TIME")) - - course = get_dummy_course('2012-12-02T12:00', end='2014-9-04T12:00') - self.assertEqual('Sep 04, 2014 at 12:00 UTC', course.end_datetime_text("DATE_TIME")) - - @ddt.data(("2015-11-01T08:59", 'Nov 01, 2015', u'Nov 01, 2015 at 01:59 PDT'), - ("2015-11-01T09:00", 'Nov 01, 2015', u'Nov 01, 2015 at 01:00 PST')) - @ddt.unpack - def test_end_date_time_zone(self, course_date, expected_short_date, expected_date_time): - """ - Test that end datetime text correctly formats datetimes - for normal daylight hours and daylight savings hours - """ - time_zone = timezone('America/Los_Angeles') - course = get_dummy_course(course_date, end=course_date) - - self.assertEqual(course.end_datetime_text(time_zone=time_zone), expected_short_date) - self.assertEqual(course.end_datetime_text("DATE_TIME", time_zone), expected_date_time) - class DiscussionTopicsTestCase(unittest.TestCase): def test_default_discussion_topics(self): diff --git a/lms/djangoapps/ccx/models.py b/lms/djangoapps/ccx/models.py index 0b10b79d89..8f8d596cbb 100644 --- a/lms/djangoapps/ccx/models.py +++ b/lms/djangoapps/ccx/models.py @@ -12,7 +12,6 @@ from pytz import utc from lazy import lazy from ccx_keys.locator import CCXLocator -from openedx.core.lib.time_zone_utils import get_time_zone_abbr from openedx.core.djangoapps.xmodule_django.models import CourseKeyField, LocationKeyField from xmodule.error_module import ErrorDescriptor from xmodule.modulestore.django import modulestore @@ -84,36 +83,6 @@ class CustomCourseForEdX(models.Model): return datetime.now(utc) > self.due - def start_datetime_text(self, format_string="SHORT_DATE", time_zone=utc): - """Returns the desired text representation of the CCX start datetime - - The returned value is in specified time zone, defaulted to UTC. - """ - i18n = self.course.runtime.service(self.course, "i18n") - strftime = i18n.strftime - value = strftime(self.start.astimezone(time_zone), format_string) - if format_string == 'DATE_TIME': - value += ' ' + get_time_zone_abbr(time_zone, self.start) - return value - - def end_datetime_text(self, format_string="SHORT_DATE", time_zone=utc): - """Returns the desired text representation of the CCX due datetime - - If the due date for the CCX is not set, the value returned is the empty - string. - - The returned value is in specified time zone, defaulted to UTC. - """ - if self.due is None: - return '' - - i18n = self.course.runtime.service(self.course, "i18n") - strftime = i18n.strftime - value = strftime(self.due.astimezone(time_zone), format_string) - if format_string == 'DATE_TIME': - value += ' ' + get_time_zone_abbr(time_zone, self.due) - return value - @property def structure(self): """ diff --git a/lms/djangoapps/ccx/tests/test_models.py b/lms/djangoapps/ccx/tests/test_models.py index f4091e4210..5fdf7fed0e 100644 --- a/lms/djangoapps/ccx/tests/test_models.py +++ b/lms/djangoapps/ccx/tests/test_models.py @@ -4,14 +4,12 @@ tests for the models import ddt import json from datetime import datetime, timedelta -from mock import patch from nose.plugins.attrib import attr -from pytz import timezone, utc +from pytz import utc from student.roles import CourseCcxCoachRole from student.tests.factories import ( AdminFactory, ) -from util.tests.test_date_utils import fake_ugettext from xmodule.modulestore.tests.django_utils import ( ModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE @@ -152,95 +150,6 @@ class TestCCX(ModuleStoreTestCase): """verify that a ccx without a due date has not ended""" self.assertFalse(self.ccx.has_ended()) # pylint: disable=no-member - # ensure that the expected localized format will be found by the i18n - # service - @patch('util.date_utils.ugettext', fake_ugettext(translations={ - "SHORT_DATE_FORMAT": "%b %d, %Y", - })) - def test_start_datetime_short_date(self): - """verify that the start date for a ccx formats properly by default""" - start = datetime(2015, 1, 1, 12, 0, 0, tzinfo=utc) - expected = "Jan 01, 2015" - self.set_ccx_override('start', start) - actual = self.ccx.start_datetime_text() # pylint: disable=no-member - self.assertEqual(expected, actual) - - @patch('util.date_utils.ugettext', fake_ugettext(translations={ - "DATE_TIME_FORMAT": "%b %d, %Y at %H:%M", - })) - def test_start_datetime_date_time_format(self): - """verify that the DATE_TIME format also works as expected""" - start = datetime(2015, 1, 1, 12, 0, 0, tzinfo=utc) - expected = "Jan 01, 2015 at 12:00 UTC" - self.set_ccx_override('start', start) - actual = self.ccx.start_datetime_text('DATE_TIME') # pylint: disable=no-member - self.assertEqual(expected, actual) - - @ddt.data((datetime(2015, 11, 1, 8, 59, 00, tzinfo=utc), "Nov 01, 2015", "Nov 01, 2015 at 01:59 PDT"), - (datetime(2015, 11, 1, 9, 00, 00, tzinfo=utc), "Nov 01, 2015", "Nov 01, 2015 at 01:00 PST")) - @ddt.unpack - def test_start_date_time_zone(self, start_date_time, expected_short_date, expected_date_time): - """ - verify that start date is correctly converted when time zone specified - during normal daylight hours and daylight savings hours - """ - time_zone = timezone('America/Los_Angeles') - - self.set_ccx_override('start', start_date_time) - actual_short_date = self.ccx.start_datetime_text(time_zone=time_zone) # pylint: disable=no-member - actual_datetime = self.ccx.start_datetime_text('DATE_TIME', time_zone) # pylint: disable=no-member - self.assertEqual(expected_short_date, actual_short_date) - self.assertEqual(expected_date_time, actual_datetime) - - @patch('util.date_utils.ugettext', fake_ugettext(translations={ - "SHORT_DATE_FORMAT": "%b %d, %Y", - })) - def test_end_datetime_short_date(self): - """verify that the end date for a ccx formats properly by default""" - end = datetime(2015, 1, 1, 12, 0, 0, tzinfo=utc) - expected = "Jan 01, 2015" - self.set_ccx_override('due', end) - actual = self.ccx.end_datetime_text() # pylint: disable=no-member - self.assertEqual(expected, actual) - - @patch('util.date_utils.ugettext', fake_ugettext(translations={ - "DATE_TIME_FORMAT": "%b %d, %Y at %H:%M", - })) - def test_end_datetime_date_time_format(self): - """verify that the DATE_TIME format also works as expected""" - end = datetime(2015, 1, 1, 12, 0, 0, tzinfo=utc) - expected = "Jan 01, 2015 at 12:00 UTC" - self.set_ccx_override('due', end) - actual = self.ccx.end_datetime_text('DATE_TIME') # pylint: disable=no-member - self.assertEqual(expected, actual) - - @ddt.data((datetime(2015, 11, 1, 8, 59, 00, tzinfo=utc), "Nov 01, 2015", "Nov 01, 2015 at 01:59 PDT"), - (datetime(2015, 11, 1, 9, 00, 00, tzinfo=utc), "Nov 01, 2015", "Nov 01, 2015 at 01:00 PST")) - @ddt.unpack - def test_end_datetime_time_zone(self, end_date_time, expected_short_date, expected_date_time): - """ - verify that end date is correctly converted when time zone specified - during normal daylight hours and daylight savings hours - """ - time_zone = timezone('America/Los_Angeles') - - self.set_ccx_override('due', end_date_time) - actual_short_date = self.ccx.end_datetime_text(time_zone=time_zone) # pylint: disable=no-member - actual_datetime = self.ccx.end_datetime_text('DATE_TIME', time_zone) # pylint: disable=no-member - self.assertEqual(expected_short_date, actual_short_date) - self.assertEqual(expected_date_time, actual_datetime) - - @patch('util.date_utils.ugettext', fake_ugettext(translations={ - "DATE_TIME_FORMAT": "%b %d, %Y at %H:%M", - })) - def test_end_datetime_no_due_date(self): - """verify that without a due date, the end date is an empty string""" - expected = '' - actual = self.ccx.end_datetime_text() # pylint: disable=no-member - self.assertEqual(expected, actual) - actual = self.ccx.end_datetime_text('DATE_TIME') # pylint: disable=no-member - self.assertEqual(expected, actual) - def test_ccx_max_student_enrollment_correct(self): """ Verify the override value for max_student_enrollments_allowed diff --git a/lms/djangoapps/learner_dashboard/views.py b/lms/djangoapps/learner_dashboard/views.py index a67bbb20ce..556489bbd7 100644 --- a/lms/djangoapps/learner_dashboard/views.py +++ b/lms/djangoapps/learner_dashboard/views.py @@ -12,6 +12,7 @@ from openedx.core.djangoapps.catalog.utils import get_programs as get_catalog_pr from openedx.core.djangoapps.credentials.utils import get_programs_credentials from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs import utils +from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences @login_required @@ -75,7 +76,8 @@ def program_details(request, program_id): 'show_program_listing': programs_config.show_program_listing, 'nav_hidden': True, 'disable_courseware_js': True, - 'uses_pattern_library': True + 'uses_pattern_library': True, + 'user_preferences': get_user_preferences(request.user) } return render_to_response('learner_dashboard/program_details.html', context) diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 9509a8362a..dc4de3e044 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -332,7 +332,7 @@ class Order(models.Model): """ this function generates the csv file """ - course_info = [] + course_names = [] csv_file = StringIO.StringIO() csv_writer = csv.writer(csv_file) csv_writer.writerow(['Course Name', 'Registration Code', 'URL']) @@ -340,15 +340,15 @@ class Order(models.Model): course_id = item.course_id course = get_course_by_id(item.course_id, depth=0) registration_codes = CourseRegistrationCode.objects.filter(course_id=course_id, order=self) - course_info.append((course.display_name, ' (' + course.start_datetime_text() + '-' + course.end_datetime_text() + ')')) + course_names.append(course.display_name) for registration_code in registration_codes: redemption_url = reverse('register_code_redemption', args=[registration_code.code]) url = '{base_url}{redemption_url}'.format(base_url=site_name, redemption_url=redemption_url) csv_writer.writerow([unicode(course.display_name).encode("utf-8"), registration_code.code, url]) - return csv_file, course_info + return csv_file, course_names - def send_confirmation_emails(self, orderitems, is_order_type_business, csv_file, pdf_file, site_name, courses_info): + def send_confirmation_emails(self, orderitems, is_order_type_business, csv_file, pdf_file, site_name, course_names): """ send confirmation e-mail """ @@ -358,8 +358,7 @@ class Order(models.Model): joined_course_names = "" if self.recipient_email: recipient_list.append((self.recipient_name, self.recipient_email, 'email_recipient')) - courses_names_with_dates = [course_info[0] + course_info[1] for course_info in courses_info] - joined_course_names = " " + ", ".join(courses_names_with_dates) + joined_course_names = " " + ", ".join(course_names) if not is_order_type_business: subject = _("Order Payment Confirmation") @@ -387,7 +386,7 @@ class Order(models.Model): 'recipient_type': recipient[2], 'site_name': site_name, 'order_items': orderitems, - 'course_names': ", ".join([course_info[0] for course_info in courses_info]), + 'course_names': ", ".join(course_names), 'dashboard_url': dashboard_url, 'currency_symbol': settings.PAID_COURSE_REGISTRATION_CURRENCY[1], 'order_placed_by': '{username} ({email})'.format( @@ -477,13 +476,13 @@ class Order(models.Model): item.purchase_item() csv_file = None - courses_info = [] + course_names = [] if self.order_type == OrderTypes.BUSINESS: # # Generate the CSV file that contains all of the RegistrationCodes that have already been # generated when the purchase has transacted # - csv_file, courses_info = self.generate_registration_codes_csv(orderitems, site_name) + csv_file, course_names = self.generate_registration_codes_csv(orderitems, site_name) try: pdf_file = self.generate_pdf_receipt(orderitems) @@ -494,7 +493,7 @@ class Order(models.Model): try: self.send_confirmation_emails( orderitems, self.order_type == OrderTypes.BUSINESS, - csv_file, pdf_file, site_name, courses_info + csv_file, pdf_file, site_name, course_names ) except Exception: # pylint: disable=broad-except # Catch all exceptions here, since the Django view implicitly diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index 36fb41caa5..9add06d8b2 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -514,7 +514,6 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): response, unicode(course.id), course.display_name, - course.start_datetime_text(), courseware_url ) @@ -966,12 +965,11 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): else: self.assertFalse(displayed, msg="Expected '{req}' requirement to be hidden".format(req=req)) - def _assert_course_details(self, response, course_key, display_name, start_text, url): + def _assert_course_details(self, response, course_key, display_name, url): """Check the course information on the page. """ response_dict = self._get_page_data(response) self.assertEqual(response_dict['course_key'], course_key) self.assertEqual(response_dict['course_name'], display_name) - self.assertEqual(response_dict['course_start_date'], start_text) self.assertEqual(response_dict['courseware_url'], url) def _assert_user_details(self, response, full_name): @@ -1001,7 +999,6 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): 'full_name': pay_and_verify_div['data-full-name'], 'course_key': pay_and_verify_div['data-course-key'], 'course_name': pay_and_verify_div['data-course-name'], - 'course_start_date': pay_and_verify_div['data-course-start-date'], 'courseware_url': pay_and_verify_div['data-courseware-url'], 'course_mode_name': pay_and_verify_div['data-course-mode-name'], 'course_mode_slug': pay_and_verify_div['data-course-mode-slug'], diff --git a/lms/static/js/discovery/discovery_factory.js b/lms/static/js/discovery/discovery_factory.js index 575bca1d3e..2bc58fac8d 100644 --- a/lms/static/js/discovery/discovery_factory.js +++ b/lms/static/js/discovery/discovery_factory.js @@ -5,17 +5,23 @@ 'js/discovery/views/search_form', 'js/discovery/views/courses_listing', 'js/discovery/views/filter_bar', 'js/discovery/views/refine_sidebar'], function(Backbone, SearchState, Filters, SearchForm, CoursesListing, FilterBar, RefineSidebar) { - return function(meanings, searchQuery) { + return function(meanings, searchQuery, userLanguage, userTimezone) { var dispatcher = _.extend({}, Backbone.Events); var search = new SearchState(); var filters = new Filters(); - var listing = new CoursesListing({model: search.discovery}); var form = new SearchForm(); var filterBar = new FilterBar({collection: filters}); var refineSidebar = new RefineSidebar({ collection: search.discovery.facetOptions, meanings: meanings }); + var listing; + var courseListingModel = search.discovery; + courseListingModel.userPreferences = { + userLanguage: userLanguage, + userTimezone: userTimezone + }; + listing = new CoursesListing({model: courseListingModel}); dispatcher.listenTo(form, 'search', function(query) { filters.reset(); diff --git a/lms/static/js/discovery/views/course_card.js b/lms/static/js/discovery/views/course_card.js index 761e4f07ec..f67ca4bf8e 100644 --- a/lms/static/js/discovery/views/course_card.js +++ b/lms/static/js/discovery/views/course_card.js @@ -4,24 +4,19 @@ 'underscore', 'backbone', 'gettext', - 'date' - ], function($, _, Backbone, gettext, Date) { + 'edx-ui-toolkit/js/utils/date-utils' + ], function($, _, Backbone, gettext, DateUtils) { 'use strict'; - function formatDate(date) { - return dateUTC(date).toString('MMM dd, yyyy'); - } - - // Return a date object using UTC time instead of local time - function dateUTC(date) { - return new Date( - date.getUTCFullYear(), - date.getUTCMonth(), - date.getUTCDate(), - date.getUTCHours(), - date.getUTCMinutes(), - date.getUTCSeconds() - ); + function formatDate(date, userLanguage, userTimezone) { + var context; + context = { + datetime: date, + language: userLanguage, + timezone: userTimezone, + format: DateUtils.dateFormatEnum.shortDate + }; + return DateUtils.localize(context); } return Backbone.View.extend({ @@ -36,8 +31,26 @@ render: function() { var data = _.clone(this.model.attributes); - data.start = formatDate(new Date(data.start)); - data.enrollment_start = formatDate(new Date(data.enrollment_start)); + var userLanguage = '', + userTimezone = ''; + if (this.model.userPreferences !== undefined) { + userLanguage = this.model.userPreferences.userLanguage; + userTimezone = this.model.userPreferences.userTimezone; + } + if (data.advertised_start !== undefined) { + data.start = data.advertised_start; + } else { + data.start = formatDate( + new Date(data.start), + userLanguage, + userTimezone + ); + } + data.enrollment_start = formatDate( + new Date(data.enrollment_start), + userLanguage, + userTimezone + ); this.$el.html(this.tpl(data)); return this; } diff --git a/lms/static/js/discovery/views/courses_listing.js b/lms/static/js/discovery/views/courses_listing.js index 62f5672722..da313678ce 100644 --- a/lms/static/js/discovery/views/courses_listing.js +++ b/lms/static/js/discovery/views/courses_listing.js @@ -31,12 +31,15 @@ }, renderItems: function() { + /* eslint no-param-reassign: [2, { "props": true }] */ var latest = this.model.latest(); var items = latest.map(function(result) { + result.userPreferences = this.model.userPreferences; var item = new CourseCardView({model: result}); return item.render().el; }, this); this.$list.append(items); + /* eslint no-param-reassign: [2, { "props": false }] */ }, attachScrollHandler: function() { diff --git a/lms/static/js/learner_dashboard/models/course_card_model.js b/lms/static/js/learner_dashboard/models/course_card_model.js index 0ed6ea78b1..2652d600c4 100644 --- a/lms/static/js/learner_dashboard/models/course_card_model.js +++ b/lms/static/js/learner_dashboard/models/course_card_model.js @@ -4,14 +4,15 @@ (function(define) { 'use strict'; define([ - 'backbone' + 'backbone', + 'edx-ui-toolkit/js/utils/date-utils' ], - function(Backbone) { + function(Backbone, DateUtils) { return Backbone.Model.extend({ initialize: function(data) { if (data) { this.context = data; - this.setActiveRunMode(this.getRunMode(data.run_modes)); + this.setActiveRunMode(this.getRunMode(data.run_modes), data.user_preferences); } }, @@ -31,7 +32,7 @@ var enrolled_mode = _.findWhere(runModes, {is_enrolled: true}), openEnrollmentRunModes = this.getEnrollableRunModes(), desiredRunMode; - // We populate our model by looking at the run modes. + // We populate our model by looking at the run modes. if (enrolled_mode) { // If the learner is already enrolled in a run mode, return that one. desiredRunMode = enrolled_mode; @@ -64,15 +65,44 @@ }); }, - setActiveRunMode: function(runMode) { + formatDate: function(date, userPreferences) { + var context, + userTimezone = '', + userLanguage = ''; + if (userPreferences !== undefined) { + userTimezone = userPreferences.time_zone; + userLanguage = userPreferences['pref-lang']; + } + context = { + datetime: date, + timezone: userTimezone, + language: userLanguage, + format: DateUtils.dateFormatEnum.shortDate + }; + return DateUtils.localize(context); + }, + + setActiveRunMode: function(runMode, userPreferences) { + var startDateString; if (runMode) { + if (runMode.advertised_start !== undefined && runMode.advertised_start !== 'None') { + startDateString = runMode.advertised_start; + } else { + startDateString = this.formatDate( + runMode.start_date, + userPreferences + ); + } this.set({ certificate_url: runMode.certificate_url, course_image_url: runMode.course_image_url || '', course_key: runMode.course_key, course_url: runMode.course_url || '', display_name: this.context.display_name, - end_date: runMode.end_date, + end_date: this.formatDate( + runMode.end_date, + userPreferences + ), enrollable_run_modes: this.getEnrollableRunModes(), is_course_ended: runMode.is_course_ended, is_enrolled: runMode.is_enrolled, @@ -81,13 +111,12 @@ marketing_url: runMode.marketing_url, mode_slug: runMode.mode_slug, run_key: runMode.run_key, - start_date: runMode.start_date, + start_date: startDateString, upcoming_run_modes: this.getUpcomingRunModes(), upgrade_url: runMode.upgrade_url }); } }, - setUnselected: function() { // Called to reset the model back to the unselected state. var unselectedMode = this.getUnselectedRunMode(this.get('enrollable_run_modes')); diff --git a/lms/static/js/learner_dashboard/views/program_details_view.js b/lms/static/js/learner_dashboard/views/program_details_view.js index 74effe5a08..6536375c7e 100644 --- a/lms/static/js/learner_dashboard/views/program_details_view.js +++ b/lms/static/js/learner_dashboard/views/program_details_view.js @@ -33,7 +33,8 @@ this.options = options; this.programModel = new Backbone.Model(this.options.programData); this.courseCardCollection = new CourseCardCollection( - this.programModel.get('course_codes') + this.programModel.get('course_codes'), + this.options.userPreferences ); this.render(); }, diff --git a/lms/static/js/spec/discovery/views/course_card_spec.js b/lms/static/js/spec/discovery/views/course_card_spec.js index ab093672ec..228035dca0 100644 --- a/lms/static/js/spec/discovery/views/course_card_spec.js +++ b/lms/static/js/spec/discovery/views/course_card_spec.js @@ -47,7 +47,7 @@ define([ expect(this.view.$el.find('.course-name')).toContainHtml(data.org); expect(this.view.$el.find('.course-name')).toContainHtml(data.content.number); expect(this.view.$el.find('.course-name')).toContainHtml(data.content.display_name); - expect(this.view.$el.find('.course-date')).toContainHtml('Jan 01, 1970'); + expect(this.view.$el.find('.course-date').text().trim()).toEqual('Starts: Jan 1, 1970'); }); }); }); diff --git a/lms/static/js/spec/learner_dashboard/course_card_view_spec.js b/lms/static/js/spec/learner_dashboard/course_card_view_spec.js index 5f89de6298..9754f70d8c 100644 --- a/lms/static/js/spec/learner_dashboard/course_card_view_spec.js +++ b/lms/static/js/spec/learner_dashboard/course_card_view_spec.js @@ -30,8 +30,9 @@ define([ context.run_modes[0].marketing_url ); expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key); - expect(view.$('.course-details .course-text .run-period').html()) - .toEqual(context.run_modes[0].start_date + ' - ' + context.run_modes[0].end_date); + expect(view.$('.course-details .course-text .run-period').html()).toEqual( + context.run_modes[0].start_date + ' - ' + context.run_modes[0].end_date + ); }; beforeEach(function() { @@ -92,6 +93,15 @@ define([ validateCourseInfoDisplay(); }); + it('should show the course advertised start date', function() { + var advertisedStart = 'This is an advertised start'; + context.run_modes[0].advertised_start = advertisedStart; + setupView(context, false); + expect(view.$('.course-details .course-text .run-period').html()).toEqual( + advertisedStart + ' - ' + context.run_modes[0].end_date + ); + }); + it('should only show certificate status section if a certificate has been earned', function() { var certUrl = 'sample-certificate'; diff --git a/lms/static/js/verify_student/pay_and_verify.js b/lms/static/js/verify_student/pay_and_verify.js index fed1aa72cf..ba4ec01a90 100644 --- a/lms/static/js/verify_student/pay_and_verify.js +++ b/lms/static/js/verify_student/pay_and_verify.js @@ -75,7 +75,6 @@ var edx = edx || {}; 'payment-confirmation-step': { courseKey: el.data('course-key'), courseName: el.data('course-name'), - courseStartDate: el.data('course-start-date'), coursewareUrl: el.data('courseware-url'), platformName: el.data('platform-name'), requirements: el.data('requirements') @@ -94,7 +93,6 @@ var edx = edx || {}; }, 'enrollment-confirmation-step': { courseName: el.data('course-name'), - courseStartDate: el.data('course-start-date'), coursewareUrl: el.data('courseware-url'), platformName: el.data('platform-name') } diff --git a/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js b/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js index e71976b765..3fda84f90a 100644 --- a/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js +++ b/lms/static/js/verify_student/views/enrollment_confirmation_step_view.js @@ -23,7 +23,6 @@ var edx = edx || {}; defaultContext: function() { return { courseName: '', - courseStartDate: '', coursewareUrl: '', platformName: '' }; diff --git a/lms/static/js/verify_student/views/payment_confirmation_step_view.js b/lms/static/js/verify_student/views/payment_confirmation_step_view.js index cba61eebc6..501b84d3e6 100644 --- a/lms/static/js/verify_student/views/payment_confirmation_step_view.js +++ b/lms/static/js/verify_student/views/payment_confirmation_step_view.js @@ -16,7 +16,6 @@ var edx = edx || {}; return { courseKey: '', courseName: '', - courseStartDate: '', coursewareUrl: '', platformName: '', requirements: [] diff --git a/lms/templates/courseware/courses.html b/lms/templates/courseware/courses.html index 994ef7e6d7..94f48ea018 100644 --- a/lms/templates/courseware/courses.html +++ b/lms/templates/courseware/courses.html @@ -20,7 +20,9 @@ <%static:require_module module_name="js/discovery/discovery_factory" class_name="DiscoveryFactory"> DiscoveryFactory( ${course_discovery_meanings | n, dump_js_escaped_json}, - getParameterByName('search_query') + getParameterByName('search_query'), + "${user_language}", + "${user_timezone}" ); diff --git a/lms/templates/learner_dashboard/program_details.html b/lms/templates/learner_dashboard/program_details.html index 8af6668543..b31f71e37a 100644 --- a/lms/templates/learner_dashboard/program_details.html +++ b/lms/templates/learner_dashboard/program_details.html @@ -16,6 +16,7 @@ from openedx.core.djangolib.js_utils import ( ProgramDetailsFactory({ programData: ${program_data | n, dump_js_escaped_json}, urls: ${urls | n, dump_js_escaped_json}, + userPreferences: ${user_preferences | n, dump_js_escaped_json}, }); diff --git a/lms/templates/shoppingcart/receipt.html b/lms/templates/shoppingcart/receipt.html index 08e213e3c8..4ae016b773 100644 --- a/lms/templates/shoppingcart/receipt.html +++ b/lms/templates/shoppingcart/receipt.html @@ -301,26 +301,6 @@ from openedx.core.lib.courses import course_image_url ${_('Registration for:')} ${ course.display_name | h } -

- - <% - course_start_time = course.start_datetime_text() - course_end_time = course.end_datetime_text() - %> - % if course_start_time or course_end_time: - ${_("Course Dates")}: - %endif - - - % if course_start_time: - ${course_start_time} - %endif - - - % if course_end_time: - ${course_end_time} - %endif - -


% if item.status == "purchased": diff --git a/lms/templates/shoppingcart/registration_code_receipt.html b/lms/templates/shoppingcart/registration_code_receipt.html index df9359ba21..cd85e78b61 100644 --- a/lms/templates/shoppingcart/registration_code_receipt.html +++ b/lms/templates/shoppingcart/registration_code_receipt.html @@ -34,12 +34,6 @@ from openedx.core.lib.courses import course_image_url

${_("{course_name}").format(course_name=course.display_name) | h} - - ${_("{start_date} - {end_date}").format( - start_date=course.start_datetime_text(), - end_date=course.end_datetime_text() - )} -


diff --git a/lms/templates/shoppingcart/registration_code_redemption.html b/lms/templates/shoppingcart/registration_code_redemption.html index c2c3213db7..266db3b955 100644 --- a/lms/templates/shoppingcart/registration_code_redemption.html +++ b/lms/templates/shoppingcart/registration_code_redemption.html @@ -34,11 +34,6 @@ from openedx.core.lib.courses import course_image_url

${course.display_name | h} - - ${course.start_datetime_text()} - - - ${course.end_datetime_text()} -


diff --git a/lms/templates/shoppingcart/shopping_cart.html b/lms/templates/shoppingcart/shopping_cart.html index 6775236220..6bc5c277c6 100644 --- a/lms/templates/shoppingcart/shopping_cart.html +++ b/lms/templates/shoppingcart/shopping_cart.html @@ -74,10 +74,6 @@ from openedx.core.lib.courses import course_image_url ${_('Registration for:')} ${ course.display_name } -

- ${_('Course Dates:')} - ${ course.start_datetime_text() } - ${ course.end_datetime_text() } -


diff --git a/lms/templates/verify_student/enrollment_confirmation_step.underscore b/lms/templates/verify_student/enrollment_confirmation_step.underscore index 55d26ed69a..8131bca06c 100644 --- a/lms/templates/verify_student/enrollment_confirmation_step.underscore +++ b/lms/templates/verify_student/enrollment_confirmation_step.underscore @@ -13,16 +13,14 @@ <%- gettext( "Course" ) %> - <%- gettext( "Status" ) %> + <%- courseName %> - - <%- _.sprintf( gettext( "Starts: %(start)s" ), { start: courseStartDate } ) %> - + diff --git a/lms/templates/verify_student/pay_and_verify.html b/lms/templates/verify_student/pay_and_verify.html index a60f7a4ae6..bb91fd62c7 100644 --- a/lms/templates/verify_student/pay_and_verify.html +++ b/lms/templates/verify_student/pay_and_verify.html @@ -65,7 +65,6 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView data-platform-name='${platform_name}' data-course-key='${course_key}' data-course-name='${course.display_name}' - data-course-start-date='${course.start_datetime_text()}' data-courseware-url='${courseware_url}' data-course-mode-name='${course_mode.name}' data-course-mode-slug='${course_mode.slug}' @@ -124,6 +123,3 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
-<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory"> - DateUtilFactory.transform(iterationKey=".localized-datetime"); - diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index d9cede9f58..82adac6150 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -9,7 +9,6 @@ from django.db import models, transaction from django.db.models.fields import BooleanField, DateTimeField, DecimalField, TextField, FloatField, IntegerField from django.db.utils import IntegrityError from django.template import defaultfilters -from django.utils.translation import ugettext from ccx_keys.locator import CCXLocator from model_utils.models import TimeStampedModel @@ -18,9 +17,7 @@ from opaque_keys.edx.keys import CourseKey from config_models.models import ConfigurationModel from lms.djangoapps import django_comment_client from openedx.core.djangoapps.models.course_details import CourseDetails -from pytz import utc from static_replace.models import AssetBaseUrlConfig -from util.date_utils import strftime_localized from xmodule import course_metadata_utils, block_metadata_utils from xmodule.course_module import CourseDescriptor, DEFAULT_START_DATE from xmodule.error_module import ErrorDescriptor @@ -359,21 +356,6 @@ class CourseOverview(TimeStampedModel): """ return course_metadata_utils.course_starts_within(self.start, days) - def start_datetime_text(self, format_string="SHORT_DATE", time_zone=utc): - """ - Returns the desired text corresponding to the course's start date and - time in the specified time zone, or utc if no time zone given. - Prefers .advertised_start, then falls back to .start. - """ - return course_metadata_utils.course_start_datetime_text( - self.start, - self.advertised_start, - format_string, - time_zone, - ugettext, - strftime_localized - ) - @property def start_date_is_still_default(self): """ @@ -385,18 +367,6 @@ class CourseOverview(TimeStampedModel): self.advertised_start, ) - 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, - format_string, - time_zone, - strftime_localized - ) - @property def sorting_score(self): """ diff --git a/openedx/core/djangoapps/content/course_overviews/tests.py b/openedx/core/djangoapps/content/course_overviews/tests.py index 2aebf28053..bd60498f7e 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests.py +++ b/openedx/core/djangoapps/content/course_overviews/tests.py @@ -127,10 +127,6 @@ class CourseOverviewTestCase(ModuleStoreTestCase): ('clean_id', ('#',)), ('has_ended', ()), ('has_started', ()), - ('start_datetime_text', ('SHORT_DATE',)), - ('start_datetime_text', ('DATE_TIME',)), - ('end_datetime_text', ('SHORT_DATE',)), - ('end_datetime_text', ('DATE_TIME',)), ('may_certify', ()), ] for method_name, method_args in methods_to_test: diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index 0d38bfe702..4e42b8c6ef 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -11,7 +11,6 @@ from django.core.cache import cache from django.core.urlresolvers import reverse from django.test import TestCase from django.test.utils import override_settings -from django.utils import timezone from django.utils.text import slugify import httpretty import mock @@ -19,6 +18,7 @@ from nose.plugins.attrib import attr from opaque_keys.edx.keys import CourseKey from edx_oauth2_provider.tests.factories import ClientFactory from provider.constants import CONFIDENTIAL +from pytz import utc from lms.djangoapps.certificates.api import MODES from lms.djangoapps.commerce.tests.test_utils import update_commerce_config @@ -718,8 +718,8 @@ class TestProgramDataExtender(ProgramsApiConfigMixin, ModuleStoreTestCase): ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) self.course = CourseFactory() - self.course.start = timezone.now() - datetime.timedelta(days=1) - self.course.end = timezone.now() + datetime.timedelta(days=1) + self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) + self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member self.organization = factories.Organization() @@ -739,14 +739,15 @@ class TestProgramDataExtender(ProgramsApiConfigMixin, ModuleStoreTestCase): course_image_url=course_overview.course_image_url, course_key=unicode(self.course.id), # pylint: disable=no-member course_url=reverse('course_root', args=[self.course.id]), # pylint: disable=no-member - end_date=strftime_localized(self.course.end, 'SHORT_DATE'), + end_date=self.course.end.replace(tzinfo=utc), enrollment_open_date=strftime_localized(utils.DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), - is_course_ended=self.course.end < timezone.now(), + is_course_ended=self.course.end < datetime.datetime.now(utc), is_enrolled=False, is_enrollment_open=True, marketing_url=MARKETING_URL, - start_date=strftime_localized(self.course.start, 'SHORT_DATE'), + start_date=self.course.start.replace(tzinfo=utc), upgrade_url=None, + advertised_start=None ), **kwargs ) @@ -828,8 +829,9 @@ class TestProgramDataExtender(ProgramsApiConfigMixin, ModuleStoreTestCase): """ Verify that course enrollment status is reflected correctly. """ - self.course.enrollment_start = timezone.now() - datetime.timedelta(days=start_offset) - self.course.enrollment_end = timezone.now() - datetime.timedelta(days=end_offset) + self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset) + self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset) + self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member data = utils.ProgramDataExtender(self.program, self.user).extend() @@ -845,7 +847,7 @@ class TestProgramDataExtender(ProgramsApiConfigMixin, ModuleStoreTestCase): Regression test for ECOM-4973. """ - self.course.enrollment_end = timezone.now() - datetime.timedelta(days=1) + self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member data = utils.ProgramDataExtender(self.program, self.user).extend() @@ -875,7 +877,7 @@ class TestProgramDataExtender(ProgramsApiConfigMixin, ModuleStoreTestCase): @ddt.data(-1, 0, 1) def test_course_course_ended(self, days_offset): - self.course.end = timezone.now() + datetime.timedelta(days=days_offset) + self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset) self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member data = utils.ProgramDataExtender(self.program, self.user).extend() diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index 6bfd5c1bf1..abe4e30f05 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -6,10 +6,9 @@ from urlparse import urljoin from django.conf import settings from django.core.urlresolvers import reverse -from django.utils import timezone from django.utils.text import slugify from opaque_keys.edx.keys import CourseKey -import pytz +from pytz import utc from course_modes.models import CourseMode from lms.djangoapps.certificates import api as certificate_api @@ -30,7 +29,7 @@ from util.organizations_helpers import get_organization_by_short_name log = logging.getLogger(__name__) # The datetime module's strftime() methods require a year >= 1900. -DEFAULT_ENROLLMENT_START_DATE = datetime.datetime(1900, 1, 1, tzinfo=pytz.UTC) +DEFAULT_ENROLLMENT_START_DATE = datetime.datetime(1900, 1, 1, tzinfo=utc) def get_programs(user, program_id=None): @@ -383,27 +382,30 @@ class ProgramDataExtender(object): run_mode['course_url'] = reverse('course_root', args=[self.course_key]) def _attach_run_mode_end_date(self, run_mode): - run_mode['end_date'] = self.course_overview.end_datetime_text() + run_mode['end_date'] = self.course_overview.end def _attach_run_mode_enrollment_open_date(self, run_mode): run_mode['enrollment_open_date'] = strftime_localized(self.enrollment_start, 'SHORT_DATE') def _attach_run_mode_is_course_ended(self, run_mode): - end_date = self.course_overview.end or datetime.datetime.max.replace(tzinfo=pytz.UTC) - run_mode['is_course_ended'] = end_date < timezone.now() + end_date = self.course_overview.end or datetime.datetime.max.replace(tzinfo=utc) + run_mode['is_course_ended'] = end_date < datetime.datetime.now(utc) def _attach_run_mode_is_enrolled(self, run_mode): run_mode['is_enrolled'] = CourseEnrollment.is_enrolled(self.user, self.course_key) def _attach_run_mode_is_enrollment_open(self, run_mode): - enrollment_end = self.course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=pytz.UTC) - run_mode['is_enrollment_open'] = self.enrollment_start <= timezone.now() < enrollment_end + enrollment_end = self.course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=utc) + run_mode['is_enrollment_open'] = self.enrollment_start <= datetime.datetime.now(utc) < enrollment_end def _attach_run_mode_marketing_url(self, run_mode): run_mode['marketing_url'] = get_run_marketing_url(self.course_key, self.user) def _attach_run_mode_start_date(self, run_mode): - run_mode['start_date'] = self.course_overview.start_datetime_text() + run_mode['start_date'] = self.course_overview.start + + def _attach_run_mode_advertised_start(self, run_mode): + run_mode['advertised_start'] = self.course_overview.advertised_start def _attach_run_mode_upgrade_url(self, run_mode): required_mode_slug = run_mode['mode_slug']