From 51815136293b825ddd94d411383b5bed7c367307 Mon Sep 17 00:00:00 2001 From: Ahsan Ulhaq Date: Fri, 25 Dec 2015 20:22:52 +0500 Subject: [PATCH] Show message for earned programs credentials ECOM-3015 --- .../contentstore/views/tests/test_programs.py | 16 +- common/djangoapps/student/tests/tests.py | 8 +- common/djangoapps/student/views.py | 34 +++++ lms/static/sass/multicourse/_dashboard.scss | 26 ++++ lms/templates/dashboard.html | 13 ++ openedx/core/djangoapps/credentials/models.py | 3 + .../djangoapps/credentials/tests/mixins.py | 137 +++++++++++++++++- .../credentials/tests/test_models.py | 14 +- .../credentials/tests/test_utils.py | 88 +++++++++++ openedx/core/djangoapps/credentials/utils.py | 61 ++++++++ openedx/core/djangoapps/programs/models.py | 1 + .../core/djangoapps/programs/tests/mixins.py | 25 +++- .../djangoapps/programs/tests/test_models.py | 18 +-- .../djangoapps/programs/tests/test_utils.py | 119 +++++++-------- openedx/core/djangoapps/programs/utils.py | 70 ++++----- openedx/core/lib/api_utils.py | 72 +++++++++ openedx/core/lib/tests/test_api_utils.py | 114 +++++++++++++++ 17 files changed, 693 insertions(+), 126 deletions(-) create mode 100644 openedx/core/djangoapps/credentials/tests/test_utils.py create mode 100644 openedx/core/djangoapps/credentials/utils.py create mode 100644 openedx/core/lib/api_utils.py create mode 100644 openedx/core/lib/tests/test_api_utils.py diff --git a/cms/djangoapps/contentstore/views/tests/test_programs.py b/cms/djangoapps/contentstore/views/tests/test_programs.py index 480a9cab01..2bca59f43c 100644 --- a/cms/djangoapps/contentstore/views/tests/test_programs.py +++ b/cms/djangoapps/contentstore/views/tests/test_programs.py @@ -21,6 +21,8 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule ClientFactory(name=ProgramsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL) + self.create_programs_config() + self.staff = UserFactory(is_staff=True) self.client.login(username=self.staff.username, password='test') @@ -29,7 +31,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule @httpretty.activate def test_programs_config_disabled(self): """Verify that the programs tab and creation button aren't rendered when config is disabled.""" - self.create_config(enable_studio_tab=False) + self.create_programs_config(enable_studio_tab=False) self.mock_programs_api() response = self.client.get(self.studio_home) @@ -48,7 +50,6 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule student = UserFactory(is_staff=False) self.client.login(username=student.username, password='test') - self.create_config() self.mock_programs_api() response = self.client.get(self.studio_home) @@ -57,7 +58,6 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule @httpretty.activate def test_programs_displayed(self): """Verify that the programs tab and creation button can be rendered when config is enabled.""" - self.create_config() # When no data is provided, expect creation prompt. self.mock_programs_api(data={'results': []}) @@ -102,7 +102,7 @@ class TestProgramAuthoringView(ProgramsApiConfigMixin, SharedModuleStoreTestCase def test_authoring_header(self): """Verify that the header contains the expected text.""" self.client.login(username=self.staff.username, password='test') - self.create_config() + self.create_programs_config() response = self._assert_status(200) self.assertIn("Program Administration", response.content) @@ -116,7 +116,7 @@ class TestProgramAuthoringView(ProgramsApiConfigMixin, SharedModuleStoreTestCase self._assert_status(404) # Enable Programs authoring interface - self.create_config() + self.create_programs_config() student = UserFactory(is_staff=False) self.client.login(username=student.username, password='test') @@ -134,13 +134,13 @@ class TestProgramsIdTokenView(ProgramsApiConfigMixin, SharedModuleStoreTestCase) def test_config_disabled(self): """Ensure the endpoint returns 404 when Programs authoring is disabled.""" - self.create_config(enable_studio_tab=False) + self.create_programs_config(enable_studio_tab=False) response = self.client.get(self.path) self.assertEqual(response.status_code, 404) def test_not_logged_in(self): """Ensure the endpoint denies access to unauthenticated users.""" - self.create_config() + self.create_programs_config() self.client.logout() response = self.client.get(self.path) self.assertEqual(response.status_code, 302) @@ -152,7 +152,7 @@ class TestProgramsIdTokenView(ProgramsApiConfigMixin, SharedModuleStoreTestCase) Ensure the endpoint responds with a valid JSON payload when authoring is enabled. """ - self.create_config() + self.create_programs_config() response = self.client.get(self.path) self.assertEqual(response.status_code, 200) payload = json.loads(response.content) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index b346e16bcd..986a97f3c4 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -1035,7 +1035,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): CourseEnrollment.enroll(self.user, self.course_2.id, mode=course_mode) self.client.login(username="jack", password="test") - self.create_config() + self.create_programs_config() with patch('student.views.get_programs_for_dashboard') as mock_data: mock_data.return_value = self._create_program_data( @@ -1068,7 +1068,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): CourseEnrollment.enroll(self.user, self.course_1.id, mode='verified') self.client.login(username="jack", password="test") - self.create_config() + self.create_programs_config() with patch( 'student.views.get_programs_for_dashboard', @@ -1098,7 +1098,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): CourseEnrollment.enroll(self.user, self.course_3.id, mode='honor') self.client.login(username="jack", password="test") - self.create_config() + self.create_programs_config() with patch('student.views.get_programs_for_dashboard') as mock_data: mock_data.return_value = self._create_program_data( @@ -1119,7 +1119,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): CourseEnrollment.enroll(self.user, self.course_1.id) self.client.login(username="jack", password="test") - self.create_config() + self.create_programs_config() program_data = self._create_program_data([(self.course_1.id, 'active')]) if key_remove and key_remove in program_data[unicode(self.course_1.id)]: diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 4abf192f22..0d2c051fb5 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -123,6 +123,7 @@ from eventtracking import tracker from notification_prefs.views import enable_notifications # Note that this lives in openedx, so this dependency should be refactored. +from openedx.core.djangoapps.credentials.utils import get_user_program_credentials from openedx.core.djangoapps.user_api.preferences import api as preferences_api from openedx.core.djangoapps.programs.utils import get_programs_for_dashboard @@ -609,6 +610,7 @@ def dashboard(request): # This is passed along in the template context to allow rendering of # program-related information on the dashboard. course_programs = _get_course_programs(user, [enrollment.course_id for enrollment in course_enrollments]) + xseries_credentials = _get_xseries_credentials(user) # Construct a dictionary of course mode information # used to render the course list. We re-use the course modes dict @@ -732,6 +734,7 @@ def dashboard(request): 'nav_hidden': True, 'course_programs': course_programs, 'disable_courseware_js': True, + 'xseries_credentials': xseries_credentials, } return render_to_response('dashboard.html', context) @@ -2410,3 +2413,34 @@ def _get_course_programs(user, user_enrolled_courses): # pylint: disable=invali log.warning('Program structure is invalid, skipping display: %r', program) return programs_data + + +def _get_xseries_credentials(user): + """Return program credentials data required for display on + the learner dashboard. + + Given a user, find all programs for which certificates have been earned + and return list of dictionaries of required program data. + + Arguments: + user (User): user object for getting programs credentials. + + Returns: + list of dict, containing data corresponding to the programs for which + the user has been awarded a credential. + """ + programs_credentials = get_user_program_credentials(user) + credentials_data = [] + for program in programs_credentials: + if program.get('status') == 'active': + try: + program_data = { + 'display_name': program['name'], + 'subtitle': program['subtitle'], + 'credential_url': program['credential_url'], + } + credentials_data.append(program_data) + except KeyError: + log.warning('Program structure is invalid: %r', program) + + return credentials_data diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 1901dcae37..a1eab8972b 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -32,6 +32,32 @@ } } + .wrapper-xseries-certificates{ + @include float(right); + @include margin-left(flex-gutter()); + width: flex-grid(3); + + .title{ + @extend %t-title7; + @extend %t-weight4; + } + + ul{ + @include padding-left(0); + margin-top: ($baseline/2); + } + + li{ + @include line-height(20); + list-style-type: none; + } + + .copy { + @extend %t-copy-sub1; + margin-top: ($baseline/2); + } + } + .profile-sidebar { background: transparent; @include float(right); diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index eee0f7cb84..337018224c 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -172,6 +172,19 @@ import json + % if xseries_credentials: +
+

${_("XSeries Program Certificates")}

+

${_("You have received a certificate for the following XSeries programs:")}

+ +
+ % endif