405 lines
21 KiB
HTML
405 lines
21 KiB
HTML
<%page expression_filter="h"/>
|
||
<%inherit file="main.html" />
|
||
<%def name="online_help_token()"><% return "learnerdashboard" %></%def>
|
||
<%namespace name='static' file='static_content.html'/>
|
||
<%!
|
||
import pytz
|
||
import six
|
||
from datetime import datetime, timedelta
|
||
from django.urls import reverse
|
||
from django.utils.translation import gettext as _
|
||
from django.template import RequestContext
|
||
from common.djangoapps.entitlements.models import CourseEntitlement
|
||
from common.djangoapps.third_party_auth import pipeline
|
||
from common.djangoapps.util.date_utils import strftime_localized
|
||
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 common.djangoapps.student.models import CourseEnrollment
|
||
%>
|
||
|
||
<%
|
||
cert_name_short = settings.CERT_NAME_SHORT
|
||
cert_name_long = settings.CERT_NAME_LONG
|
||
%>
|
||
|
||
|
||
<%block name="pagetitle">${_("Dashboard")}</%block>
|
||
<%block name="bodyclass">view-dashboard is-authenticated</%block>
|
||
|
||
<%block name="header_extras">
|
||
% for template_name in ["donation"]:
|
||
<script type="text/template" id="${template_name}-tpl">
|
||
<%static:include path="dashboard/${template_name}.underscore" />
|
||
</script>
|
||
% endfor
|
||
</%block>
|
||
|
||
<%block name="js_extra">
|
||
<script src="${static.url('js/commerce/credit.js')}"></script>
|
||
<script type="text/javascript" src="${static.url('js/learner_dashboard/certificate_api.js')}"></script>
|
||
<%static:js group='dashboard'/>
|
||
<script type="text/javascript">
|
||
$(document).ready(function() {
|
||
edx.dashboard.legacy.init({
|
||
dashboard: "${reverse('dashboard') | n, js_escaped_string}",
|
||
signInUser: "${reverse('signin_user') | n, js_escaped_string}",
|
||
changeEmailSettings: "${reverse('change_email_settings') | n, js_escaped_string}",
|
||
sendAccountActivationEmail: "${reverse('send_account_activation_email') | n, js_escaped_string}"
|
||
|
||
});
|
||
});
|
||
</script>
|
||
<%static:webpack entry="UnenrollmentFactory">
|
||
UnenrollmentFactory({
|
||
urls: {
|
||
dashboard: "${reverse('dashboard') | n, js_escaped_string}",
|
||
signInUser: "${reverse('signin_user') | n, js_escaped_string}",
|
||
changeEmailSettings: "${reverse('change_email_settings') | n, js_escaped_string}",
|
||
browseCourses: "${marketing_link('COURSES') | n, js_escaped_string}"
|
||
},
|
||
isEdx: false
|
||
});
|
||
</%static:webpack>
|
||
<%static:webpack entry="EntitlementUnenrollmentFactory">
|
||
## Wait until the document is fully loaded before initializing the EntitlementUnenrollmentView
|
||
## to ensure events are setup correctly.
|
||
$(document).ready(function() {
|
||
EntitlementUnenrollmentFactory({
|
||
dashboardPath: "${reverse('dashboard') | n, js_escaped_string}",
|
||
signInPath: "${reverse('signin_user') | n, js_escaped_string}",
|
||
browseCourses: "${marketing_link('COURSES') | n, js_escaped_string}",
|
||
isEdx: false
|
||
});
|
||
});
|
||
</%static:webpack>
|
||
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
|
||
<%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory">
|
||
DashboardSearchFactory();
|
||
</%static:require_module>
|
||
% endif
|
||
% if redirect_message:
|
||
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
|
||
var banner = new MessageBannerView({urgency: 'low', type: 'warning'});
|
||
$('#content').prepend(banner.$el);
|
||
banner.showMessage(${redirect_message | n, dump_js_escaped_json})
|
||
</%static:require_module>
|
||
% endif
|
||
% if recovery_email_message:
|
||
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
|
||
var banner = new MessageBannerView({urgency: 'low', type: 'warning', hideCloseBtn: false, isRecoveryEmailMsg: true});
|
||
$('#content').prepend(banner.$el);
|
||
banner.showMessage(${recovery_email_message | n, dump_js_escaped_json})
|
||
</%static:require_module>
|
||
% endif
|
||
% if recovery_email_activation_message:
|
||
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
|
||
var banner = new MessageBannerView({urgency: 'low', type: 'warning', isRecoveryEmailMsg: true});
|
||
$('#content').prepend(banner.$el);
|
||
banner.showMessage(${recovery_email_activation_message | n, dump_js_escaped_json})
|
||
</%static:require_module>
|
||
% endif
|
||
% if enterprise_learner_portal_enabled_message:
|
||
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
|
||
var banner = new MessageBannerView({urgency: 'low', type: 'warning', isLearnerPortalEnabled: true});
|
||
$('#content').prepend(banner.$el);
|
||
banner.showMessage(${enterprise_learner_portal_enabled_message | n, dump_js_escaped_json})
|
||
</%static:require_module>
|
||
% endif
|
||
</%block>
|
||
|
||
<div class="dashboard-notifications" tabindex="-1">
|
||
|
||
%if banner_account_activation_message:
|
||
<div class="dashboard-banner">
|
||
${banner_account_activation_message | n, decode.utf8}
|
||
</div>
|
||
%endif
|
||
|
||
%if enrollment_message:
|
||
<div class="dashboard-banner">
|
||
${enrollment_message | n, decode.utf8}
|
||
</div>
|
||
%endif
|
||
|
||
%if enterprise_message:
|
||
<div class="dashboard-banner">
|
||
${ enterprise_message | n, decode.utf8 }
|
||
</div>
|
||
%endif
|
||
|
||
%if account_activation_messages:
|
||
<div class="activation-message-container">
|
||
% for account_activation_message in account_activation_messages:
|
||
<div class="account-activation ${account_activation_message.tags}" role="alert" aria-label="Account Activation Message" tabindex="-1">
|
||
<div class="message-copy" >
|
||
${ account_activation_message | n, decode.utf8 }
|
||
</div>
|
||
</div>
|
||
% endfor
|
||
</div>
|
||
%endif
|
||
|
||
</div>
|
||
|
||
<main id="main" aria-label="Content" tabindex="-1">
|
||
<div class="dashboard" id="dashboard-main">
|
||
<div class="main-container">
|
||
<div class="my-courses" id="my-courses">
|
||
% if display_dashboard_courses:
|
||
<%include file="learner_dashboard/_dashboard_navigation_courses.html"/>
|
||
% endif
|
||
|
||
% if len(course_entitlements + course_enrollments) > 0:
|
||
<ul class="listing-courses">
|
||
<%
|
||
share_settings = configuration_helpers.get_value(
|
||
'SOCIAL_SHARING_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
|
||
entitlement_days_until_expiration = entitlement.get_days_until_expiration() if entitlement else None
|
||
entitlement_expiration = datetime.now(tz=pytz.UTC) + timedelta(days=entitlement_days_until_expiration) if (entitlement and entitlement_days_until_expiration < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD) else None
|
||
entitlement_expiration_date = strftime_localized(entitlement_expiration, 'SHORT_DATE') if entitlement and entitlement_expiration else None
|
||
entitlement_expired_at = strftime_localized(entitlement.expired_at_datetime, 'SHORT_DATE') if entitlement and entitlement.expired_at_datetime 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'],
|
||
'advertised_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
|
||
'start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
|
||
'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 to populate card with metadata
|
||
pseudo_session = unfulfilled_entitlement_pseudo_sessions[str(entitlement.uuid)]
|
||
if not pseudo_session:
|
||
continue
|
||
pseudo_key = pseudo_session['key']
|
||
if not isinstance(pseudo_key, CourseKey):
|
||
pseudo_key = CourseKey.from_string(pseudo_session['key'])
|
||
enrollment = CourseEnrollment(user=user, course=CourseOverview.get_from_id(pseudo_key), mode=pseudo_session['type'])
|
||
# We only show email settings for entitlement cards if the entitlement has an associated enrollment
|
||
show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for)
|
||
course_overview = enrollment.course_overview
|
||
else:
|
||
show_email_settings = (enrollment.course_id in show_email_settings_for)
|
||
course_overview = CourseOverview.get_from_id(enrollment.course_id)
|
||
|
||
session_id = enrollment.course_id
|
||
show_courseware_link = show_courseware_links_for.get(session_id, False)
|
||
cert_status = cert_statuses.get(session_id)
|
||
can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable()
|
||
partner_managed_enrollment = enrollment.mode == 'masters'
|
||
# checks if we can unenroll based on the value of partner_managed_enrollment
|
||
can_unenroll_partner_managed_enrollment = False if partner_managed_enrollment else (not cert_status)
|
||
# checks if we can unenroll based on the value of unfulfilled_entitlement
|
||
can_unenroll_unfulfilled_entitlement = cert_status.get('can_unenroll') if cert_status and not unfulfilled_entitlement else False
|
||
# compares the three different parameters by which we can unenroll
|
||
can_unenroll = (can_unenroll_partner_managed_enrollment or can_unenroll_unfulfilled_entitlement) and not disable_unenrollment
|
||
credit_status = credit_statuses.get(session_id)
|
||
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_voucher_refundable = (session_id in enrolled_courses_voucher_refundable)
|
||
course_requirements = courses_requirements_not_met.get(session_id)
|
||
related_programs = inverted_programs.get(six.text_type(entitlement.course_uuid if is_unfulfilled_entitlement else session_id))
|
||
show_consent_link = (session_id in consent_required_courses)
|
||
resume_button_url = resume_button_urls[dashboard_index]
|
||
%>
|
||
<%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, enrollments_fbe_is_on=enrollments_fbe_is_on, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_refund_entitlement=can_refund_entitlement, 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_voucher_refundable=is_course_voucher_refundable, 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, resume_button_url=resume_button_url, partner_managed_enrollment=partner_managed_enrollment' />
|
||
% endfor
|
||
% if show_load_all_courses_link:
|
||
<br/>
|
||
${len(course_enrollments)} ${_("results successfully populated,")}
|
||
<a href="${reverse('dashboard')}?course_limit=None">
|
||
${_("Click to load all enrolled courses")}
|
||
</a>
|
||
% endif
|
||
</ul>
|
||
% else:
|
||
<div class="empty-dashboard-message">
|
||
% if display_dashboard_courses:
|
||
<p>${_("You are not enrolled in any courses yet.")}</p>
|
||
% if empty_dashboard_message:
|
||
<p class="custom-message">${empty_dashboard_message | n, decode.utf8}</p>
|
||
%endif
|
||
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
|
||
<a class="btn btn-primary" href="${marketing_link('COURSES')}">
|
||
${_("Explore courses")}
|
||
</a>
|
||
%endif
|
||
% else:
|
||
<p>${_("Activate your account!")}</p>
|
||
<p class="custom-message">${ activate_account_message | n, decode.utf8 }</p>
|
||
% endif
|
||
</div>
|
||
% endif
|
||
|
||
% if staff_access and len(errored_courses) > 0:
|
||
<div id="course-errors">
|
||
<h2>${_("Course-loading errors")}</h2>
|
||
|
||
% for course_dir, errors in errored_courses.items():
|
||
<h3>${course_dir}</h3>
|
||
<ul>
|
||
% for (msg, err) in errors:
|
||
<li>${msg}
|
||
<ul><li><pre>${err}</pre></li></ul>
|
||
</li>
|
||
% endfor
|
||
</ul>
|
||
% endfor
|
||
</div>
|
||
% endif
|
||
</div>
|
||
</div>
|
||
<div class="side-container" role="complementary" aria-label="messages">
|
||
%if display_sidebar_account_activation_message:
|
||
<div class="sidebar-notification">
|
||
<%include file="${static.get_template_path('registration/account_activation_sidebar_notice.html')}" />
|
||
</div>
|
||
%endif
|
||
|
||
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
|
||
<div id="dashboard-search-bar" class="search-bar dashboard-search-bar" role="search" aria-label="Dashboard">
|
||
<form class="search-form">
|
||
<label for="dashboard-search-input">${_('Search Your Courses')}</label>
|
||
<div class="search-field-wrapper">
|
||
<input id="dashboard-search-input" type="text" class="search-field"/>
|
||
<button type="submit" class="search-button" title="${_('Search')}">
|
||
<span class="icon fa fa-search" aria-hidden="true"></span>
|
||
</button>
|
||
<button type="button" class="cancel-button" title="${_('Clear search')}">
|
||
<span class="icon fa fa-remove" aria-hidden="true"></span>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div id="dashboard-search-results" class="search-results dashboard-search-results"></div>
|
||
% endif
|
||
|
||
<%block name="skip_links">
|
||
% if settings.FEATURES.get('ENABLE_ANNOUNCEMENTS'):
|
||
<a id="announcements-skip" class="nav-skip sr-only sr-only-focusable" href="#announcements">${_("Skip to list of announcements")}</a>
|
||
% endif
|
||
</%block>
|
||
% if settings.FEATURES.get('ENABLE_ANNOUNCEMENTS'):
|
||
<%include file='dashboard/_dashboard_announcements.html' />
|
||
% endif
|
||
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
%if show_account_activation_popup:
|
||
<div id="activate-account-modal" class="modal activate-account-modal" aria-hidden="true" tabindex=0 >
|
||
<div class="inner-wrapper" role="dialog" aria-labelledby="activate-account-modal-title" aria-live="polite">
|
||
<h3>
|
||
${_("Activate your account so you can log back in")}
|
||
<span class="sr">,
|
||
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
|
||
${_("window open")}
|
||
</span>
|
||
</h3>
|
||
<p class="activate-account-modal-body">${Text(_("We sent an email to {strong_start}{email}{strong_end} with a link to activate your account. Can’t find it? Check your spam folder or {link_start}resend the email{link_end}.")).format(
|
||
strong_start=HTML('<strong>'),
|
||
email=user.email,
|
||
strong_end=HTML('</strong>'),
|
||
link_start=HTML('<a href="#" id="send_cta_email" >'),
|
||
link_end=HTML('</a>')
|
||
)}
|
||
</p>
|
||
<div class="activate-account-modal-button">
|
||
<button class="btn btn-primary" id="button">
|
||
${Text(_("Continue to {platform_name}")).format(platform_name=settings.PLATFORM_NAME)}
|
||
<svg style="vertical-align:bottom" width="24" height="24" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
%endif
|
||
|
||
<div id="email-settings-modal" class="modal" aria-hidden="true">
|
||
<div class="inner-wrapper" role="dialog" aria-labelledby="email-settings-title">
|
||
<button class="close-modal">
|
||
<span class="icon fa fa-remove" aria-hidden="true"></span>
|
||
<span class="sr">
|
||
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
|
||
${_("Close")}
|
||
</span>
|
||
</button>
|
||
|
||
<header>
|
||
<h2 id="email-settings-title">
|
||
${Text(_("Email Settings for {course_number}")).format(course_number=HTML('<span id="email_settings_course_number"></span>'))}
|
||
<span class="sr">,
|
||
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
|
||
${_("window open")}
|
||
</span>
|
||
</h2>
|
||
<hr/>
|
||
</header>
|
||
|
||
<form id="email_settings_form" method="post">
|
||
<input name="course_id" id="email_settings_course_id" type="hidden" />
|
||
<label><input type="checkbox" id="receive_emails" name="receive_emails" />${_("Receive course emails")} </label>
|
||
<div class="submit">
|
||
<input type="submit" id="submit" value="${_("Save Settings")}" />
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">
|
||
<div class="inner-wrapper" role="dialog" aria-labelledby="unenrollment-modal-title" aria-live="polite">
|
||
<button class="close-modal">
|
||
<span class="icon fa fa-remove" aria-hidden="true"></span>
|
||
<span class="sr">
|
||
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
|
||
${_("Close")}
|
||
</span>
|
||
</button>
|
||
|
||
<header class="unenroll-header">
|
||
<h2 id="unenrollment-modal-title">
|
||
<span id='track-info'></span>
|
||
<span id='refund-info'></span>
|
||
<span class="sr">,
|
||
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
|
||
${_("window open")}
|
||
</span>
|
||
</h2>
|
||
<hr/>
|
||
</header>
|
||
<div id="unenroll_error" class="modal-form-error"></div>
|
||
<form id="unenroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
|
||
<input name="course_id" id="unenroll_course_id" type="hidden" />
|
||
<input name="enrollment_action" type="hidden" value="unenroll" />
|
||
<div class="submit">
|
||
<input class="submit-button" name="submit" type="submit" value="${_("Unenroll")}" />
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<%include file="dashboard/_dashboard_entitlement_unenrollment_modal.html"/>
|