Merge pull request #12908 from edx/renzo/course-card-upgrade-cta
Add upgrade section to program detail course cards.
This commit is contained in:
@@ -67,8 +67,9 @@ def program_details(request, program_id):
|
||||
urls = {
|
||||
'program_listing_url': reverse('program_listing_view'),
|
||||
'track_selection_url': strip_course_id(
|
||||
reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY})),
|
||||
'commerce_api_url': reverse('commerce_api:v0:baskets:create')
|
||||
reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY})
|
||||
),
|
||||
'commerce_api_url': reverse('commerce_api:v0:baskets:create'),
|
||||
}
|
||||
|
||||
context = {
|
||||
|
||||
@@ -65,17 +65,18 @@
|
||||
course_key: runMode.course_key,
|
||||
course_url: runMode.course_url || '',
|
||||
display_name: this.context.display_name,
|
||||
start_date: runMode.start_date,
|
||||
end_date: runMode.end_date,
|
||||
enrollable_run_modes: this.getEnrollableRunModes(),
|
||||
enrollment_open_date: runMode.enrollment_open_date || '',
|
||||
is_course_ended: runMode.is_course_ended,
|
||||
is_enrolled: runMode.is_enrolled,
|
||||
is_enrollment_open: runMode.is_enrollment_open,
|
||||
key: this.context.key,
|
||||
marketing_url: runMode.marketing_url || '',
|
||||
is_course_ended: runMode.is_course_ended,
|
||||
mode_slug: runMode.mode_slug,
|
||||
run_key: runMode.run_key,
|
||||
enrollment_open_date: runMode.enrollment_open_date || '',
|
||||
enrollable_run_modes: this.getEnrollableRunModes()
|
||||
start_date: runMode.start_date,
|
||||
upgrade_url: runMode.upgrade_url
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -27,13 +27,10 @@
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = this.model.toJSON(),
|
||||
$icons;
|
||||
var data = this.model.toJSON();
|
||||
|
||||
data = $.extend(data, {certificateSvg: this.iconTpl()});
|
||||
HtmlUtils.setHtml(this.$el, this.statusTpl(data));
|
||||
|
||||
$icons = this.$('.certificate-icon');
|
||||
HtmlUtils.setHtml($icons, this.iconTpl());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/models/course_enroll_model',
|
||||
'js/learner_dashboard/views/upgrade_message_view',
|
||||
'js/learner_dashboard/views/certificate_status_view',
|
||||
'js/learner_dashboard/views/course_enroll_view',
|
||||
'text!../../../templates/learner_dashboard/course_card.underscore'
|
||||
@@ -18,6 +19,7 @@
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
EnrollModel,
|
||||
UpgradeMessageView,
|
||||
CertificateStatusView,
|
||||
CourseEnrollView,
|
||||
pageTpl
|
||||
@@ -44,7 +46,8 @@
|
||||
},
|
||||
|
||||
postRender: function(){
|
||||
var $certStatus = this.$('.certificate-status');
|
||||
var $upgradeMessage = this.$('.upgrade-message'),
|
||||
$certStatus = this.$('.certificate-status');
|
||||
|
||||
this.enrollView = new CourseEnrollView({
|
||||
$parentEl: this.$('.course-actions'),
|
||||
@@ -53,13 +56,23 @@
|
||||
enrollModel: this.enrollModel
|
||||
});
|
||||
|
||||
if ( this.model.get('certificate_url') ) {
|
||||
if ( this.model.get('upgrade_url') ) {
|
||||
this.upgradeMessage = new UpgradeMessageView({
|
||||
$el: $upgradeMessage,
|
||||
model: this.model
|
||||
});
|
||||
|
||||
$certStatus.remove();
|
||||
} else if ( this.model.get('certificate_url') ) {
|
||||
this.certificateStatus = new CertificateStatusView({
|
||||
$el: $certStatus,
|
||||
model: this.model
|
||||
});
|
||||
|
||||
$upgradeMessage.remove();
|
||||
} else {
|
||||
// Styles are applied to the element that show if it's empty
|
||||
// Styles are applied to these elements which will be visible if they're empty.
|
||||
$upgradeMessage.remove();
|
||||
$certStatus.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/upgrade_message.underscore',
|
||||
'text!../../../templates/learner_dashboard/certificate_icon.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
upgradeMessageTpl,
|
||||
certificateIconTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
messageTpl: HtmlUtils.template(upgradeMessageTpl),
|
||||
iconTpl: HtmlUtils.template(certificateIconTpl),
|
||||
|
||||
initialize: function(options) {
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = this.model.toJSON();
|
||||
|
||||
data = $.extend(data, {certificateSvg: this.iconTpl()});
|
||||
HtmlUtils.setHtml(this.$el, this.messageTpl(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -19,27 +19,30 @@ define([
|
||||
key: 'ANUx'
|
||||
},
|
||||
run_modes: [{
|
||||
start_date: 'Apr 25, 2016',
|
||||
end_date: 'Jun 13, 2019',
|
||||
course_key: 'course-v1:ANUx+ANU-ASTRO1x+3T2015',
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
marketing_url: 'https://www.edx.org/course/astrophysics-exploring',
|
||||
certificate_url: '',
|
||||
course_image_url: 'http://test.com/image1',
|
||||
course_key: 'course-v1:ANUx+ANU-ASTRO1x+3T2015',
|
||||
course_started: true,
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
end_date: 'Jun 13, 2019',
|
||||
enrollment_open_date: 'Mar 03, 2016',
|
||||
is_course_ended: false,
|
||||
is_enrolled: true,
|
||||
is_enrollment_open: true,
|
||||
marketing_url: 'https://www.edx.org/course/astrophysics-exploring',
|
||||
mode_slug: 'verified',
|
||||
run_key: '2T2016',
|
||||
course_started: true,
|
||||
is_enrolled: true,
|
||||
is_course_ended: false,
|
||||
is_enrollment_open: true,
|
||||
certificate_url: '',
|
||||
enrollment_open_date: 'Mar 03, 2016'
|
||||
start_date: 'Apr 25, 2016',
|
||||
upgrade_url: ''
|
||||
}]
|
||||
},
|
||||
|
||||
setupView = function(data, isEnrolled){
|
||||
data.run_modes[0].is_enrolled = isEnrolled;
|
||||
var programData = $.extend({}, data);
|
||||
|
||||
programData.run_modes[0].is_enrolled = isEnrolled;
|
||||
setFixtures('<div class="course-card card"></div>');
|
||||
courseCardModel = new CourseCardModel(data);
|
||||
courseCardModel = new CourseCardModel(programData);
|
||||
view = new CourseCardView({
|
||||
model: courseCardModel
|
||||
});
|
||||
@@ -86,37 +89,71 @@ define([
|
||||
});
|
||||
|
||||
it('should only show certificate status section if a certificate has been earned', function() {
|
||||
var data = context,
|
||||
var data = $.extend({}, context),
|
||||
certUrl = 'sample-certificate';
|
||||
|
||||
setupView(context, false);
|
||||
expect(view.$('certificate-status').length).toEqual(0);
|
||||
expect(view.$('.certificate-status').length).toEqual(0);
|
||||
view.remove();
|
||||
|
||||
data.run_modes[0].certificate_url = certUrl;
|
||||
setupView(data, false);
|
||||
expect(view.$('.certificate-status').length).toEqual(1);
|
||||
expect(view.$('.certificate-status .cta-secondary').attr('href')).toEqual(certUrl);
|
||||
});
|
||||
|
||||
it('should render the course card with coming soon', function(){
|
||||
it('should only show upgrade message section if an upgrade is required', function() {
|
||||
var data = $.extend({}, context),
|
||||
upgradeUrl = '/path/to/upgrade';
|
||||
|
||||
expect(view.$('.upgrade-message').length).toEqual(0);
|
||||
view.remove();
|
||||
context.run_modes[0].is_enrollment_open = false;
|
||||
setupView(context, false);
|
||||
expect(view.$('.header-img').attr('src')).toEqual(context.run_modes[0].course_image_url);
|
||||
expect(view.$('.course-details .course-title').text().trim()).toEqual(context.display_name);
|
||||
|
||||
data.run_modes[0].upgrade_url = upgradeUrl;
|
||||
setupView(data, false);
|
||||
expect(view.$('.upgrade-message').length).toEqual(1);
|
||||
expect(view.$('.upgrade-message .cta-primary').attr('href')).toEqual(upgradeUrl);
|
||||
});
|
||||
|
||||
it('should not show both the upgrade message and certificate status sections', function() {
|
||||
var data = $.extend({}, context);
|
||||
|
||||
// Verify that no empty elements are left in the DOM.
|
||||
data.run_modes[0].upgrade_url = '';
|
||||
data.run_modes[0].certificate_url = '';
|
||||
setupView(data, false);
|
||||
expect(view.$('.upgrade-message').length).toEqual(0);
|
||||
expect(view.$('.certificate-status').length).toEqual(0);
|
||||
view.remove();
|
||||
|
||||
// Verify that the upgrade message takes priority.
|
||||
data.run_modes[0].upgrade_url = '/path/to/upgrade';
|
||||
data.run_modes[0].certificate_url = '/path/to/certificate';
|
||||
setupView(data, false);
|
||||
expect(view.$('.upgrade-message').length).toEqual(1);
|
||||
expect(view.$('.certificate-status').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should render the course card with coming soon', function(){
|
||||
var data = $.extend({}, context);
|
||||
|
||||
data.run_modes[0].is_enrollment_open = false;
|
||||
setupView(data, false);
|
||||
expect(view.$('.header-img').attr('src')).toEqual(data.run_modes[0].course_image_url);
|
||||
expect(view.$('.course-details .course-title').text().trim()).toEqual(data.display_name);
|
||||
expect(view.$('.course-details .course-title-link').length).toBe(0);
|
||||
expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key);
|
||||
expect(view.$('.course-details .course-text .course-key').html()).toEqual(data.key);
|
||||
expect(view.$('.course-details .course-text .run-period').length).toBe(0);
|
||||
expect(view.$('.no-action-message').text().trim()).toBe('Coming Soon');
|
||||
expect(view.$('.enrollment-open-date').text().trim())
|
||||
.toEqual(context.run_modes[0].enrollment_open_date);
|
||||
.toEqual(data.run_modes[0].enrollment_open_date);
|
||||
});
|
||||
|
||||
it('should render if enrollment_open_date is not provided', function(){
|
||||
view.remove();
|
||||
context.run_modes[0].is_enrollment_open = true;
|
||||
delete context.run_modes[0].enrollment_open_date;
|
||||
setupView(context, false);
|
||||
var data = $.extend({}, context);
|
||||
|
||||
data.run_modes[0].is_enrollment_open = true;
|
||||
delete data.run_modes[0].enrollment_open_date;
|
||||
setupView(data, false);
|
||||
validateCourseInfoDisplay();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade-message,
|
||||
.certificate-status {
|
||||
border-top: 1px solid palette(grayscale, x-trans);
|
||||
padding-top: $baseline;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<div class="message col-12 md-col-8">
|
||||
<span class="certificate-icon green-certificate-icon" aria-hidden="true"></span>
|
||||
<% // safe-lint: disable=underscore-not-escaped %>
|
||||
<span class="certificate-icon green-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
|
||||
<span class="card-msg"><%- gettext('Congratulations! You have earned a certificate for this course.') %></span>
|
||||
</div>
|
||||
<div class="action col-12 md-col-4">
|
||||
<a href="<%- certificate_url %>" class="btn-brand cta-secondary">
|
||||
<span class="certificate-icon blue-certificate-icon" aria-hidden="true"></span>
|
||||
<% // safe-lint: disable=underscore-not-escaped %>
|
||||
<span class="certificate-icon blue-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
|
||||
<%- gettext('View/Share Certificate') %>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -39,4 +39,5 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="section action-msg-view"></div>
|
||||
<div class="section upgrade-message"></div>
|
||||
<div class="section certificate-status"></div>
|
||||
|
||||
12
lms/templates/learner_dashboard/upgrade_message.underscore
Normal file
12
lms/templates/learner_dashboard/upgrade_message.underscore
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="message col-12 md-col-8">
|
||||
<% // safe-lint: disable=underscore-not-escaped %>
|
||||
<span class="certificate-icon green-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
|
||||
<span class="card-msg"><%- gettext('You need a certificate in this course to be eligible for a program certificate.') %></span>
|
||||
</div>
|
||||
<div class="action col-12 md-col-4">
|
||||
<a href="<%- upgrade_url %>" class="btn-brand cta-primary">
|
||||
<% // safe-lint: disable=underscore-not-escaped %>
|
||||
<span class="certificate-icon green-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
|
||||
<%- gettext('Upgrade Now') %>
|
||||
</a>
|
||||
</div>
|
||||
@@ -10,6 +10,7 @@ from django.conf import settings
|
||||
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
|
||||
import httpretty
|
||||
import mock
|
||||
@@ -18,6 +19,7 @@ from edx_oauth2_provider.tests.factories import ClientFactory
|
||||
from provider.constants import CONFIDENTIAL
|
||||
|
||||
from lms.djangoapps.certificates.api import MODES
|
||||
from lms.djangoapps.commerce.tests.test_utils import update_commerce_config
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.credentials.tests import factories as credentials_factories
|
||||
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin
|
||||
@@ -34,6 +36,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
CERTIFICATES_API_MODULE = 'lms.djangoapps.certificates.api'
|
||||
ECOMMERCE_URL_ROOT = 'http://example-ecommerce.com'
|
||||
|
||||
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@@ -672,11 +675,14 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(ECOMMERCE_PUBLIC_URL_ROOT=ECOMMERCE_URL_ROOT)
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
"""Tests of the utility function used to supplement program data."""
|
||||
password = 'test'
|
||||
maxDiff = None
|
||||
sku = 'abc123'
|
||||
password = 'test'
|
||||
checkout_path = '/basket'
|
||||
|
||||
def setUp(self):
|
||||
super(TestSupplementProgramData, self).setUp()
|
||||
@@ -704,15 +710,18 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member
|
||||
run_mode = dict(
|
||||
factories.RunMode(
|
||||
certificate_url=None,
|
||||
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
|
||||
course_image_url=course_overview.course_image_url,
|
||||
start_date=strftime_localized(self.course.start, 'SHORT_DATE'),
|
||||
end_date=strftime_localized(self.course.end, 'SHORT_DATE'),
|
||||
enrollment_open_date=None,
|
||||
is_course_ended=self.course.end < timezone.now(),
|
||||
is_enrolled=False,
|
||||
is_enrollment_open=True,
|
||||
marketing_url='',
|
||||
marketing_url=None,
|
||||
start_date=strftime_localized(self.course.start, 'SHORT_DATE'),
|
||||
upgrade_url=None,
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
@@ -722,19 +731,72 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_student_enrollment_status(self, is_enrolled):
|
||||
"""Verify that program data is supplemented correctly."""
|
||||
@ddt.data(
|
||||
(False, None, False),
|
||||
(True, MODES.audit, True),
|
||||
(True, MODES.verified, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
@mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
|
||||
def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode):
|
||||
"""Verify that program data is supplemented with the student's enrollment status."""
|
||||
expected_upgrade_url = '{root}/{path}?sku={sku}'.format(
|
||||
root=ECOMMERCE_URL_ROOT,
|
||||
path=self.checkout_path.strip('/'),
|
||||
sku=self.sku,
|
||||
)
|
||||
|
||||
update_commerce_config(enabled=True, checkout_page=self.checkout_path)
|
||||
|
||||
mock_mode = mock.Mock()
|
||||
mock_mode.sku = self.sku
|
||||
mock_get_mode.return_value = mock_mode
|
||||
|
||||
if is_enrolled:
|
||||
CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # pylint: disable=no-member
|
||||
CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data, is_enrolled=is_enrolled)
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrolled=is_enrolled,
|
||||
upgrade_url=expected_upgrade_url if is_upgrade_required else None
|
||||
)
|
||||
|
||||
@ddt.data(MODES.audit, MODES.verified)
|
||||
def test_inactive_enrollment_no_upgrade(self, enrolled_mode):
|
||||
"""Verify that a student with an inactive enrollment isn't encouraged to upgrade."""
|
||||
update_commerce_config(enabled=True, checkout_page=self.checkout_path)
|
||||
|
||||
CourseEnrollmentFactory(
|
||||
user=self.user,
|
||||
course_id=self.course.id, # pylint: disable=no-member
|
||||
mode=enrolled_mode,
|
||||
is_active=False,
|
||||
)
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data)
|
||||
|
||||
@mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
|
||||
def test_ecommerce_disabled(self, mock_get_mode):
|
||||
"""Verify that the utility can operate when the ecommerce service is disabled."""
|
||||
update_commerce_config(enabled=False, checkout_page=self.checkout_path)
|
||||
|
||||
mock_mode = mock.Mock()
|
||||
mock_mode.sku = self.sku
|
||||
mock_get_mode.return_value = mock_mode
|
||||
|
||||
CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data, is_enrolled=True, upgrade_url=None)
|
||||
|
||||
@ddt.data(
|
||||
[1, 1, False],
|
||||
[1, -1, True],
|
||||
(1, 1, False),
|
||||
(1, -1, True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_course_enrollment_status(self, start_offset, end_offset, is_enrollment_open):
|
||||
@@ -746,13 +808,15 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
if is_enrollment_open:
|
||||
self._assert_supplemented(data, is_enrollment_open=is_enrollment_open)
|
||||
enrollment_open_date = None
|
||||
else:
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrollment_open=is_enrollment_open,
|
||||
enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE')
|
||||
)
|
||||
enrollment_open_date = strftime_localized(self.course.enrollment_start, 'SHORT_DATE')
|
||||
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrollment_open=is_enrollment_open,
|
||||
enrollment_open_date=enrollment_open_date,
|
||||
)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status')
|
||||
@@ -765,11 +829,21 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
if is_uuid_available:
|
||||
expected_url = reverse('certificates:render_cert_by_uuid', kwargs={'certificate_uuid': test_uuid})
|
||||
self._assert_supplemented(data, certificate_url=expected_url)
|
||||
else:
|
||||
self._assert_supplemented(data)
|
||||
expected_url = reverse(
|
||||
'certificates:render_cert_by_uuid',
|
||||
kwargs={'certificate_uuid': test_uuid}
|
||||
) if is_uuid_available else None
|
||||
|
||||
self._assert_supplemented(data, certificate_url=expected_url)
|
||||
|
||||
@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 = self.update_course(self.course, self.user.id) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data)
|
||||
|
||||
@mock.patch(UTILS_MODULE + '.get_organization_by_short_name')
|
||||
def test_organization_logo_exists(self, mock_get_organization_by_short_name):
|
||||
@@ -780,6 +854,7 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
mock_get_organization_by_short_name.return_value = {
|
||||
'logo': mock_image
|
||||
}
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
self.assertEqual(data['organizations'][0].get('img'), mock_logo_url)
|
||||
|
||||
@@ -799,11 +874,3 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
mock_get_organization_by_short_name.return_value = {'logo': None}
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
self.assertEqual(data['organizations'][0].get('img'), None)
|
||||
|
||||
@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 = self.update_course(self.course, self.user.id) # pylint: disable=no-member
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data)
|
||||
|
||||
@@ -10,7 +10,9 @@ from django.utils.text import slugify
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
import pytz
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.certificates import api as certificate_api
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.lib.edx_api_utils import get_edx_api_data
|
||||
@@ -322,6 +324,7 @@ class ProgramProgressMeter(object):
|
||||
return parsed
|
||||
|
||||
|
||||
# TODO: This function will benefit from being refactored as a class.
|
||||
def supplement_program_data(program_data, user):
|
||||
"""Supplement program course codes with CourseOverview and CourseEnrollment data.
|
||||
|
||||
@@ -330,8 +333,8 @@ def supplement_program_data(program_data, user):
|
||||
user (User): The user whose enrollments to inspect.
|
||||
"""
|
||||
for organization in program_data['organizations']:
|
||||
# TODO cache the results of the get_organization_by_short_name call
|
||||
# so we don't have to hit database that frequently
|
||||
# TODO: Cache the results of the get_organization_by_short_name call so
|
||||
# the database is hit less frequently.
|
||||
org_obj = get_organization_by_short_name(organization['key'])
|
||||
if org_obj and org_obj.get('logo'):
|
||||
organization['img'] = org_obj['logo'].url
|
||||
@@ -341,34 +344,58 @@ def supplement_program_data(program_data, user):
|
||||
course_key = CourseKey.from_string(run_mode['course_key'])
|
||||
course_overview = CourseOverview.get_from_id(course_key)
|
||||
|
||||
run_mode['course_url'] = reverse('course_root', args=[course_key])
|
||||
run_mode['course_image_url'] = course_overview.course_image_url
|
||||
course_url = reverse('course_root', args=[course_key])
|
||||
course_image_url = course_overview.course_image_url
|
||||
|
||||
run_mode['start_date'] = course_overview.start_datetime_text()
|
||||
run_mode['end_date'] = course_overview.end_datetime_text()
|
||||
start_date_string = course_overview.start_datetime_text()
|
||||
end_date_string = course_overview.end_datetime_text()
|
||||
|
||||
end_date = course_overview.end or datetime.datetime.max.replace(tzinfo=pytz.UTC)
|
||||
run_mode['is_course_ended'] = end_date < timezone.now()
|
||||
is_course_ended = end_date < timezone.now()
|
||||
|
||||
run_mode['is_enrolled'] = CourseEnrollment.is_enrolled(user, course_key)
|
||||
is_enrolled = CourseEnrollment.is_enrolled(user, course_key)
|
||||
|
||||
enrollment_start = course_overview.enrollment_start or datetime.datetime.min.replace(tzinfo=pytz.UTC)
|
||||
enrollment_end = course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=pytz.UTC)
|
||||
is_enrollment_open = enrollment_start <= timezone.now() < enrollment_end
|
||||
run_mode['is_enrollment_open'] = is_enrollment_open
|
||||
if not is_enrollment_open:
|
||||
# Only render this enrollment open date if the enrollment open is in the future
|
||||
run_mode['enrollment_open_date'] = strftime_localized(enrollment_start, 'SHORT_DATE')
|
||||
|
||||
# TODO: Currently unavailable on LMS.
|
||||
run_mode['marketing_url'] = ''
|
||||
enrollment_open_date = None if is_enrollment_open else strftime_localized(enrollment_start, 'SHORT_DATE')
|
||||
|
||||
certificate_data = certificate_api.certificate_downloadable_status(user, course_key)
|
||||
certificate_uuid = certificate_data.get('uuid')
|
||||
if certificate_uuid:
|
||||
run_mode['certificate_url'] = certificate_api.get_certificate_url(
|
||||
course_id=course_key,
|
||||
uuid=certificate_uuid,
|
||||
)
|
||||
certificate_url = certificate_api.get_certificate_url(
|
||||
course_id=course_key,
|
||||
uuid=certificate_uuid,
|
||||
) if certificate_uuid else None
|
||||
|
||||
required_mode_slug = run_mode['mode_slug']
|
||||
enrolled_mode_slug, _ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
|
||||
is_mode_mismatch = required_mode_slug != enrolled_mode_slug
|
||||
is_upgrade_required = is_enrolled and is_mode_mismatch
|
||||
|
||||
# Requires that the ecommerce service be in use.
|
||||
required_mode = CourseMode.mode_for_course(course_key, required_mode_slug)
|
||||
ecommerce = EcommerceService()
|
||||
sku = getattr(required_mode, 'sku', None)
|
||||
|
||||
if ecommerce.is_enabled(user) and sku:
|
||||
upgrade_url = ecommerce.checkout_page_url(required_mode.sku) if is_upgrade_required else None
|
||||
else:
|
||||
upgrade_url = None
|
||||
|
||||
run_mode.update({
|
||||
'certificate_url': certificate_url,
|
||||
'course_image_url': course_image_url,
|
||||
'course_url': course_url,
|
||||
'end_date': end_date_string,
|
||||
'enrollment_open_date': enrollment_open_date,
|
||||
'is_course_ended': is_course_ended,
|
||||
'is_enrolled': is_enrolled,
|
||||
'is_enrollment_open': is_enrollment_open,
|
||||
# TODO: Not currently available on LMS.
|
||||
'marketing_url': None,
|
||||
'start_date': start_date_string,
|
||||
'upgrade_url': upgrade_url,
|
||||
})
|
||||
|
||||
return program_data
|
||||
|
||||
Reference in New Issue
Block a user