Commit 28837c02 by Albert St. Aubin Committed by Harry Rein

Allowing a user to fulfill their entitlement on the course dashboard.

The user can now enroll in a session, unenroll from a session or change session
from a new course enrollment card on the course dashboard.
parent 292848cf
......@@ -371,8 +371,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
# Verify that the correct banner color is rendered
self.assertContains(
response,
"<div class=\"course {}\" aria-labelledby=\"course-title-{}\">".format(
self.MODE_CLASSES[status], self.course.id)
"<article class=\"course {}\"".format(self.MODE_CLASSES[status])
)
# Verify that the correct copy is rendered on the dashboard
......
......@@ -58,6 +58,7 @@ from bulk_email.models import BulkEmailFlag, Optout # pylint: disable=import-er
from certificates.api import get_certificate_url, has_html_certificates_enabled # pylint: disable=import-error
from certificates.models import ( # pylint: disable=import-error
CertificateStatuses,
GeneratedCertificate,
certificate_status_for_student
)
from course_modes.models import CourseMode
......@@ -65,6 +66,7 @@ from courseware.access import has_access
from courseware.courses import get_courses, sort_by_announcement, sort_by_start_date # pylint: disable=import-error
from django_comment_common.models import assign_role
from edxmako.shortcuts import render_to_response, render_to_string
from entitlements.models import CourseEntitlement
from eventtracking import tracker
from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
......@@ -72,7 +74,7 @@ from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
# Note that this lives in LMS, so this dependency should be refactored.
from notification_prefs.views import enable_notifications
from openedx.core.djangoapps import monitoring_utils
from openedx.core.djangoapps.catalog.utils import get_programs_with_type
from openedx.core.djangoapps.catalog.utils import get_programs_with_type, get_course_runs_for_course
from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course
from openedx.core.djangoapps.credit.email_utils import get_credit_provider_display_names, make_providers_strings
from openedx.core.djangoapps.embargo import api as embargo_api
......@@ -686,15 +688,22 @@ def dashboard(request):
'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
) or settings.SUPPORT_SITE_LINK
# get the org whitelist or the org blacklist for the current site
# Get the org whitelist or the org blacklist for the current site
site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site(user)
course_enrollments = list(get_course_enrollments(user, site_org_whitelist, site_org_blacklist))
# Get the entitlements for the user and a mapping to all available sessions for that entitlement
course_entitlements = list(CourseEntitlement.objects.filter(user=user).select_related('enrollment_course_run'))
course_entitlement_available_sessions = {
str(entitlement.uuid): get_course_runs_for_course(str(entitlement.course_uuid))
for entitlement in course_entitlements
}
# Record how many courses there are so that we can get a better
# understanding of usage patterns on prod.
monitoring_utils.accumulate('num_courses', len(course_enrollments))
# sort the enrollment pairs by the enrollment date
# Sort the enrollment pairs by the enrollment date
course_enrollments.sort(key=lambda x: x.created, reverse=True)
# Retrieve the course modes for each course
......@@ -863,6 +872,10 @@ def dashboard(request):
valid_verification_statuses = ['approved', 'must_reverify', 'pending', 'expired']
display_sidebar_on_dashboard = len(order_history_list) or verification_status in valid_verification_statuses
# Filter out any course enrollment course cards that are associated with fulfilled entitlements
for entitlement in [e for e in course_entitlements if e.enrollment_course_run is not None]:
course_enrollments = [enr for enr in course_enrollments if entitlement.enrollment_course_run.course_id != enr.course_id] # pylint: disable=line-too-long
context = {
'enterprise_message': enterprise_message,
'consent_required_courses': consent_required_courses,
......@@ -871,6 +884,8 @@ def dashboard(request):
'redirect_message': redirect_message,
'account_activation_messages': account_activation_messages,
'course_enrollments': course_enrollments,
'course_entitlements': course_entitlements,
'course_entitlement_available_sessions': course_entitlement_available_sessions,
'course_optouts': course_optouts,
'banner_account_activation_message': banner_account_activation_message,
'sidebar_account_activation_message': sidebar_account_activation_message,
......
......@@ -10,10 +10,10 @@ var edx = edx || {};
edx.dashboard.dropdown.toggleCourseActionsDropdownMenu = function(event) {
// define variables for code legibility
var dashboardIndex = $(event.currentTarget).data().dashboardIndex,
dropdown = $('#actions-dropdown-' + dashboardIndex),
$dropdown = $('#actions-dropdown-' + dashboardIndex),
dropdownButton = $('#actions-dropdown-link-' + dashboardIndex),
ariaExpandedState = (dropdownButton.attr('aria-expanded') === 'true'),
menuItems = dropdown.find('a');
menuItems = $dropdown.find('a');
var catchKeyPress = function(object, event) {
// get currently focused item
......@@ -57,12 +57,12 @@ var edx = edx || {};
};
// Toggle the visibility control for the selected element and set the focus
dropdown.toggleClass('is-visible');
if (dropdown.hasClass('is-visible')) {
dropdown.attr('tabindex', -1);
dropdown.focus();
$dropdown.toggleClass('is-visible');
if ($dropdown.hasClass('is-visible')) {
$dropdown.attr('tabindex', -1);
$dropdown.focus();
} else {
dropdown.removeAttr('tabindex');
$dropdown.removeAttr('tabindex');
dropdownButton.focus();
}
......@@ -71,8 +71,8 @@ var edx = edx || {};
// catch keypresses when inside dropdownMenu (we want to catch spacebar;
// escape; up arrow or shift+tab; and down arrow or tab)
dropdown.on('keydown', function(event) {
catchKeyPress($(this), event);
$dropdown.on('keydown', function(e) {
catchKeyPress($(this), e);
});
};
......
(function(define) {
'use strict';
define([
'js/learner_dashboard/views/course_entitlement_view'
],
function(EntitlementView) {
return function(options) {
return new EntitlementView(options);
};
});
}).call(this, define || RequireJS.define);
......@@ -6,10 +6,12 @@
define([
'backbone',
'underscore',
'gettext',
'jquery',
'edx-ui-toolkit/js/utils/date-utils'
'edx-ui-toolkit/js/utils/date-utils',
'edx-ui-toolkit/js/utils/string-utils'
],
function(Backbone, _, $, DateUtils) {
function(Backbone, _, gettext, $, DateUtils, StringUtils) {
return Backbone.Model.extend({
initialize: function(data) {
if (data) {
......@@ -140,7 +142,7 @@
formatDateString: function(run) {
var pacingType = run.pacing_type,
dateString = '',
dateString,
start = this.get('start_date') || run.start_date,
end = this.get('end_date') || run.end_date,
now = new Date(),
......@@ -148,21 +150,24 @@
endDate = new Date(end);
if (pacingType === 'self_paced') {
dateString = 'Self-paced';
if (start && startDate > now) {
dateString += ' - Starts ' + start;
if (start) {
dateString = startDate > now ?
StringUtils.interpolate(gettext('(Self-paced) Starts {start}'), {start: start}) :
StringUtils.interpolate(gettext('(Self-paced) Started {start}'), {start: start});
} else if (end && endDate > now) {
dateString += ' - Ends ' + end;
dateString = StringUtils.interpolate(gettext('(Self-paced) Ends {end}'), {end: end});
} else if (end && endDate < now) {
dateString += ' - Ended ' + end;
dateString = StringUtils.interpolate(gettext('(Self-paced) Ended {end}'), {end: end});
}
} else {
if (start && end) {
dateString = start + ' - ' + end;
} else if (start) {
dateString = 'Starts ' + start;
dateString = startDate > now ?
StringUtils.interpolate(gettext('Starts {start}'), {start: start}) :
StringUtils.interpolate(gettext('Started {start}'), {start: start});
} else if (end) {
dateString = 'Ends ' + end;
dateString = StringUtils.interpolate(gettext('Ends {end}'), {end: end});
}
}
return dateString;
......
/**
* Store data for the current
*/
(function(define) {
'use strict';
define([
'backbone'
],
function(Backbone) {
return Backbone.Model.extend({
defaults: {
availableSessions: [],
entitlementUUID: '',
currentSessionId: '',
userId: '',
courseName: ''
}
});
}
);
}).call(this, define || RequireJS.define);
define([
'backbone',
'underscore',
'jquery',
'js/learner_dashboard/models/course_entitlement_model',
'js/learner_dashboard/views/course_entitlement_view'
], function(Backbone, _, $, CourseEntitlementModel, CourseEntitlementView) {
'use strict';
describe('Course Entitlement View', function() {
var view = null,
setupView,
selectOptions,
entitlementAvailableSessions,
initialSessionId,
entitlementUUID = 'a9aiuw76a4ijs43u18',
testSessionIds = ['test_session_id_1', 'test_session_id_2'];
setupView = function(isAlreadyEnrolled) {
setFixtures('<div class="course-entitlement-selection-container"></div>');
initialSessionId = isAlreadyEnrolled ? testSessionIds[0] : '';
entitlementAvailableSessions = [{
enrollment_end: null,
session_start: '2013-02-05T05:00:00+00:00',
pacing_type: 'instructor_paced',
session_id: testSessionIds[0],
session_end: null
}, {
enrollment_end: '2017-12-22T03:30:00Z',
session_start: '2018-01-03T13:00:00+00:00',
pacing_type: 'self_paced',
session_id: testSessionIds[1],
session_end: '2018-03-09T21:30:00+00:00'
}];
view = new CourseEntitlementView({
el: '.course-entitlement-selection-container',
triggerOpenBtn: '#course-card-0 .change-session',
courseCardMessages: '#course-card-0 .messages-list > .message',
courseTitleLink: '#course-card-0 .course-title a',
courseImageLink: '#course-card-0 .wrapper-course-image > a',
dateDisplayField: '#course-card-0 .info-date-block',
enterCourseBtn: '#course-card-0 .enter-course',
availableSessions: JSON.stringify(entitlementAvailableSessions),
entitlementUUID: entitlementUUID,
currentSessionId: initialSessionId,
userId: '1',
enrollUrl: '/api/enrollment/v1/enrollment',
courseHomeUrl: '/courses/course-v1:edX+DemoX+Demo_Course/course/'
});
};
afterEach(function() {
if (view) view.remove();
});
describe('Initialization of view', function() {
it('Should create a entitlement view element', function() {
setupView(false);
expect(view).toBeDefined();
});
});
describe('Available Sessions Select - Unfulfilled Entitlement', function() {
beforeEach(function() {
setupView(false);
selectOptions = view.$('.session-select').find('option');
});
it('Select session dropdown should show all available course runs and a coming soon option.', function() {
expect(selectOptions.length).toEqual(entitlementAvailableSessions.length + 1);
});
it('Self paced courses should have visual indication in the selection option.', function() {
var selfPacedOptionIndex = _.findIndex(entitlementAvailableSessions, function(session) {
return session.pacing_type === 'self_paced';
});
var selfPacedOption = selectOptions[selfPacedOptionIndex];
expect(selfPacedOption && selfPacedOption.text.includes('(Self-paced)')).toBe(true);
});
it('Courses with an an enroll by date should indicate so on the selection option.', function() {
var enrollEndSetOptionIndex = _.findIndex(entitlementAvailableSessions, function(session) {
return session.enrollment_end !== null;
});
var enrollEndSetOption = selectOptions[enrollEndSetOptionIndex];
expect(enrollEndSetOption && enrollEndSetOption.text.includes('Open until')).toBe(true);
});
it('Title element should correctly indicate the expected behavior.', function() {
expect(view.$('.action-header').text().includes(
'To access the course, select a session.'
)).toBe(true);
});
});
describe('Available Sessions Select - Fulfilled Entitlement', function() {
beforeEach(function() {
setupView(true);
selectOptions = view.$('.session-select').find('option');
});
it('Select session dropdown should show available course runs, coming soon and leave options.', function() {
expect(selectOptions.length).toEqual(entitlementAvailableSessions.length + 2);
});
it('Select session dropdown should allow user to leave the current session.', function() {
var leaveSessionOption = selectOptions[selectOptions.length - 1];
expect(leaveSessionOption.text.includes('Leave the current session and decide later')).toBe(true);
});
it('Currently selected session should be specified in the dropdown options.', function() {
var selectedSessionIndex = _.findIndex(entitlementAvailableSessions, function(session) {
return initialSessionId === session.session_id;
});
expect(selectOptions[selectedSessionIndex].text.includes('Currently Selected')).toBe(true);
});
it('Title element should correctly indicate the expected behavior.', function() {
expect(view.$('.action-header').text().includes(
'Change to a different session or leave the current session.'
)).toBe(true);
});
});
describe('Select Session Action Button and popover behavior - Unfulfilled Entitlement', function() {
beforeEach(function() {
setupView(false);
});
it('Change session button should have the correct text.', function() {
expect(view.$('.enroll-btn-initial').text() === 'Select Session').toBe(true);
});
it('Select session button should show popover when clicked.', function() {
view.$('.enroll-btn-initial').click();
expect(view.$('.verification-modal').length > 0).toBe(true);
});
});
describe('Change Session Action Button and popover behavior - Fulfilled Entitlement', function() {
beforeEach(function() {
setupView(true);
selectOptions = view.$('.session-select').find('option');
});
it('Change session button should show correct text.', function() {
expect(view.$('.enroll-btn-initial').text().trim() === 'Change Session').toBe(true);
});
it('Switch session button should be disabled when on the currently enrolled session.', function() {
expect(view.$('.enroll-btn-initial')).toHaveClass('disabled');
});
});
});
}
);
......@@ -100,6 +100,8 @@
'string_utils': 'js/src/string_utils',
'utility': 'js/src/utility',
'draggabilly': 'js/vendor/draggabilly',
'popper': 'common/js/vendor/popper',
'bootstrap': 'common/js/vendor/bootstrap',
// Files needed by OVA
'annotator': 'js/vendor/ova/annotator-full',
......@@ -206,6 +208,13 @@
'grouping-annotator': {
deps: ['annotator']
},
'popper': {
exports: 'Popper'
},
'bootstrap': {
deps: ['jquery', 'popper'],
exports: 'bootstrap'
},
'ova': {
exports: 'ova',
deps: [
......
......@@ -762,6 +762,7 @@
'js/spec/learner_dashboard/unenroll_view_spec.js',
'js/spec/learner_dashboard/course_card_view_spec.js',
'js/spec/learner_dashboard/course_enroll_view_spec.js',
'js/spec/learner_dashboard/course_entitlement_view_spec.js',
'js/spec/markdown_editor_spec.js',
'js/spec/dateutil_factory_spec.js',
'js/spec/navigation_spec.js',
......
......@@ -163,11 +163,31 @@
display: inline-block;
}
.info-date-block {
@extend %t-title7;
color: $gray; // WCAG 2.0 AA compliant
.info-date-block-container {
display: block;
.info-date-block{
@extend %t-title7;
color: $gray; // WCAG 2.0 AA compliant
.fa-close {
color: theme-color("error");
}
.fa-check {
color: theme-color("success");
}
}
.change-session {
@extend %t-title7;
@include margin(0, 0, 0, $baseline/4);
padding: 0;
border: none;
letter-spacing: normal;
}
}
}
......@@ -633,18 +653,20 @@
.message-copy .copy {
@extend %t-copy-sub1;
margin: 2px 0 0 0;
margin: 2px 0 0;
}
// CASE: expandable
&.is-expandable {
.wrapper-tip {
.message-title, .message-copy {
.message-title,
.message-copy {
margin-bottom: 0;
display: inline-block;
}
.message-title .value, .message-copy {
.message-title .value,
.message-copy {
@include transition(color $tmg-f2 ease-in-out 0s);
}
......@@ -652,7 +674,9 @@
&:hover {
cursor: pointer;
.message-title .value, .message-copy, .ui-toggle-expansion {
.message-title .value,
.message-copy,
.ui-toggle-expansion {
color: $link-color;
}
}
......@@ -789,7 +813,7 @@
.action-view-consent {
@extend %btn-pl-white-base;
@include float(right);
&.archived {
@extend %btn-pl-default-base;
}
......@@ -1071,6 +1095,89 @@
@include padding($baseline/2, $baseline, $baseline/2, $baseline/2);
}
}
// Course Entitlement Session Selection
.course-entitlement-selection-container {
background-color: theme-color("inverse");
.action-header {
padding-bottom: $baseline/4;
font-weight: $font-weight-bold;
color: theme-color("dark");
}
.action-controls {
display: flex;
.session-select {
background-color: theme-color("inverse");
height: $baseline*1.5;
flex-grow: 5;
margin-bottom: $baseline*0.4;
}
.enroll-btn-initial {
@include margin-left($baseline);
height: $baseline*1.5;
flex-grow: 1;
letter-spacing: 0;
background: theme-color("inverse");
border-color: theme-color("primary");
color: theme-color("primary");
text-shadow: none;
font-size: $font-size-base;
padding: 0;
box-shadow: none;
border-radius: $border-radius-sm;
transition: all 0.4s ease-out;
&:hover {
background: theme-color("primary");
border-color: theme-color("primary");
color: theme-color("inverse");
}
}
@include media-breakpoint-down(xs) {
flex-direction: column;
.enroll-btn-initial {
margin: $baseline/4 0 $baseline/4;
}
}
}
.popover {
.popover-title {
margin-bottom: $baseline/2;
}
.action-items {
display: flex;
justify-content: space-between;
margin-top: $baseline/2;
.final-confirmation-btn {
box-shadow: none;
border: 1px solid theme-color("dark");
background: none;
color: theme-color("dark");
text-shadow: none;
letter-spacing: 0;
flex-grow: 1;
margin: 0 $baseline/4;
padding: $baseline/10 $baseline;
font-size: $font-size-base;
&:hover {
background: theme-color("primary");
color: theme-color("inverse");
}
}
}
}
}
}
// CASE: empty dashboard
......@@ -1323,11 +1430,11 @@ p.course-block {
padding: 6px 32px 7px;
text-align: center;
margin-top: 16px;
opacity:0.5;
background:#808080;
border:0;
opacity: 0.5;
background: #808080;
border: 0;
color: theme-color("inverse");
box-shadow:none;
box-shadow: none;
&.archived {
@include button(simple, $button-archive-color);
......@@ -1557,5 +1664,4 @@ a.fade-cover {
color: theme-color("inverse");
text-decoration: none;
}
}
......@@ -6,11 +6,16 @@
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.template import RequestContext
from entitlements.models import CourseEntitlement
import third_party_auth
from third_party_auth import pipeline
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
from openedx.core.djangolib.markup import HTML, Text
from student.models import CourseEnrollment
%>
<%
......@@ -108,7 +113,7 @@ from openedx.core.djangolib.markup import HTML, Text
<div class="my-courses" id="my-courses">
<%include file="learner_dashboard/_dashboard_navigation_courses.html"/>
% if len(course_enrollments) > 0:
% if len(course_entitlements + course_enrollments) > 0:
<ul class="listing-courses">
<%
share_settings = configuration_helpers.get_value(
......@@ -116,20 +121,53 @@ from openedx.core.djangolib.markup import HTML, Text
getattr(settings, 'SOCIAL_SHARING_SETTINGS', {})
)
%>
% for dashboard_index, enrollment in enumerate(course_enrollments):
<% show_courseware_link = (enrollment.course_id in show_courseware_links_for) %>
<% cert_status = cert_statuses.get(enrollment.course_id) %>
<% can_unenroll = (not cert_status) or cert_status.get('can_unenroll') %>
<% credit_status = credit_statuses.get(enrollment.course_id) %>
<% show_email_settings = (enrollment.course_id in show_email_settings_for) %>
<% course_mode_info = all_course_modes.get(enrollment.course_id) %>
<% is_paid_course = (enrollment.course_id in enrolled_courses_either_paid) %>
<% is_course_blocked = (enrollment.course_id in block_courses) %>
<% course_verification_status = verification_status_by_course.get(enrollment.course_id, {}) %>
<% course_requirements = courses_requirements_not_met.get(enrollment.course_id) %>
<% related_programs = inverted_programs.get(unicode(enrollment.course_id)) %>
<% show_consent_link = (enrollment.course_id in consent_required_courses) %>
<%include file='dashboard/_dashboard_course_listing.html' args='course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
% for dashboard_index, enrollment in enumerate(course_entitlements + course_enrollments):
<%
# Check if the course run is an entitlement and if it has an associated session
entitlement = enrollment if isinstance(enrollment, CourseEntitlement) else None
entitlement_session = entitlement.enrollment_course_run if entitlement else None
is_fulfilled_entitlement = True if entitlement and entitlement_session else False
is_unfulfilled_entitlement = True if entitlement and not entitlement_session else False
entitlement_available_sessions = []
if entitlement:
# Grab the available, enrollable sessions for a given entitlement and scrape them for relevant attributes
entitlement_available_sessions = [{
'session_id': course['key'],
'enrollment_end': course['enrollment_end'],
'pacing_type': course['pacing_type'],
'session_start_advertised': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
'session_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
'session_end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
} for course in course_entitlement_available_sessions[str(entitlement.uuid)]]
if is_fulfilled_entitlement:
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
enrollment = entitlement_session
else:
# If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session
upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)]
next_session = upcoming_sessions[0] if upcoming_sessions else None
if not next_session:
continue
enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type'])
session_id = enrollment.course_id
show_courseware_link = (session_id in show_courseware_links_for)
cert_status = cert_statuses.get(session_id)
can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False
credit_status = credit_statuses.get(session_id)
show_email_settings = (session_id in show_email_settings_for)
course_mode_info = all_course_modes.get(session_id)
is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid)
is_course_blocked = (session_id in block_courses)
course_verification_status = verification_status_by_course.get(session_id, {})
course_requirements = courses_requirements_not_met.get(session_id)
related_programs = inverted_programs.get(unicode(session_id))
show_consent_link = (session_id in consent_required_courses)
course_overview = enrollment.course_overview
%>
<%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
% endfor
</ul>
......
<div id="change-session-<%- entitlementUUID %>" class="message is-shown">
<div class="action-header">
<% if (currentSessionId) { %>
<%- gettext('Change to a different session or leave the current session.')%>
<% } else { %>
<%- gettext('To access the course, select a session.')%>
<% } %>
</div>
<div class="action-controls">
<select class="session-select" aria-label="<%- StringUtils.interpolate( gettext('Session Selection Dropdown for {courseName}'), { courseName: courseName }) %>">
<% _.each(availableSessions, function(session) { %>
<option data-session_id="<%- session.session_id %>">
<% if (session.session_id === currentSessionId) { %>
<%- StringUtils.interpolate( gettext('{sessionDates} - Currently Selected'), {sessionDates: session.session_dates}) %>
<% } else if (session.enrollment_end){ %>
<%- StringUtils.interpolate( gettext('{sessionDates} (Open until {enrollmentEnd})'), {sessionDates: session.session_dates, enrollmentEnd: session.enrollment_end}) %>
<% } else { %>
<%- session.session_dates %>
<% } %>
</option>
<% }) %>
<option disabled><%- gettext('More sessions coming soon') %></option>
<% if (currentSessionId){%> <option><%- gettext('Leave the current session and decide later')%></option><% } %>
</select>
<button class="enroll-btn-initial">
<% if (currentSessionId) { %>
<%- gettext('Change Session') %>
<% } else { %>
<%- gettext('Select Session') %>
<% } %>
</button>
</div>
</div>
<div class="verification-modal" role="dialog" aria-labelledby="enrollment-verification-title">
<p id="enrollment-verification-title">
<div class="popover-title">
<%- confirmationMsgTitle %>
</div>
<%- confirmationMsgBody %>
</p>
<div class="action-items">
<button type="button" class="popover-dismiss final-confirmation-btn">
<%- gettext('Cancel') %>
</button>
<button type="button" class="enroll-btn final-confirmation-btn">
<%- gettext('OK') %>
</button>
</div>
</div>
\ No newline at end of file
......@@ -233,7 +233,7 @@ def get_course_runs_for_course(course_uuid):
resource_id=course_uuid,
api=api,
cache_key=cache_key if catalog_integration.is_cache_enabled else None,
long_term_cache=True
long_term_cache=True,
)
return data.get('course_runs', [])
else:
......
......@@ -9,9 +9,14 @@ import third_party_auth
from third_party_auth import pipeline
from django.core.urlresolvers import reverse
import json
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.theming import helpers as theming_helpers
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
from openedx.core.djangolib.markup import HTML, Text
from openedx.core.djangoapps.theming import helpers as theming_helpers
from entitlements.models import CourseEntitlement
from student.models import CourseEnrollment
%>
<%
......@@ -108,28 +113,58 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
<header class="wrapper-header-courses">
<h2 class="header-courses">${_("My Courses")}</h2>
</header>
% if len(course_entitlements + course_enrollments) > 0:
<ul class="listing-courses">
<% share_settings = getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}) %>
% for dashboard_index, enrollment in enumerate(course_entitlements + course_enrollments):
<%
# Check if the course run is an entitlement and if it has an associated session
entitlement = enrollment if isinstance(enrollment, CourseEntitlement) else None
entitlement_session = entitlement.enrollment_course_run if entitlement else None
is_fulfilled_entitlement = True if entitlement and entitlement_session else False
is_unfulfilled_entitlement = True if entitlement and not entitlement_session else False
% if len(course_enrollments) > 0:
<ul class="listing-courses">
<% share_settings = getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}) %>
% for dashboard_index, enrollment in enumerate(course_enrollments):
<% show_courseware_link = (enrollment.course_id in show_courseware_links_for) %>
<% cert_status = cert_statuses.get(enrollment.course_id) %>
<% can_unenroll = (not cert_status) or cert_status.get('can_unenroll') %>
<% credit_status = credit_statuses.get(enrollment.course_id) %>
<% show_email_settings = (enrollment.course_id in show_email_settings_for) %>
<% course_mode_info = all_course_modes.get(enrollment.course_id) %>
<% is_paid_course = (enrollment.course_id in enrolled_courses_either_paid) %>
<% is_course_blocked = (enrollment.course_id in block_courses) %>
<% course_verification_status = verification_status_by_course.get(enrollment.course_id, {}) %>
<% course_requirements = courses_requirements_not_met.get(enrollment.course_id) %>
<% related_programs = inverted_programs.get(unicode(enrollment.course_id)) %>
<% show_consent_link = (enrollment.course_id in consent_required_courses) %>
<%include file = 'dashboard/_dashboard_course_listing.html' args='course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
% endfor
entitlement_available_sessions = []
if entitlement:
# Grab the available, enrollable sessions for a given entitlement and scrape them for relevant attributes
entitlement_available_sessions = [{
'session_id': course['key'],
'enrollment_end': course['enrollment_end'],
'pacing_type': course['pacing_type'],
'session_start_advertised': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
'session_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
'session_end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
} for course in course_entitlement_available_sessions[str(entitlement.uuid)]]
if is_fulfilled_entitlement:
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
enrollment = entitlement_session
else:
# If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session
upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)]
next_session = upcoming_sessions[0] if upcoming_sessions else None
if not next_session:
continue
enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type'])
</ul>
session_id = enrollment.course_id
show_courseware_link = (session_id in show_courseware_links_for)
cert_status = cert_statuses.get(session_id)
can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False
credit_status = credit_statuses.get(session_id)
show_email_settings = (session_id in show_email_settings_for)
course_mode_info = all_course_modes.get(session_id)
is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid)
is_course_blocked = (session_id in block_courses)
course_verification_status = verification_status_by_course.get(session_id, {})
course_requirements = courses_requirements_not_met.get(session_id)
related_programs = inverted_programs.get(unicode(session_id))
show_consent_link = (session_id in consent_required_courses)
course_overview = enrollment.course_overview
%>
<%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' />
% endfor
</ul>
% else:
<section class="empty-dashboard-message">
<p>${_("You are not enrolled in any courses yet.")}</p>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment