delete datetext functions, implement dateutils
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ var edx = edx || {};
|
||||
defaultContext: function() {
|
||||
return {
|
||||
courseName: '',
|
||||
courseStartDate: '',
|
||||
coursewareUrl: '',
|
||||
platformName: ''
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@ var edx = edx || {};
|
||||
return {
|
||||
courseKey: '',
|
||||
courseName: '',
|
||||
courseStartDate: '',
|
||||
coursewareUrl: '',
|
||||
platformName: '',
|
||||
requirements: []
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
|
||||
@@ -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},
|
||||
});
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
|
||||
@@ -301,26 +301,6 @@ from openedx.core.lib.courses import course_image_url
|
||||
<span class="course-registration-title">${_('Registration for:')}</span>
|
||||
<span class="course-display-name">${ course.display_name | h }</span>
|
||||
</h3>
|
||||
<p class="course-meta-info" aria-describedby="course-title">
|
||||
<span class="course-dates-title">
|
||||
<%
|
||||
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
|
||||
</span>
|
||||
<span class="course-display-dates">
|
||||
% if course_start_time:
|
||||
${course_start_time}
|
||||
%endif
|
||||
-
|
||||
% if course_end_time:
|
||||
${course_end_time}
|
||||
%endif
|
||||
</span>
|
||||
</p>
|
||||
<hr>
|
||||
<div class="three-col">
|
||||
% if item.status == "purchased":
|
||||
|
||||
@@ -34,12 +34,6 @@ from openedx.core.lib.courses import course_image_url
|
||||
<div class="course-title">
|
||||
<h1>
|
||||
${_("{course_name}").format(course_name=course.display_name) | h}
|
||||
<span class="course-dates">
|
||||
${_("{start_date} - {end_date}").format(
|
||||
start_date=course.start_datetime_text(),
|
||||
end_date=course.end_datetime_text()
|
||||
)}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
@@ -34,11 +34,6 @@ from openedx.core.lib.courses import course_image_url
|
||||
<div class="course-title">
|
||||
<h1>
|
||||
${course.display_name | h}
|
||||
<span class="course-dates">
|
||||
${course.start_datetime_text()}
|
||||
-
|
||||
${course.end_datetime_text()}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
@@ -74,10 +74,6 @@ from openedx.core.lib.courses import course_image_url
|
||||
<span class="course-registration-title">${_('Registration for:')}</span>
|
||||
<span class="course-display-name">${ course.display_name }</span>
|
||||
</h3>
|
||||
<p class="course-meta-info" aria-describedby="course-title">
|
||||
<span class="course-dates-title">${_('Course Dates:')}</span>
|
||||
<span class="course-display-dates">${ course.start_datetime_text() } - ${ course.end_datetime_text() }</span>
|
||||
</p>
|
||||
<hr>
|
||||
<div class="three-col">
|
||||
<div class="col-1">
|
||||
|
||||
@@ -13,16 +13,14 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" ><%- gettext( "Course" ) %></th>
|
||||
<th scope="col" ><%- gettext( "Status" ) %></th>
|
||||
<th scope="col" ></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><%- courseName %></td>
|
||||
<td>
|
||||
<%- _.sprintf( gettext( "Starts: %(start)s" ), { start: courseStartDate } ) %>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
|
||||
@@ -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
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
|
||||
DateUtilFactory.transform(iterationKey=".localized-datetime");
|
||||
</%static:require_module_async>
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user