Update program detail fragment.
LEARNER-3755
This commit is contained in:
@@ -32,7 +32,11 @@ class ProgramsFragmentView(EdxFragmentView):
|
||||
Render the program listing fragment.
|
||||
"""
|
||||
user = request.user
|
||||
mobile_only = json.loads(request.GET.get('mobile_only', 'false'))
|
||||
try:
|
||||
mobile_only = json.loads(request.GET.get('mobile_only', 'false'))
|
||||
except ValueError:
|
||||
mobile_only = False
|
||||
|
||||
programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current()
|
||||
if not programs_config.enabled or not user.is_authenticated():
|
||||
raise Http404
|
||||
@@ -80,7 +84,12 @@ class ProgramDetailsFragmentView(EdxFragmentView):
|
||||
if not program_data:
|
||||
raise Http404
|
||||
|
||||
program_data = ProgramDataExtender(program_data, request.user).extend()
|
||||
try:
|
||||
mobile_only = json.loads(request.GET.get('mobile_only', 'false'))
|
||||
except ValueError:
|
||||
mobile_only = False
|
||||
|
||||
program_data = ProgramDataExtender(program_data, request.user, mobile_only=mobile_only).extend()
|
||||
course_data = meter.progress(programs=[program_data], count_only=False)[0]
|
||||
certificate_data = get_certificates(request.user, program_data)
|
||||
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
|
||||
$.extend(unselectedRun, {
|
||||
marketing_url: courseRun.marketing_url,
|
||||
is_enrollment_open: courseRun.is_enrollment_open
|
||||
is_enrollment_open: courseRun.is_enrollment_open,
|
||||
key: courseRun.key || '',
|
||||
is_mobile_only: courseRun.is_mobile_only || false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -196,7 +198,7 @@
|
||||
if (isEnrolled && courseRun.course_url) {
|
||||
courseTitleLink = courseRun.course_url;
|
||||
} else if (!isEnrolled && courseRun.marketing_url) {
|
||||
courseTitleLink = courseRun.marketing_url;
|
||||
courseTitleLink = this.updateMarketingUrl(courseRun);
|
||||
}
|
||||
this.set({
|
||||
certificate_url: courseRun.certificate_url,
|
||||
@@ -217,7 +219,8 @@
|
||||
upcoming_course_runs: this.getUpcomingCourseRuns(),
|
||||
upgrade_url: courseRun.upgrade_url,
|
||||
price: this.getCertificatePriceString(courseRun),
|
||||
course_title_link: courseTitleLink
|
||||
course_title_link: courseTitleLink,
|
||||
is_mobile_only: courseRun.is_mobile_only || false
|
||||
});
|
||||
|
||||
// This is used to render the date for completed and in progress courses
|
||||
@@ -240,6 +243,30 @@
|
||||
});
|
||||
this.setActiveCourseRun(selectedCourseRun);
|
||||
}
|
||||
},
|
||||
|
||||
// update marketing url for deep linking if is_mobile_only true
|
||||
updateMarketingUrl: function(courseRun) {
|
||||
if (courseRun.is_mobile_only === true) {
|
||||
var marketingUrl = courseRun.marketing_url, // eslint-disable-line vars-on-top
|
||||
href = marketingUrl,
|
||||
path,
|
||||
start;
|
||||
|
||||
if (marketingUrl.indexOf('course_info?path_id') < 0) {
|
||||
start = marketingUrl.indexOf('course/');
|
||||
|
||||
if (start > -1) {
|
||||
path = marketingUrl.substr(start);
|
||||
}
|
||||
|
||||
href = 'edxapp://course_info?path_id=' + path;
|
||||
}
|
||||
|
||||
return href;
|
||||
} else {
|
||||
return courseRun.marketing_url;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
|
||||
events: {
|
||||
'click .enroll-button': 'handleEnroll'
|
||||
'click .enroll-button': 'handleEnroll',
|
||||
'change .run-select': 'updateEnrollUrl'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
@@ -55,16 +56,18 @@
|
||||
|
||||
handleEnroll: function() {
|
||||
// Enrollment click event handled here
|
||||
var courseRunKey = $('.run-select').val() || this.model.get('course_run_key');
|
||||
this.model.updateCourseRun(courseRunKey);
|
||||
if (this.model.get('is_enrolled')) {
|
||||
// Create the enrollment.
|
||||
this.enrollModel.save({
|
||||
course_id: courseRunKey
|
||||
}, {
|
||||
success: _.bind(this.enrollSuccess, this),
|
||||
error: _.bind(this.enrollError, this)
|
||||
});
|
||||
if (this.model.get('is_mobile_only') !== true) {
|
||||
var courseRunKey = $('.run-select').val() || this.model.get('course_run_key'); // eslint-disable-line vars-on-top, max-len
|
||||
this.model.updateCourseRun(courseRunKey);
|
||||
if (this.model.get('is_enrolled')) {
|
||||
// Create the enrollment.
|
||||
this.enrollModel.save({
|
||||
course_id: courseRunKey
|
||||
}, {
|
||||
success: _.bind(this.enrollSuccess, this),
|
||||
error: _.bind(this.enrollError, this)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -100,6 +103,14 @@
|
||||
}
|
||||
},
|
||||
|
||||
updateEnrollUrl: function() {
|
||||
if (this.model.get('is_mobile_only') === true) {
|
||||
var courseRunKey = $('.run-select').val(), // eslint-disable-line vars-on-top
|
||||
href = 'edxapp://enroll?course_id=' + courseRunKey + '&email_opt_in=true';
|
||||
$('.enroll-course-button').attr('href', href);
|
||||
}
|
||||
},
|
||||
|
||||
redirect: function(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
button, .enroll-course-button {
|
||||
background-color: palette(primary, dark);
|
||||
height: 37px;
|
||||
width: 128px;
|
||||
@@ -371,6 +371,10 @@
|
||||
@include float(right);
|
||||
}
|
||||
}
|
||||
|
||||
.enroll-course-button {
|
||||
padding: 7px 18.5px 0 18.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-choice {
|
||||
|
||||
@@ -33,9 +33,15 @@
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="enroll-button">
|
||||
<% if (typeof is_mobile_only != 'undefined' && is_mobile_only === true) { %>
|
||||
<a href="edxapp://enroll?course_id=<%- course_run_key %>&email_opt_in=true" class="enroll-course-button btn-brand btn cta-primary">
|
||||
<%- gettext('Enroll Now') %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<button type="button" class="btn-brand btn cta-primary">
|
||||
<%- gettext('Enroll Now') %>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } else if (upcoming_course_runs.length > 0) {%>
|
||||
<div class="no-action-message">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div><%- gettext('To complete the program, you must earn a verified certificate for each course.') %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (is_learner_eligible_for_one_click_purchase) { %>
|
||||
<% if (is_learner_eligible_for_one_click_purchase && (typeof is_mobile_only === 'undefined' || is_mobile_only === false)) { %>
|
||||
<a href="<%- completeProgramURL %>" class="btn-brand btn cta-primary upgrade-button complete-program">
|
||||
<%- gettext('Upgrade All Remaining Courses (')%>
|
||||
<% if (discount_data.is_discounted) { %>
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
<span><%- gettext('Needs verified certificate ') %></span>
|
||||
<span class="price"> <%- price %></span>
|
||||
</div>
|
||||
<% if (typeof is_mobile_only === 'undefined' || is_mobile_only === false) { %>
|
||||
<div class="action col-12 md-col-4">
|
||||
<a href="<%- upgrade_url %>" class="btn-brand btn cta-primary upgrade-button single-course-run">
|
||||
<%- gettext('Upgrade to Verified') %>
|
||||
<a>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import datetime
|
||||
import json
|
||||
import uuid
|
||||
from copy import deepcopy
|
||||
|
||||
import ddt
|
||||
import httpretty
|
||||
@@ -823,7 +824,7 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
"""Tests of the program data extender utility class."""
|
||||
maxDiff = None
|
||||
sku = 'abc123'
|
||||
checkout_path = '/basket'
|
||||
checkout_path = '/basket/add'
|
||||
instructors = {
|
||||
'instructors': [
|
||||
{
|
||||
@@ -852,7 +853,11 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
|
||||
def _assert_supplemented(self, actual, **kwargs):
|
||||
"""DRY helper used to verify that program data is extended correctly."""
|
||||
self.course_run.update(
|
||||
program = deepcopy(self.program)
|
||||
course_run = deepcopy(self.course_run)
|
||||
course = deepcopy(self.catalog_course)
|
||||
|
||||
course_run.update(
|
||||
dict(
|
||||
{
|
||||
'certificate_url': None,
|
||||
@@ -868,10 +873,10 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
self.catalog_course['course_runs'] = [self.course_run]
|
||||
self.program['courses'] = [self.catalog_course]
|
||||
course['course_runs'] = [course_run]
|
||||
program['courses'] = [course]
|
||||
|
||||
self.assertEqual(actual, self.program)
|
||||
self.assertEqual(actual, program)
|
||||
|
||||
@ddt.data(-1, 0, 1)
|
||||
def test_is_enrollment_open(self, days_offset):
|
||||
@@ -887,15 +892,13 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
self._assert_supplemented(data)
|
||||
|
||||
@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(
|
||||
expected_upgrade_url = '{root}/{path}/?sku={sku}'.format(
|
||||
root=ECOMMERCE_URL_ROOT,
|
||||
path=self.checkout_path.strip('/'),
|
||||
sku=self.sku,
|
||||
@@ -1275,6 +1278,14 @@ class TestProgramDataExtender(ModuleStoreTestCase):
|
||||
self.assertTrue(data['is_learner_eligible_for_one_click_purchase'])
|
||||
self.assertEqual(set(data['skus']), expected_skus)
|
||||
|
||||
def test_course_url_with_mobile_only(self):
|
||||
"""
|
||||
Verify that correct course url is returned for mobile.
|
||||
"""
|
||||
data = ProgramDataExtender(self.program, self.user, mobile_only=True).extend()
|
||||
expected_course_url = 'edxapp://enrolled_course_info?course_id={}'.format(self.course.id)
|
||||
self._assert_supplemented(data, course_url=expected_course_url)
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@mock.patch(UTILS_MODULE + '.get_credentials')
|
||||
|
||||
@@ -82,6 +82,7 @@ class ProgramProgressMeter(object):
|
||||
def __init__(self, site, user, enrollments=None, uuid=None, mobile_only=False):
|
||||
self.site = site
|
||||
self.user = user
|
||||
self.mobile_only = mobile_only
|
||||
|
||||
self.enrollments = enrollments or list(CourseEnrollment.enrollments_for_user(self.user))
|
||||
self.enrollments.sort(key=lambda e: e.created, reverse=True)
|
||||
@@ -106,7 +107,7 @@ class ProgramProgressMeter(object):
|
||||
if uuid:
|
||||
self.programs = [get_programs(self.site, uuid=uuid)]
|
||||
else:
|
||||
self.programs = attach_program_detail_url(get_programs(self.site), mobile_only)
|
||||
self.programs = attach_program_detail_url(get_programs(self.site), self.mobile_only)
|
||||
|
||||
def invert_programs(self):
|
||||
"""Intersect programs and enrollments.
|
||||
@@ -407,9 +408,11 @@ class ProgramDataExtender(object):
|
||||
program_data (dict): Representation of a program.
|
||||
user (User): The user whose enrollments to inspect.
|
||||
"""
|
||||
def __init__(self, program_data, user):
|
||||
def __init__(self, program_data, user, mobile_only=False):
|
||||
self.data = program_data
|
||||
self.user = user
|
||||
self.mobile_only = mobile_only
|
||||
self.data.update({'is_mobile_only': self.mobile_only})
|
||||
|
||||
self.course_run_key = None
|
||||
self.course_overview = None
|
||||
@@ -452,7 +455,10 @@ class ProgramDataExtender(object):
|
||||
) if certificate_uuid else None
|
||||
|
||||
def _attach_course_run_course_url(self, run_mode):
|
||||
run_mode['course_url'] = reverse('course_root', args=[self.course_run_key])
|
||||
if self.mobile_only:
|
||||
run_mode['course_url'] = 'edxapp://enrolled_course_info?course_id={}'.format(run_mode.get('key'))
|
||||
else:
|
||||
run_mode['course_url'] = reverse('course_root', args=[self.course_run_key])
|
||||
|
||||
def _attach_course_run_enrollment_open_date(self, run_mode):
|
||||
run_mode['enrollment_open_date'] = strftime_localized(self.enrollment_start, 'SHORT_DATE')
|
||||
@@ -498,6 +504,9 @@ class ProgramDataExtender(object):
|
||||
def _attach_course_run_may_certify(self, run_mode):
|
||||
run_mode['may_certify'] = self.course_overview.may_certify()
|
||||
|
||||
def _attach_course_run_is_mobile_only(self, run_mode):
|
||||
run_mode['is_mobile_only'] = self.mobile_only
|
||||
|
||||
def _filter_out_courses_with_entitlements(self, courses):
|
||||
"""
|
||||
Removes courses for which the current user already holds an applicable entitlement.
|
||||
@@ -670,7 +679,6 @@ def get_certificates(user, extended_program):
|
||||
return certificates
|
||||
|
||||
|
||||
# pylint: disable=missing-docstring
|
||||
class ProgramMarketingDataExtender(ProgramDataExtender):
|
||||
"""
|
||||
Utility for extending program data meant for the program marketing page which lives in the
|
||||
|
||||
Reference in New Issue
Block a user