From 0a6029f7a7434a47c24d15ac6f5007f449f5e063 Mon Sep 17 00:00:00 2001 From: Clinton Blackburn <cblackburn@edx.org> Date: Thu, 24 Aug 2017 17:32:19 -0400 Subject: [PATCH] Added verified upgrade hero to course run homepage Audit learners are now shown a prompt to upgrade to the verified track of the course run. This message goes away after the learner upgrades. --- common/djangoapps/student/models.py | 11 +++++++---- lms/static/sass/_build-lms-v2.scss | 2 ++ lms/static/sass/features/_course-upgrade-message.scss | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ openedx/features/course_experience/__init__.py | 2 ++ openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html | 24 ++++++++++++++++++++++++ openedx/features/course_experience/static/course_experience/js/CourseHome.js | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- openedx/features/course_experience/templates/course_experience/course-home-fragment.html | 32 ++++++++++++++++++++++++++++++++ openedx/features/course_experience/tests/views/test_course_home.py | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ openedx/features/course_experience/views/course_home.py | 24 ++++++++++++++++++++++-- 10 files changed, 467 insertions(+), 29 deletions(-) create mode 100644 lms/static/sass/features/_course-upgrade-message.scss diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 74a9748..dd5372d 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -33,6 +33,7 @@ from django.db.models import Count from django.db.models.signals import post_save, pre_save from django.dispatch import Signal, receiver from django.utils import timezone +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_noop from django_countries.fields import CountryField @@ -1688,6 +1689,10 @@ class CourseEnrollment(models.Model): self._course_overview = None return self._course_overview + @cached_property + def verified_mode(self): + return CourseMode.verified_mode_for_course(self.course_id) + @property def upgrade_deadline(self): """ @@ -1723,11 +1728,9 @@ class CourseEnrollment(models.Model): pass try: - verified_mode = CourseMode.verified_mode_for_course(self.course_id) - - if verified_mode: + if self.verified_mode: log.debug('Schedules: Defaulting to verified mode expiration date-time for %s.', self.course_id) - return verified_mode.expiration_datetime + return self.verified_mode.expiration_datetime else: log.debug('Schedules: No verified mode located for %s.', self.course_id) except CourseMode.DoesNotExist: diff --git a/lms/static/sass/_build-lms-v2.scss b/lms/static/sass/_build-lms-v2.scss index 9ea180b..65ac354 100644 --- a/lms/static/sass/_build-lms-v2.scss +++ b/lms/static/sass/_build-lms-v2.scss @@ -21,6 +21,7 @@ // Elements @import 'notifications'; +@import 'elements/controls'; @import 'elements-v2/pagination'; // Features @@ -28,6 +29,7 @@ @import 'features/course-experience'; @import 'features/course-search'; @import 'features/course-sock'; +@import 'features/course-upgrade-message'; // Views @import "views/program-marketing-page"; diff --git a/lms/static/sass/features/_course-upgrade-message.scss b/lms/static/sass/features/_course-upgrade-message.scss new file mode 100644 index 0000000..a9a3f06 --- /dev/null +++ b/lms/static/sass/features/_course-upgrade-message.scss @@ -0,0 +1,126 @@ +/* + NOTE: If you make significant changes to the design, remember to update the Segment event properties and change + the creative property. This will allow us to better track individual performance of each style of the message. + Search for the courseware_verified_certificate_upsell promotion ID. + */ +$upgrade-message-background-color: $blue-d1; + +// Expanded upgrade message +.vc-message { + background: $blue-d1; + color: $white; + padding: $baseline; + position: relative; + margin: 0 0 $baseline; + + // CSS animation for smooth height transition + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + + &:after { + content: ""; + display: table; + clear: both; + } + + // Message copy + .vc-title { + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 1rem; + @include float(left); + } + + .vc-selling-points { + @include clear(left); + @include padding-left(0); + font-size: 0.825rem; + margin: 1rem 0; + display: table; + + > .vc-selling-point { + list-style: none; + display: table-row; + + &:before { + content: "\2022"; + display: table-cell; + @include padding-right($baseline/2); + } + + &:after { + content: ""; + display: table-row; + height: 0.25rem; + } + } + } + img { + max-width: 100%; + } + + // Show/hide button + .vc-toggle { + @include float(right); + color: $white; + } + + // Upgrade Button + .btn-upgrade { + @extend %btn-primary-green; + background: $uxpl-green-base; + } + + // Cert image + .vc-hero { + @include float(right); + @include padding-left(1rem); + clear: both; + width: 35%; + + img { + max-width: 100%; + } + } +} + +// Collapsed upgrade message +.vc-message.polite { + padding-top: $baseline/2; + padding-bottom: $baseline/2; + min-height: 46px; + display: flex; + flex-flow: row wrap; + align-items: center; + + .vc-title { + margin: 0; + @include margin-right(auto); + } + + .vc-cta { + @include margin-right(1rem); + } + + .vc-toggle { + order: 99; + } + + .vc-fade:not(.vc-polite-only) { + display: none; + } +} + +@media (max-width: $bp-screen-md) { + .vc-message.polite .vc-title { + clear: both; + width: 100%; + margin-bottom: 1rem; + } +} + +@media (max-width: $bp-screen-sm) { + .vc-message .vc-hero { + display: none !important; + } +} diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py index c079b2c..31f994e 100644 --- a/openedx/features/course_experience/__init__.py +++ b/openedx/features/course_experience/__init__.py @@ -25,6 +25,8 @@ COURSE_PRE_START_ACCESS_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_star # Waffle flag to enable a review page link from the unified home page SHOW_REVIEWS_TOOL_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_reviews_tool') +SHOW_UPGRADE_MSG_ON_COURSE_HOME = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_upgrade_msg_on_course_home') + # Waffle flag to switch between the 'welcome message' and 'latest update' on the course home page. # Important Admin Note: This is meant to be configured using waffle_utils course # override only. Either do not create the actual waffle flag, or be sure to unset the diff --git a/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html b/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html index d3007af..134c535 100644 --- a/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html +++ b/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html @@ -29,6 +29,30 @@ <div class="page-content"> <div class="layout layout-1t2t"> <main class="layout-col layout-col-b"> + <div class="section"> + <div class="vc-message tex2jax_ignore" role="group" aria-labelledby="vc-title" tabindex="-1" style="display: none;"> + <h3 class="vc-title vc-fade vc-polite-only">Pursue a verified certificate</h3> + <button class="vc-toggle vc-fade vc-polite-only btn-link" type="button" aria-controls="moreinfo" + aria-expanded="true" aria-label="Show/Hide">Show less + </button> + + <div class="vc-hero vc-fade"> + <img src="img/sample-certificate.png" + alt="Sample verified certificate with your name, the course title, the logo of the institution and the signatures of the instructors for this course."/> + </div> + + <ul class="vc-selling-points vc-fade"> + <li class="vc-selling-point">Official proof of completion</li> + <li class="vc-selling-point">Easily shareable certificate</li> + <li class="vc-selling-point">Proven motivator to complete the course</li> + <li class="vc-selling-point">Certificate purchases help us continue to offer free courses</li> + </ul> + + <div class="vc-cta vc-fade vc-polite-only"> + <a class="btn btn-upgrade" href="#">Upgrade ($100)</a> + </div> + </div> + </div> <div class="section section-dates"> <div class="welcome-message"> <div class="dismiss-message"> diff --git a/openedx/features/course_experience/static/course_experience/js/CourseHome.js b/openedx/features/course_experience/static/course_experience/js/CourseHome.js index b6d3b81..523c4c5 100644 --- a/openedx/features/course_experience/static/course_experience/js/CourseHome.js +++ b/openedx/features/course_experience/static/course_experience/js/CourseHome.js @@ -1,7 +1,10 @@ -/* globals Logger */ +/* globals gettext, Logger */ export class CourseHome { // eslint-disable-line import/prefer-default-export constructor(options) { + this.courseRunKey = options.courseRunKey; + this.msgStateStorageKey = `course_experience.upgrade_msg.${this.courseRunKey}.collapsed`; + // Logging for 'Resume Course' or 'Start Course' button click const $resumeCourseLink = $(options.resumeCourseLink); $resumeCourseLink.on('click', (event) => { @@ -26,5 +29,97 @@ export class CourseHome { // eslint-disable-line import/prefer-default-export }, ); }); + + $(document).ready(() => { + this.configureUpgradeMessage(); + }); + } + + static fireSegmentEvent(event, properties) { + /* istanbul ignore next */ + if (!window.analytics) { + return; + } + + window.analytics.track(event, properties); + } + + /** + * Persists the collapsed state of the upgrade message. If the message is collapsed, + * this information is persisted to local storage. Expanding the message *removes* the + * key from local storage. + */ + persistUpgradeMessageState(collapsed) { + if (window.localStorage) { + if (collapsed) { + window.localStorage.setItem(this.msgStateStorageKey, true); + } else { + window.localStorage.removeItem(this.msgStateStorageKey); + } + } + } + + configureUpgradeMessage() { + const $vcMessage = $('.vc-message'); + const $vcDismissToggle = $('.vc-toggle', $vcMessage); + const logEventProperties = { courseRunKey: this.courseRunKey }; + const promotionEventProperties = { + promotion_id: 'courseware_verified_certificate_upsell', + creative: 'original_hero', + name: 'In-Course Verification Prompt', + position: 'hero', + }; + + CourseHome.fireSegmentEvent('Promotion Viewed', promotionEventProperties); + Logger.log('edx.course.upgrade.hero.displayed', logEventProperties); + + // Get height of container and button + let vcHeight = $vcMessage.outerHeight(); + + // Update based on window + window.onresize = () => { + if (!$vcMessage.hasClass('polite')) { + vcHeight = $vcMessage.outerHeight(); + } + }; + + function collapseMessage(duration = 400) { + $('.vc-fade').fadeOut(duration, () => { + $vcDismissToggle.text(gettext('Show more')).attr('aria-expanded', false); + $('.vc-polite-only').fadeIn(duration); + $vcMessage.height('auto').addClass('polite'); + }); + } + + // Use the previously-persisted state to determine the initial display state of the message. + if (window.localStorage && window.localStorage.getItem(this.msgStateStorageKey)) { + collapseMessage(0); + } + $vcMessage.show(); + + $vcDismissToggle.click(() => { + if ($vcMessage.hasClass('polite')) { + // Expand message + Logger.log('edx.course.upgrade.hero.expanded', logEventProperties); + this.persistUpgradeMessageState(false); + + $('.vc-fade').fadeOut(400); + $vcMessage.animate({ height: vcHeight }, 400, () => { + $vcMessage.height('auto').removeClass('polite'); + $vcDismissToggle.text(gettext('Show less')).attr('aria-expanded', true); + $('.vc-fade').fadeIn(400); + }); + } else { + // Collapse message + Logger.log('edx.course.upgrade.hero.collapsed', logEventProperties); + this.persistUpgradeMessageState(true); + collapseMessage(); + } + }); + + $('.btn-upgrade', $vcMessage).click(() => { + CourseHome.fireSegmentEvent('Promotion Clicked', promotionEventProperties); + Logger.log('edx.course.upgrade.hero.clicked', logEventProperties); + }); } } diff --git a/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js b/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js index be48d90..c31eb47 100644 --- a/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js +++ b/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js @@ -3,18 +3,21 @@ import { CourseHome } from '../CourseHome'; describe('Course Home factory', () => { - describe('Ensure course tool click logging', () => { - let home; // eslint-disable-line no-unused-vars + let home; + const runKey = 'course-v1:edX+DemoX+Demo_Course'; + window.analytics = jasmine.createSpyObj('analytics', ['page', 'track', 'trackLink']); - beforeEach(() => { - loadFixtures('course_experience/fixtures/course-home-fragment.html'); - home = new CourseHome({ - resumeCourseLink: '.action-resume-course', - courseToolLink: '.course-tool-link', - }); - spyOn(Logger, 'log'); + beforeEach(() => { + loadFixtures('course_experience/fixtures/course-home-fragment.html'); + spyOn(Logger, 'log'); + home = new CourseHome({ // eslint-disable-line no-unused-vars + courseRunKey: runKey, + resumeCourseLink: '.action-resume-course', + courseToolLink: '.course-tool-link', }); + }); + describe('Ensure course tool click logging', () => { it('sends an event when resume or start course is clicked', () => { $('.action-resume-course').click(); expect(Logger.log).toHaveBeenCalledWith( @@ -22,7 +25,7 @@ describe('Course Home factory', () => { { event_type: 'start', url: `http://${window.location.host}/courses/course-v1:edX+DemoX+Demo_Course/courseware` + - '/19a30717eff543078a5d94ae9d6c18a5/', + '/19a30717eff543078a5d94ae9d6c18a5/', }, ); }); @@ -43,4 +46,57 @@ describe('Course Home factory', () => { } }); }); + + describe('Upgrade message events', () => { + const segmentEventProperties = { + promotion_id: 'courseware_verified_certificate_upsell', + creative: 'original_hero', + name: 'In-Course Verification Prompt', + position: 'hero', + }; + + it('should send events to Segment and edX on initial load', () => { + expect(window.analytics.track).toHaveBeenCalledWith('Promotion Viewed', segmentEventProperties); + expect(Logger.log).toHaveBeenCalledWith('edx.course.upgrade.hero.displayed', { courseRunKey: runKey }); + }); + + it('should send events to Segment and edX after clicking the upgrade button ', () => { + $('.vc-message .btn-upgrade').click(); + expect(window.analytics.track).toHaveBeenCalledWith('Promotion Viewed', segmentEventProperties); + expect(Logger.log).toHaveBeenCalledWith('edx.course.upgrade.hero.clicked', { courseRunKey: runKey }); + }); + }); + + describe('upgrade message display toggle', () => { + let $message; + let $toggle; + + beforeEach(() => { + $.fx.off = true; + + $message = $('.vc-message'); + $toggle = $('.vc-toggle', $message); + expect($message.length).toEqual(1); + expect($toggle.length).toEqual(1); + }); + + it('hides/shows the message and writes/removes a key from local storage', () => { + // NOTE (CCB): Ideally this should be two tests--one for collapse, another for expansion. + // After a couple hours I have been unable to make these two tests pass, probably due to + // issues with the initial state of local storage. + expect($message.is(':visible')).toBeTruthy(); + expect($message.hasClass('polite')).toBeFalsy(); + expect($toggle.text().trim()).toEqual('Show less'); + + $toggle.click(); + expect($message.hasClass('polite')).toBeTruthy(); + expect($toggle.text().trim()).toEqual('Show more'); + expect(window.localStorage.getItem(home.msgStateStorageKey)).toEqual('true'); + + $toggle.click(); + expect($message.hasClass('polite')).toBeFalsy(); + expect($toggle.text().trim()).toEqual('Show less'); + expect(window.localStorage.getItem(home.msgStateStorageKey)).toBeNull(); + }); + }); }); diff --git a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html index 058d094..7216dae 100644 --- a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html +++ b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html @@ -57,6 +57,37 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV <div class="page-content"> <div class="layout layout-1t2t"> <main class="layout-col layout-col-b"> + + % if upgrade_url and upgrade_price: + <div class="section"> + <div class="vc-message tex2jax_ignore" role="group" aria-labelledby="vc-title" tabindex="-1" style="display: none;"> + + <h3 class="vc-title vc-fade vc-polite-only">Pursue a verified certificate</h3> + + <button class="vc-toggle vc-fade vc-polite-only btn-link" type="button" aria-controls="moreinfo" + aria-expanded="true" aria-label="${_("Show/Hide")}"> + ${_("Show less")} + </button> + + <div class="vc-hero vc-fade"> + <img src="${static.url('course_experience/images/verified-cert.png')}" + alt="${_("Sample verified certificate with your name, the course title, the logo of the institution and the signatures of the instructors for this course.")}"/> + </div> + + <ul class="vc-selling-points vc-fade"> + <li class="vc-selling-point">${_("Official proof of completion")}</li> + <li class="vc-selling-point">${_("Easily shareable certificate")}</li> + <li class="vc-selling-point">${_("Proven motivator to complete the course")}</li> + <li class="vc-selling-point">${_("Certificate purchases help us continue to offer free courses")}</li> + </ul> + + <div class="vc-cta vc-fade vc-polite-only"> + <a class="btn-upgrade" href="${ upgrade_url }">${_("Upgrade ({price})").format(price='$' + str(upgrade_price))}</a> + </div> + </div> + </div> + % endif + % if course_home_message_fragment: ${HTML(course_home_message_fragment.body_html())} % endif @@ -109,6 +140,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV <%static:webpack entry="CourseHome"> new CourseHome({ + courseRunKey: "${course_key | n, js_escaped_string}", resumeCourseLink: ".action-resume-course", courseToolLink: ".course-tool-link", }); diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py index 141d138..1a65f28 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -3,28 +3,36 @@ Tests for the course home page. """ from datetime import datetime, timedelta + import ddt import mock -from pytz import UTC -from waffle.testutils import override_flag - -from courseware.tests.factories import StaffFactory from django.conf import settings from django.core.urlresolvers import reverse from django.http import QueryDict from django.utils.http import urlquote_plus +from pytz import UTC +from waffle.models import Flag +from waffle.testutils import override_flag + +from commerce.models import CommerceConfiguration +from commerce.utils import EcommerceService +from course_modes.models import CourseMode +from courseware.tests.factories import StaffFactory from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES, override_waffle_flag -from openedx.features.course_experience import SHOW_REVIEWS_TOOL_FLAG, UNIFIED_COURSE_TAB_FLAG +from openedx.features.course_experience import ( + SHOW_REVIEWS_TOOL_FLAG, + SHOW_UPGRADE_MSG_ON_COURSE_HOME, + UNIFIED_COURSE_TAB_FLAG +) from student.models import CourseEnrollment from student.tests.factories import UserFactory from util.date_utils import strftime_localized from xmodule.modulestore import ModuleStoreEnum -from xmodule.modulestore.tests.django_utils import CourseUserType, SharedModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import CourseUserType, ModuleStoreTestCase, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls - -from ... import COURSE_PRE_START_ACCESS_FLAG from .helpers import add_course_mode from .test_course_updates import create_course_update +from ... import COURSE_PRE_START_ACCESS_FLAG TEST_PASSWORD = 'test' TEST_CHAPTER_NAME = 'Test Chapter' @@ -68,6 +76,7 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase): """ Base class for testing the course home page. """ + @classmethod def setUpClass(cls): """ @@ -113,9 +122,6 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase): class TestCourseHomePage(CourseHomePageTestCase): def setUp(self): - """ - Set up for the tests. - """ super(TestCourseHomePage, self).setUp() self.client.login(username=self.user.username, password=TEST_PASSWORD) @@ -160,7 +166,7 @@ class TestCourseHomePage(CourseHomePageTestCase): course_home_url(self.course) # Fetch the view and verify the query counts - with self.assertNumQueries(41, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): + with self.assertNumQueries(42, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with check_mongo_calls(4): url = course_home_url(self.course) self.client.get(url) @@ -374,3 +380,75 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): response = self.client.get(url) self.assertContains(response, TEST_COURSE_HOME_MESSAGE) self.assertContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START) + + +class CourseHomeFragmentViewTests(ModuleStoreTestCase): + CREATE_USER = False + + def setUp(self): + super(CourseHomeFragmentViewTests, self).setUp() + CommerceConfiguration.objects.create(checkout_on_ecommerce_service=True) + + end = datetime.now(UTC) + timedelta(days=30) + self.course = CourseFactory( + start=datetime.now(UTC) - timedelta(days=30), + end=end, + ) + self.url = course_home_url(self.course) + + CourseMode.objects.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT) + self.verified_mode = CourseMode.objects.create( + course_id=self.course.id, + mode_slug=CourseMode.VERIFIED, + min_price=100, + expiration_datetime=end, + sku='test' + ) + + self.user = UserFactory() + self.client.login(username=self.user.username, password=TEST_PASSWORD) + + name = SHOW_UPGRADE_MSG_ON_COURSE_HOME.waffle_namespace._namespaced_name( + SHOW_UPGRADE_MSG_ON_COURSE_HOME.flag_name) + self.flag, __ = Flag.objects.update_or_create(name=name, defaults={'everyone': True}) + + def assert_upgrade_message_not_displayed(self): + response = self.client.get(self.url) + self.assertNotIn('vc-message', response.content) + + def assert_upgrade_message_displayed(self): + response = self.client.get(self.url) + self.assertIn('vc-message', response.content) + url = EcommerceService().get_checkout_page_url(self.verified_mode.sku) + expected = '<a class="btn-upgrade" href="{url}">Upgrade (${price})</a>'.format( + url=url, + price=self.verified_mode.min_price + ) + self.assertIn(expected, response.content) + + def test_no_upgrade_message_if_logged_out(self): + self.client.logout() + self.assert_upgrade_message_not_displayed() + + def test_no_upgrade_message_if_not_enrolled(self): + self.assertEqual(len(CourseEnrollment.enrollments_for_user(self.user)), 0) + self.assert_upgrade_message_not_displayed() + + def test_no_upgrade_message_if_verified_track(self): + CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) + self.assert_upgrade_message_not_displayed() + + def test_no_upgrade_message_if_upgrade_deadline_passed(self): + self.verified_mode.expiration_datetime = datetime.now(UTC) - timedelta(days=20) + self.verified_mode.save() + self.assert_upgrade_message_not_displayed() + + def test_no_upgrade_message_if_flag_disabled(self): + self.flag.everyone = False + self.flag.save() + CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) + self.assert_upgrade_message_not_displayed() + + def test_display_upgrade_message_if_audit_and_deadline_not_passed(self): + CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) + self.assert_upgrade_message_displayed() diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py index 2ca8316..026eeb9 100644 --- a/openedx/features/course_experience/views/course_home.py +++ b/openedx/features/course_experience/views/course_home.py @@ -9,6 +9,7 @@ from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_control from django.views.decorators.csrf import ensure_csrf_cookie +from commerce.utils import EcommerceService from courseware.access import has_access from courseware.courses import ( can_self_enroll_in_course, @@ -24,7 +25,7 @@ from student.models import CourseEnrollment from util.views import ensure_valid_course_key from web_fragments.fragment import Fragment -from .. import LATEST_UPDATE_FLAG +from .. import LATEST_UPDATE_FLAG, SHOW_UPGRADE_MSG_ON_COURSE_HOME from ..utils import get_course_outline_block_tree from .course_dates import CourseDatesFragmentView from .course_home_messages import CourseHomeMessageFragmentView @@ -116,9 +117,10 @@ class CourseHomeFragmentView(EdxFragmentView): # Render the full content to enrolled users, as well as to course and global staff. # Unenrolled users who are not course or global staff are given only a subset. + enrollment = CourseEnrollment.get_enrollment(request.user, course_key) user_access = { 'is_anonymous': request.user.is_anonymous(), - 'is_enrolled': CourseEnrollment.is_enrolled(request.user, course_key), + 'is_enrolled': enrollment is not None, 'is_staff': has_access(request.user, 'staff', course_key), } if user_access['is_enrolled'] or user_access['is_staff']: @@ -157,6 +159,22 @@ class CourseHomeFragmentView(EdxFragmentView): request, course_id=course_id, user_access=user_access, **kwargs ) + # Get info for upgrade messaging + upgrade_price = None + upgrade_url = None + + # TODO Add switch to control deployment + if SHOW_UPGRADE_MSG_ON_COURSE_HOME.is_enabled(course_key) and enrollment and enrollment.upgrade_deadline: + verified_mode = enrollment.verified_mode + if verified_mode: + upgrade_price = verified_mode.min_price + + ecommerce_service = EcommerceService() + if ecommerce_service.is_enabled(request.user): + upgrade_url = ecommerce_service.get_checkout_page_url(verified_mode.sku) + else: + upgrade_url = reverse('verify_student_upgrade_and_verify', args=(course_key,)) + # Render the course home fragment context = { 'request': request, @@ -174,6 +192,8 @@ class CourseHomeFragmentView(EdxFragmentView): 'course_sock_fragment': course_sock_fragment, 'disable_courseware_js': True, 'uses_pattern_library': True, + 'upgrade_price': upgrade_price, + 'upgrade_url': upgrade_url, } html = render_to_string('course_experience/course-home-fragment.html', context) return Fragment(html) -- libgit2 0.26.0