From f9f4876bee81e1f0c1c2face109ff838ca5fa914 Mon Sep 17 00:00:00 2001 From: Bill Filler Date: Tue, 5 Sep 2017 10:47:56 -0400 Subject: [PATCH] Add 'View Consent' button to dashboard when required Enterprise customers can require user to agree to Data Sharing Consent form before they can access a course. We now add it conditionally to Course Dashboard when it's required so it's apparent to user and they have a way to revist the consent form if they've previously declined or the course has not yet started. WL-1281 --- common/djangoapps/student/tests/test_views.py | 41 ++++ common/djangoapps/student/views.py | 18 +- lms/static/sass/multicourse/_dashboard.scss | 11 +- lms/templates/dashboard.html | 3 +- .../dashboard/_dashboard_course_listing.html | 196 +++++++++--------- .../dashboard/_dashboard_show_consent.html | 25 +++ 6 files changed, 194 insertions(+), 100 deletions(-) create mode 100644 lms/templates/dashboard/_dashboard_show_consent.html diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index cf20f209f9..790cafd809 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -7,6 +7,7 @@ import json import unittest import ddt +import mock import pytz from django.conf import settings from django.core.urlresolvers import reverse @@ -335,3 +336,43 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): remove_prerequisite_course(self.course.id, get_course_milestones(self.course.id)[0]) response = self.client.get(reverse('dashboard')) self.assertNotIn('
', response.content) + + @mock.patch('student.views.consent_needed_for_course') + @mock.patch('student.views.enterprise_customer_for_request') + @ddt.data( + (True, True, True), + (True, True, False), + (True, False, False), + (False, True, False), + (False, False, False), + ) + @ddt.unpack + def test_enterprise_view_consent_for_course( + self, + enterprise_enabled, + consent_needed, + future_course, + mock_enterprise_customer, + mock_consent_necessary + ): + """ + Verify that the 'View Consent' icon show up if data sharing consent turned on + for enterprise customer + """ + if future_course: + self.course = CourseFactory.create(start=self.TOMORROW, emit_signals=True) + else: + self.course = CourseFactory.create(emit_signals=True) + self.course_enrollment = CourseEnrollmentFactory(course_id=self.course.id, user=self.user) + + if enterprise_enabled: + mock_enterprise_customer.return_value = {'name': 'TestEnterprise', 'uuid': 'abc123xxx'} + else: + mock_enterprise_customer.return_value = None + + mock_consent_necessary.return_value = consent_needed + + # Assert 'View Consent' button shows up appropriately + response = self.client.get(reverse('dashboard')) + self.assertEquals('View Consent' in response.content, enterprise_enabled and consent_needed) + self.assertEquals('TestEnterprise' in response.content, enterprise_enabled and consent_needed) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index c3c3cd0151..220cf31d40 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -87,7 +87,11 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers from openedx.core.djangoapps.user_api.preferences import api as preferences_api from openedx.core.djangolib.markup import HTML from openedx.features.course_experience import course_home_url_name -from openedx.features.enterprise_support.api import get_dashboard_consent_notification +from openedx.features.enterprise_support.api import ( + consent_needed_for_course, + enterprise_customer_for_request, + get_dashboard_consent_notification +) from shoppingcart.api import order_history from shoppingcart.models import CourseRegistrationCode, DonationConfiguration from student.cookies import delete_logged_in_cookies, set_logged_in_cookies, set_user_info_cookie @@ -729,6 +733,16 @@ def dashboard(request): enterprise_message = get_dashboard_consent_notification(request, user, course_enrollments) + enterprise_customer = enterprise_customer_for_request(request) + consent_required_courses = set() + enterprise_customer_name = None + if enterprise_customer: + consent_required_courses = { + enrollment.course_id for enrollment in course_enrollments + if consent_needed_for_course(request, request.user, str(enrollment.course_id), True) + } + enterprise_customer_name = enterprise_customer['name'] + # Account activation message account_activation_messages = [ message for message in messages.get_messages(request) if 'account-activation' in message.tags @@ -847,6 +861,8 @@ def dashboard(request): context = { 'enterprise_message': enterprise_message, + 'consent_required_courses': consent_required_courses, + 'enterprise_customer_name': enterprise_customer_name, 'enrollment_message': enrollment_message, 'redirect_message': redirect_message, 'account_activation_messages': account_activation_messages, diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index c0f9974564..a4a61d3b65 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -721,7 +721,7 @@ @include clearfix(); - position: relative; + position: inherit; @include left($baseline/2); @include padding(($baseline * 0.4), 0, ($baseline * 0.4), ($baseline * 0.75)); @@ -772,6 +772,15 @@ opacity: 0.875; } } + + .action-view-consent { + @extend %btn-pl-white-base; + @include float(right); + + &.archived { + @extend %btn-pl-default-base; + } + } } // TYPE: status diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 9c9a27ea8c..b70271f6bf 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -128,7 +128,8 @@ from openedx.core.djangolib.markup import HTML, Text <% 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)) %> - <%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 = (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 diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 0183816b71..e8030ea9ca 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -1,4 +1,4 @@ -<%page args="course_overview, enrollment, show_courseware_link, cert_status, can_unenroll, credit_status, show_email_settings, course_mode_info, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, related_programs, display_course_modes_on_dashboard" expression_filter="h"/> +<%page args="course_overview, enrollment, show_courseware_link, cert_status, can_unenroll, credit_status, show_email_settings, course_mode_info, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, related_programs, display_course_modes_on_dashboard, show_consent_link, enterprise_customer_name" expression_filter="h"/> <%! import urllib @@ -289,110 +289,112 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_ <%include file="_dashboard_credit_info.html" args="credit_status=credit_status"/> % endif - % if verification_status.get('status') in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_RESUBMITTED, VERIFY_STATUS_APPROVED, VERIFY_STATUS_NEED_TO_REVERIFY] and not is_course_blocked: -
- % if verification_status['status'] == VERIFY_STATUS_NEED_TO_VERIFY: -
- % if verification_status['days_until_deadline'] is not None: -

