From f9f4876bee81e1f0c1c2face109ff838ca5fa914 Mon Sep 17 00:00:00 2001 From: Bill Filler <bfiller@edx.org> 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 cf20f209f9c..790cafd809a 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('<div class="prerequisites">', 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 c3c3cd01516..220cf31d403 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 c0f99745647..a4a61d3b654 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 9c9a27ea8c8..b70271f6bf8 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 </ul> diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 0183816b715..e8030ea9caf 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: - <div class="message message-status wrapper-message-primary is-shown"> - % if verification_status['status'] == VERIFY_STATUS_NEED_TO_VERIFY: - <div class="verification-reminder"> - % if verification_status['days_until_deadline'] is not None: - <h4 class="message-title">${_('Verification not yet complete.')}</h4> - <p class="message-copy">${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'])}</p> - % else: - <h4 class="message-title">${_('Almost there!')}</h4> - <p class="message-copy">${_('You still need to verify for this course.')}</p> + % if is_course_blocked: + <p id="block-course-msg" class="course-block"> + ${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('<button type="button">'), + contact_link_end=HTML('</button>'), + unenroll_link_start=HTML( + '<a id="unregister_block_course" rel="leanModal" ' + 'data-course-id="{course_id}" data-course-number="{course_number}" data-course-name="{course_name}" ' + 'href="#unenroll-modal">' + ).format( + course_id=course_overview.id, + course_number=course_overview.number, + course_name=course_overview.display_name_with_default, + ), + unenroll_link_end=HTML('</a>'), + )} + </p> + % 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]: + <div class="message message-status wrapper-message-primary is-shown"> + % if verification_status['status'] == VERIFY_STATUS_NEED_TO_VERIFY: + <div class="verification-reminder"> + % if verification_status['days_until_deadline'] is not None: + <h4 class="message-title">${_('Verification not yet complete.')}</h4> + <p class="message-copy">${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'])}</p> + % else: + <h4 class="message-title">${_('Almost there!')}</h4> + <p class="message-copy">${_('You still need to verify for this course.')}</p> + % endif + </div> + <div class="verification-cta"> + <a href="${reverse('verify_student_verify_now', kwargs={'course_id': unicode(course_overview.id)})}" class="btn" data-course-id="${course_overview.id}">${_('Verify Now')}</a> + </div> + % elif verification_status['status'] == VERIFY_STATUS_SUBMITTED: + <h4 class="message-title">${_('You have submitted your verification information.')}</h4> + <p class="message-copy">${_('You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).')}</p> + % elif verification_status['status'] == VERIFY_STATUS_RESUBMITTED: + <h4 class="message-title">${_('Your current verification will expire soon!')}</h4> + <p class="message-copy">${_('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).')}</p> + % elif verification_status['status'] == VERIFY_STATUS_APPROVED: + <h4 class="message-title">${_('You have successfully verified your ID with edX')}</h4> + % if verification_status.get('verification_good_until') is not None: + <p class="message-copy">${_('Your current verification is effective until {date}.').format(date=verification_status['verification_good_until'])} % endif - </div> - <div class="verification-cta"> - <a href="${reverse('verify_student_verify_now', kwargs={'course_id': unicode(course_overview.id)})}" class="btn" data-course-id="${course_overview.id}">${_('Verify Now')}</a> - </div> - % elif verification_status['status'] == VERIFY_STATUS_SUBMITTED: - <h4 class="message-title">${_('You have submitted your verification information.')}</h4> - <p class="message-copy">${_('You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).')}</p> - % elif verification_status['status'] == VERIFY_STATUS_RESUBMITTED: - <h4 class="message-title">${_('Your current verification will expire soon!')}</h4> - <p class="message-copy">${_('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).')}</p> - % elif verification_status['status'] == VERIFY_STATUS_APPROVED: - <h4 class="message-title">${_('You have successfully verified your ID with edX')}</h4> - % if verification_status.get('verification_good_until') is not None: - <p class="message-copy">${_('Your current verification is effective until {date}.').format(date=verification_status['verification_good_until'])} + % elif verification_status['status'] == VERIFY_STATUS_NEED_TO_REVERIFY: + <h4 class="message-title">${_('Your current verification will expire soon.')}</h4> + ## Translators: start_link and end_link will be replaced with HTML tags; + ## please do not translate these. + <p class="message-copy">${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('<a href="{href}">').format(href=reverse('verify_student_reverify')), + end_link=HTML('</a>'), + days=settings.VERIFY_STUDENT.get("EXPIRING_SOON_WINDOW") + )} + </p> % endif - % elif verification_status['status'] == VERIFY_STATUS_NEED_TO_REVERIFY: - <h4 class="message-title">${_('Your current verification will expire soon.')}</h4> - ## Translators: start_link and end_link will be replaced with HTML tags; - ## please do not translate these. - <p class="message-copy">${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('<a href="{href}">').format(href=reverse('verify_student_reverify')), - end_link=HTML('</a>'), - days=settings.VERIFY_STUDENT.get("EXPIRING_SOON_WINDOW") - )} - </p> - % endif - </div> + </div> % endif - % if course_mode_info['show_upsell'] and not is_course_blocked: + % if course_mode_info['show_upsell']: <div class="message message-upsell has-actions is-shown"> - <div class="wrapper-extended"> - <p class="message-copy" align="justify"> - <b class="message-copy-bold"> - ${_("Pursue a {cert_name_long} to highlight the knowledge and skills you gain in this course.").format(cert_name_long=cert_name_long)} - </b><br> - ${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('<br>'), - link_start=HTML('<a href="{}" class="verified-info" data-course-key="{}">').format( - marketing_link('WHAT_IS_VERIFIED_CERT'), - enrollment.course_id - ), - link_end=HTML('</a>'), - cert_name_long=cert_name_long - )} - </p> - <div class="action-upgrade-container"> - % if use_ecommerce_payment_flow and course_mode_info['verified_sku']: - <a class="action action-upgrade" href="${ecommerce_payment_page}?sku=${course_mode_info['verified_sku']}"> - % else: - <a class="action action-upgrade" href="${reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_overview.id)})}" data-course-id="${course_overview.id}" data-user="${user.username}"> - % endif - <span class="action-upgrade-icon" aria-hidden="true"></span> - <span class="wrapper-copy"> - <span class="copy" id="upgrade-to-verified">${_("Upgrade to Verified")}</span> - <span class="sr"> ${_(course_overview.display_name_with_default)}</span> - </span> - </a> - </div> + <p class="message-copy" align="justify"> + <b class="message-copy-bold"> + ${_("Pursue a {cert_name_long} to highlight the knowledge and skills you gain in this course.").format(cert_name_long=cert_name_long)} + </b><br> + ${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('<br>'), + link_start=HTML('<a href="{}" class="verified-info" data-course-key="{}">').format( + marketing_link('WHAT_IS_VERIFIED_CERT'), + enrollment.course_id + ), + link_end=HTML('</a>'), + cert_name_long=cert_name_long + )} + </p> + <div class="action-upgrade-container"> + % if use_ecommerce_payment_flow and course_mode_info['verified_sku']: + <a class="action action-upgrade" href="${ecommerce_payment_page}?sku=${course_mode_info['verified_sku']}"> + % else: + <a class="action action-upgrade" href="${reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_overview.id)})}" data-course-id="${course_overview.id}" data-user="${user.username}"> + % endif + <span class="action-upgrade-icon" aria-hidden="true"></span> + <span class="wrapper-copy"> + <span class="copy" id="upgrade-to-verified">${_("Upgrade to Verified")}</span> + <span class="sr"> ${_(course_overview.display_name_with_default)}</span> + </span> + </a> + </div> </div> </div> - %endif - - % if is_course_blocked: - <p id="block-course-msg" class="course-block"> - ${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('<button type="button">'), - contact_link_end=HTML('</button>'), - unenroll_link_start=HTML( - '<a id="unregister_block_course" rel="leanModal" ' - 'data-course-id="{course_id}" data-course-number="{course_number}" data-course-name="{course_name}" ' - 'href="#unenroll-modal">' - ).format( - course_id=course_overview.id, - course_number=course_overview.number, - course_name=course_overview.display_name_with_default, - ), - unenroll_link_end=HTML('</a>'), - )} - </p> - %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 00000000000..0217cb44ba7 --- /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'/> + +<div class="message message-upsell has-actions is-shown"> + <div class="wrapper-extended"> + <p class="message-copy" align="justify"> + <b class="message-copy-bold"> + ${_("Consent to share your data")} + </b> + <br> + ${_("To access this course, you must first consent to share your learning achievements with {enterprise_customer_name}.").format(enterprise_customer_name=enterprise_customer_name)} + </p> + <div class="action-upgrade-container"> + <a class="action action-view-consent" href="${course_target}" data-course-key="${enrollment.course_id}"> + <span class="wrapper-copy"> + <span class="copy" id="view-consent">${_("View Consent")}</span> + <span class="sr"> ${_(course_overview.display_name_with_default)}</span> + </span> + </a> + </div> + </div> +</div> \ No newline at end of file -- GitLab