Render credit pathways in program details sidebar
This commit is contained in:
@@ -12,6 +12,7 @@ from web_fragments.fragment import Fragment
|
||||
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.learner_dashboard.utils import FAKE_COURSE_KEY, strip_course_id
|
||||
from openedx.core.djangoapps.catalog.utils import get_credit_pathways
|
||||
from openedx.core.djangoapps.credentials.utils import get_credentials_records_url
|
||||
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
@@ -106,6 +107,16 @@ class ProgramDetailsFragmentView(EdxFragmentView):
|
||||
if not certificate_data:
|
||||
program_record_url = None
|
||||
|
||||
credit_pathways = []
|
||||
try:
|
||||
for pathway_id in program_data['pathway_ids']:
|
||||
pathway = get_credit_pathways(request.site, pathway_id)
|
||||
if pathway and pathway['email']:
|
||||
credit_pathways.append(pathway)
|
||||
# if pathway caching did not complete fully (no pathway_ids)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
urls = {
|
||||
'program_listing_url': reverse('program_listing_view'),
|
||||
'track_selection_url': strip_course_id(
|
||||
@@ -121,7 +132,8 @@ class ProgramDetailsFragmentView(EdxFragmentView):
|
||||
'user_preferences': get_user_preferences(request.user),
|
||||
'program_data': program_data,
|
||||
'course_data': course_data,
|
||||
'certificate_data': certificate_data
|
||||
'certificate_data': certificate_data,
|
||||
'credit_pathways': credit_pathways,
|
||||
}
|
||||
|
||||
html = render_to_string('learner_dashboard/program_details_fragment.html', context)
|
||||
|
||||
@@ -14,7 +14,12 @@ from django.urls import reverse, reverse_lazy
|
||||
from django.test import override_settings
|
||||
|
||||
from lms.envs.test import CREDENTIALS_PUBLIC_SERVICE_URL
|
||||
from openedx.core.djangoapps.catalog.tests.factories import CourseFactory, CourseRunFactory, ProgramFactory
|
||||
from openedx.core.djangoapps.catalog.tests.factories import (
|
||||
CreditPathwayFactory,
|
||||
CourseFactory,
|
||||
CourseRunFactory,
|
||||
ProgramFactory
|
||||
)
|
||||
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
|
||||
from openedx.core.djangoapps.credentials import STUDENT_RECORDS_FLAG
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
|
||||
@@ -25,6 +30,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory
|
||||
|
||||
PROGRAMS_UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
PROGRAMS_MODULE = 'lms.djangoapps.learner_dashboard.programs'
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@@ -175,6 +181,7 @@ class TestProgramListing(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@mock.patch(PROGRAMS_MODULE + '.get_credit_pathways')
|
||||
@mock.patch(PROGRAMS_UTILS_MODULE + '.get_programs')
|
||||
@override_waffle_flag(STUDENT_RECORDS_FLAG, active=True)
|
||||
class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, SharedModuleStoreTestCase):
|
||||
@@ -192,7 +199,11 @@ class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, Shared
|
||||
course_run = CourseRunFactory(key=unicode(modulestore_course.id)) # pylint: disable=no-member
|
||||
course = CourseFactory(course_runs=[course_run])
|
||||
|
||||
cls.data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
|
||||
cls.program_data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
|
||||
cls.pathway_data = CreditPathwayFactory()
|
||||
cls.program_data['pathway_ids'] = [cls.pathway_data['id']]
|
||||
cls.pathway_data['program_uuids'] = [cls.program_data['uuid']]
|
||||
del cls.pathway_data['programs']
|
||||
|
||||
def setUp(self):
|
||||
super(TestProgramDetails, self).setUp()
|
||||
@@ -207,7 +218,7 @@ class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, Shared
|
||||
self.assertContains(response,
|
||||
'"program_record_url": "{}/records/programs/'.format(CREDENTIALS_PUBLIC_SERVICE_URL))
|
||||
self.assertContains(response, 'program_listing_url')
|
||||
self.assertContains(response, self.data['title'])
|
||||
self.assertContains(response, self.program_data['title'])
|
||||
self.assert_programs_tab_present(response)
|
||||
|
||||
def assert_programs_tab_present(self, response):
|
||||
@@ -217,7 +228,11 @@ class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, Shared
|
||||
any(soup.find_all('a', class_='tab-nav-link', href=reverse('program_listing_view')))
|
||||
)
|
||||
|
||||
def test_login_required(self, mock_get_programs):
|
||||
def assert_pathway_data_present(self, response):
|
||||
self.assertContains(response, 'creditPathways')
|
||||
self.assertContains(response, self.pathway_data['name'])
|
||||
|
||||
def test_login_required(self, mock_get_programs, mock_get_credit_pathways):
|
||||
"""
|
||||
Verify that login is required to access the page.
|
||||
"""
|
||||
@@ -226,7 +241,8 @@ class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, Shared
|
||||
catalog_integration = self.create_catalog_integration()
|
||||
UserFactory(username=catalog_integration.service_username)
|
||||
|
||||
mock_get_programs.return_value = self.data
|
||||
mock_get_programs.return_value = self.program_data
|
||||
mock_get_credit_pathways.return_value = self.pathway_data
|
||||
|
||||
self.client.logout()
|
||||
|
||||
@@ -243,8 +259,9 @@ class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, Shared
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assert_program_data_present(response)
|
||||
self.assert_pathway_data_present(response)
|
||||
|
||||
def test_404_if_disabled(self, _mock_get_programs):
|
||||
def test_404_if_disabled(self, _mock_get_programs, _mock_get_credit_pathways):
|
||||
"""
|
||||
Verify that the page 404s if disabled.
|
||||
"""
|
||||
@@ -253,7 +270,7 @@ class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, Shared
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_404_if_no_data(self, mock_get_programs):
|
||||
def test_404_if_no_data(self, mock_get_programs, _mock_get_credit_pathways):
|
||||
"""Verify that the page 404s if no program data is found."""
|
||||
self.create_programs_config()
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -59,6 +59,7 @@ describe('Program Details Header View', () => {
|
||||
full_program_price: 300,
|
||||
card_image_url: 'some image',
|
||||
faq: [],
|
||||
pathway_ids: [2],
|
||||
price_ranges: [
|
||||
{
|
||||
max: 378,
|
||||
@@ -473,6 +474,17 @@ describe('Program Details Header View', () => {
|
||||
userPreferences: {
|
||||
'pref-lang': 'en',
|
||||
},
|
||||
creditPathways: [
|
||||
{
|
||||
org_name: 'Test Org Name',
|
||||
email: 'test@test.com',
|
||||
name: 'Name of Test Pathway',
|
||||
program_uuids: ['0ffff5d6-0177-4690-9a48-aa2fecf94610'],
|
||||
description: 'Test credit pathway description',
|
||||
id: 2,
|
||||
destination_url: 'edx.org',
|
||||
},
|
||||
],
|
||||
};
|
||||
const data = options.programData;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class ProgramDetailsSidebarView extends Backbone.View {
|
||||
this.certificateCollection = options.certificateCollection || [];
|
||||
this.programCertificate = this.getProgramCertificate();
|
||||
this.programRecordUrl = options.programRecordUrl;
|
||||
this.creditPathways = options.creditPathways;
|
||||
this.render();
|
||||
}
|
||||
|
||||
@@ -25,6 +26,7 @@ class ProgramDetailsSidebarView extends Backbone.View {
|
||||
programCertificate: this.programCertificate ?
|
||||
this.programCertificate.toJSON() : {},
|
||||
programRecordUrl: this.programRecordUrl,
|
||||
creditPathways: this.creditPathways,
|
||||
});
|
||||
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
|
||||
@@ -113,6 +113,7 @@ class ProgramDetailsView extends Backbone.View {
|
||||
courseModel: this.courseData,
|
||||
certificateCollection: this.certificateCollection,
|
||||
programRecordUrl: this.options.urls.program_record_url,
|
||||
creditPathways: this.options.creditPathways,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -658,7 +658,7 @@ $btn-color-primary: palette(primary, dark);
|
||||
}
|
||||
}
|
||||
|
||||
.record-heading {
|
||||
.divider-heading {
|
||||
font-family: $font-family-sans-serif;
|
||||
font-weight: bold;
|
||||
color: palette(primary, dark);
|
||||
@@ -669,14 +669,35 @@ $btn-color-primary: palette(primary, dark);
|
||||
margin: $baseline*0.5 0 $baseline 0;
|
||||
}
|
||||
|
||||
.view-record-wrapper {
|
||||
.sidebar-button-wrapper {
|
||||
text-align: right;
|
||||
|
||||
.view-record-button {
|
||||
.sidebar-button {
|
||||
font-size: 0.9375em;
|
||||
}
|
||||
}
|
||||
|
||||
.pathway-wrapper {
|
||||
@include margin-left($baseline*0.75);
|
||||
|
||||
width: $baseline*15.5;
|
||||
border-bottom: 1px solid $gray-l4;
|
||||
|
||||
@media (min-width: $bp-screen-sm) {
|
||||
width: auto;
|
||||
}
|
||||
.pathway-info {
|
||||
margin: 15px 0;
|
||||
font-size: 0.9375em;
|
||||
color: #414141;
|
||||
|
||||
.pathway-heading {
|
||||
font-weight: bold;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $bp-screen-md) {
|
||||
@include float(right);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ ProgramDetailsFactory({
|
||||
certificateData: ${certificate_data | n, dump_js_escaped_json},
|
||||
urls: ${urls | n, dump_js_escaped_json},
|
||||
userPreferences: ${user_preferences | n, dump_js_escaped_json},
|
||||
creditPathways: ${credit_pathways | n, dump_js_escaped_json},
|
||||
});
|
||||
</%static:webpack>
|
||||
</%block>
|
||||
|
||||
@@ -10,15 +10,41 @@
|
||||
<aside class="aside js-course-certificates"></aside>
|
||||
<% if (programRecordUrl) { %>
|
||||
<aside class="aside js-program-record program-record">
|
||||
<h2 class="record-heading"><%- gettext('Program Record') %></h2>
|
||||
|
||||
<h2 class="divider-heading"><%- gettext('Program Record') %></h2>
|
||||
|
||||
<div class="motivating-section">
|
||||
<p class="motivating-message"><%- gettext('Once you complete one of the program requirements you have a program record. This record is marked complete once you meet all program requirements. A program record can be used to continue your learning journey and demonstrate your learning to others.') %></p>
|
||||
</div>
|
||||
<div class="view-record-wrapper">
|
||||
<div class="sidebar-button-wrapper">
|
||||
<a href="<%- programRecordUrl %>" class="program-record-link">
|
||||
<button class="btn view-record-button"><%- gettext('View Program Record') %></button>
|
||||
<button class="btn sidebar-button"><%- gettext('View Program Record') %></button>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
<% } %>
|
||||
<% if (creditPathways.length > 0) { %>
|
||||
<aside class="aside js-program-pathways program-pathways">
|
||||
<h2 class = "divider-heading"><%- gettext('Additional Learning Opportunities') %></h2>
|
||||
|
||||
<% for (var i = 0; i < creditPathways.length; i++) {
|
||||
var pathway = creditPathways[i];
|
||||
%>
|
||||
<div class="pathway-wrapper">
|
||||
<div class = "pathway-info">
|
||||
<h2 class="pathway-heading"> <%- pathway.name %> </h2>
|
||||
<% if (pathway.description) { %>
|
||||
<p> <%- pathway.description %> </p>
|
||||
<% } %>
|
||||
<% if (pathway.destination_url) { %>
|
||||
<div class="sidebar-button-wrapper">
|
||||
<a href="<%- pathway.destination_url %>" class="pathway-link">
|
||||
<button class="btn sidebar-button"><%- gettext('Learn More') %></button>
|
||||
</a>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</aside>
|
||||
<% } %>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user