${_('Verification not yet complete.')}

-

${ungettext( - 'You only have {days} day left to verify for this course.', - 'You only have {days} days left to verify for this course.', - verification_status['days_until_deadline'] - ).format(days=verification_status['days_until_deadline'])}

- % else: -

${_('Almost there!')}

-

${_('You still need to verify for this course.')}

+ % if is_course_blocked: +

+ ${Text(_("You can no longer access this course because payment has not yet been received. " + "You can {contact_link_start}contact the account holder{contact_link_end} " + "to request payment, or you can " + "{unenroll_link_start}unenroll{unenroll_link_end} " + "from this course")).format( + contact_link_start=HTML(''), + unenroll_link_start=HTML( + '' + ).format( + course_id=course_overview.id, + course_number=course_overview.number, + course_name=course_overview.display_name_with_default, + ), + unenroll_link_end=HTML(''), + )} +

+ % else: + % if show_consent_link: + <%include file="_dashboard_show_consent.html" args="course_overview=course_overview, course_target=course_target, enrollment=enrollment, enterprise_customer_name=enterprise_customer_name"/> + %endif + + % if verification_status.get('status') in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_RESUBMITTED, VERIFY_STATUS_APPROVED, VERIFY_STATUS_NEED_TO_REVERIFY]: +
+ % if verification_status['status'] == VERIFY_STATUS_NEED_TO_VERIFY: +
+ % if verification_status['days_until_deadline'] is not None: +

${_('Verification not yet complete.')}

+

${ungettext( + 'You only have {days} day left to verify for this course.', + 'You only have {days} days left to verify for this course.', + verification_status['days_until_deadline'] + ).format(days=verification_status['days_until_deadline'])}

+ % else: +

${_('Almost there!')}

+

${_('You still need to verify for this course.')}

+ % endif +
+ + % elif verification_status['status'] == VERIFY_STATUS_SUBMITTED: +

${_('You have submitted your verification information.')}

+

${_('You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).')}

+ % elif verification_status['status'] == VERIFY_STATUS_RESUBMITTED: +

${_('Your current verification will expire soon!')}

+

${_('You have submitted your reverification information. You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).')}

+ % elif verification_status['status'] == VERIFY_STATUS_APPROVED: +

${_('You have successfully verified your ID with edX')}

+ % if verification_status.get('verification_good_until') is not None: +

${_('Your current verification is effective until {date}.').format(date=verification_status['verification_good_until'])} % endif -

- - % elif verification_status['status'] == VERIFY_STATUS_SUBMITTED: -

${_('You have submitted your verification information.')}

-

${_('You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).')}

- % elif verification_status['status'] == VERIFY_STATUS_RESUBMITTED: -

${_('Your current verification will expire soon!')}

-

${_('You have submitted your reverification information. You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).')}

- % elif verification_status['status'] == VERIFY_STATUS_APPROVED: -

${_('You have successfully verified your ID with edX')}

- % if verification_status.get('verification_good_until') is not None: -

${_('Your current verification is effective until {date}.').format(date=verification_status['verification_good_until'])} + % elif verification_status['status'] == VERIFY_STATUS_NEED_TO_REVERIFY: +

${_('Your current verification will expire soon.')}

+ ## Translators: start_link and end_link will be replaced with HTML tags; + ## please do not translate these. +

${Text(_('Your current verification will expire in {days} days. {start_link}Re-verify your identity now{end_link} using a webcam and a government-issued photo ID.')).format( + start_link=HTML('').format(href=reverse('verify_student_reverify')), + end_link=HTML(''), + days=settings.VERIFY_STUDENT.get("EXPIRING_SOON_WINDOW") + )} +

% endif - % elif verification_status['status'] == VERIFY_STATUS_NEED_TO_REVERIFY: -

${_('Your current verification will expire soon.')}

- ## Translators: start_link and end_link will be replaced with HTML tags; - ## please do not translate these. -

${Text(_('Your current verification will expire in {days} days. {start_link}Re-verify your identity now{end_link} using a webcam and a government-issued photo ID.')).format( - start_link=HTML('').format(href=reverse('verify_student_reverify')), - end_link=HTML(''), - days=settings.VERIFY_STUDENT.get("EXPIRING_SOON_WINDOW") - )} -

- % endif -
+
% endif - % if course_mode_info['show_upsell'] and not is_course_blocked: + % if course_mode_info['show_upsell']:
-
-

- - ${_("Pursue a {cert_name_long} to highlight the knowledge and skills you gain in this course.").format(cert_name_long=cert_name_long)} -
- ${Text(_("It's official. It's easily shareable. " - "It's a proven motivator to complete the course. {line_break}" - "{link_start}Learn more about the verified {cert_name_long}{link_end}.")).format( - line_break=HTML('
'), - link_start=HTML('').format( - marketing_link('WHAT_IS_VERIFIED_CERT'), - enrollment.course_id - ), - link_end=HTML(''), - cert_name_long=cert_name_long - )} -

- +

+ + ${_("Pursue a {cert_name_long} to highlight the knowledge and skills you gain in this course.").format(cert_name_long=cert_name_long)} +
+ ${Text(_("It's official. It's easily shareable. " + "It's a proven motivator to complete the course. {line_break}" + "{link_start}Learn more about the verified {cert_name_long}{link_end}.")).format( + line_break=HTML('
'), + link_start=HTML('').format( + marketing_link('WHAT_IS_VERIFIED_CERT'), + enrollment.course_id + ), + link_end=HTML(''), + cert_name_long=cert_name_long + )} +

+
- %endif - - % if is_course_blocked: -

- ${Text(_("You can no longer access this course because payment has not yet been received. " - "You can {contact_link_start}contact the account holder{contact_link_end} " - "to request payment, or you can " - "{unenroll_link_start}unenroll{unenroll_link_end} " - "from this course")).format( - contact_link_start=HTML(''), - unenroll_link_start=HTML( - '' - ).format( - course_id=course_overview.id, - course_number=course_overview.number, - course_name=course_overview.display_name_with_default, - ), - unenroll_link_end=HTML(''), - )} -

- %endif - + % endif + % endif % if course_requirements: ## Multiple pre-requisite courses are not supported on frontend that's why we are pulling first element diff --git a/lms/templates/dashboard/_dashboard_show_consent.html b/lms/templates/dashboard/_dashboard_show_consent.html new file mode 100644 index 0000000000..0217cb44ba --- /dev/null +++ b/lms/templates/dashboard/_dashboard_show_consent.html @@ -0,0 +1,25 @@ +<%page expression_filter="h" args="course_overview, course_target, enrollment, enterprise_customer_name" /> +<%! +from django.utils.translation import ugettext as _ +%> +<%namespace name='static' file='../static_content.html'/> + +
+
+

+ + ${_("Consent to share your data")} + +
+ ${_("To access this course, you must first consent to share your learning achievements with {enterprise_customer_name}.").format(enterprise_customer_name=enterprise_customer_name)} +

+ +
+
\ No newline at end of file