diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py
index 3a83509594..1894711f00 100644
--- a/common/djangoapps/course_modes/tests/test_views.py
+++ b/common/djangoapps/course_modes/tests/test_views.py
@@ -398,8 +398,8 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
self.assertEquals(course_mode, expected_mode)
- @patch('openedx.features.discounts.utils.can_receive_discount')
- @patch('openedx.features.discounts.utils.discount_percentage')
+ @patch('openedx.features.course_experience.utils.can_receive_discount')
+ @patch('openedx.features.course_experience.utils.discount_percentage')
def test_discount_on_track_selection(self, discount_percentage_mock, can_receive_discount_mock):
can_receive_discount_mock.return_value = True
discount_percentage_mock.return_value = 15
diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py
index 77d12334be..6bcc521f83 100644
--- a/common/djangoapps/course_modes/views.py
+++ b/common/djangoapps/course_modes/views.py
@@ -36,7 +36,7 @@ from openedx.core.djangoapps.embargo import api as embargo_api
from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
-from openedx.features.discounts.utils import get_first_purchase_offer_banner_fragment
+from openedx.features.course_experience.utils import get_first_purchase_offer_banner_fragment
from openedx.features.discounts.applicability import discount_percentage
from student.models import CourseEnrollment
from util.db import outer_atomic
diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py
index 9efd2ccac0..249c00cc53 100644
--- a/common/lib/xmodule/xmodule/seq_module.py
+++ b/common/lib/xmodule/xmodule/seq_module.py
@@ -367,7 +367,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
'banner_text': banner_text,
'save_position': view != PUBLIC_VIEW,
'show_completion': view != PUBLIC_VIEW,
- 'gated_content': self._get_gated_content_info(prereq_met, prereq_meta_info),
+ 'gated_content': self._get_gated_content_info(prereq_met, prereq_meta_info)
}
fragment.add_content(self.system.render_template("seq_module.html", params))
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 60599c7205..36e7036661 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -84,7 +84,7 @@ from openedx.core.lib.xblock_utils import (
)
from openedx.core.lib.xblock_utils import request_token as xblock_request_token
from openedx.core.lib.xblock_utils import wrap_xblock
-from openedx.features.course_duration_limits.access import course_expiration_wrapper, offer_banner_wrapper
+from openedx.features.course_duration_limits.access import course_expiration_wrapper
from student.models import anonymous_id_for_user, user_by_anonymous_id
from student.roles import CourseBetaTesterRole
from track import contexts
@@ -731,7 +731,6 @@ def get_module_system_for_user(
block_wrappers.append(partial(display_access_messages, user))
block_wrappers.append(partial(course_expiration_wrapper, user))
- block_wrappers.append(partial(offer_banner_wrapper, user))
if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'):
if is_masquerading_as_specific_student(user, course_id):
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 927135ca20..3fcc003667 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -225,8 +225,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS = 20
@ddt.data(
- (ModuleStoreEnum.Type.mongo, 10, 185),
- (ModuleStoreEnum.Type.split, 4, 179),
+ (ModuleStoreEnum.Type.mongo, 10, 182),
+ (ModuleStoreEnum.Type.split, 4, 176),
)
@ddt.unpack
def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
diff --git a/lms/static/sass/_build-lms-v1.scss b/lms/static/sass/_build-lms-v1.scss
index fae155c379..ed3e8ad2d1 100644
--- a/lms/static/sass/_build-lms-v1.scss
+++ b/lms/static/sass/_build-lms-v1.scss
@@ -74,7 +74,6 @@
@import 'features/content-type-gating';
@import 'features/course-duration-limits';
@import 'features/enterprise-learner-portal-banner';
-@import 'features/first-purchase-banner';
// search
@import 'search/search';
diff --git a/lms/static/sass/bootstrap/lms-main.scss b/lms/static/sass/bootstrap/lms-main.scss
index fc384030be..b1a066465b 100644
--- a/lms/static/sass/bootstrap/lms-main.scss
+++ b/lms/static/sass/bootstrap/lms-main.scss
@@ -26,7 +26,7 @@ $static-path: '../..';
@import 'features/course-upgrade-message';
@import 'features/course-duration-limits';
@import 'features/enterprise-learner-portal-banner';
-@import 'features/first-purchase-banner';
+
// Individual Pages
@import "views/program-marketing-page";
diff --git a/lms/static/sass/features/_course-experience.scss b/lms/static/sass/features/_course-experience.scss
index 70b7789846..23ed9e204c 100644
--- a/lms/static/sass/features/_course-experience.scss
+++ b/lms/static/sass/features/_course-experience.scss
@@ -20,6 +20,26 @@
}
}
+// First purchase offer banner
+.first-purchase-offer-banner {
+ background-color: #DEE3F1;
+ font-size: 16px;
+ border-radius: 7px;
+ padding: 20px;
+ margin: 20px auto;
+
+ .first-purchase-offer-banner-bold {
+ font-weight: bold;
+ color: #23419F;
+ }
+
+ a {
+ color: #00496f;
+ text-decoration: underline;
+ font-weight: bold;
+ }
+}
+
// Course call to action message
.course-message {
display: flex;
diff --git a/lms/static/sass/features/_first-purchase-banner.scss b/lms/static/sass/features/_first-purchase-banner.scss
deleted file mode 100644
index 60bfe28214..0000000000
--- a/lms/static/sass/features/_first-purchase-banner.scss
+++ /dev/null
@@ -1,27 +0,0 @@
-// First purchase offer banner
-.first-purchase-offer-banner {
- background-color: #DEE3F1;
- font-size: 16px;
- border-radius: 7px;
- padding: 20px;
- margin: 20px auto;
- box-sizing: border-box;
- line-height: 1.5;
-
- .first-purchase-offer-banner-bold {
- font-weight: bold;
- color: #393f43;
- }
-
- a {
- color: #23419F !important;
- text-decoration: underline !important;
- font-weight: bold !important;
- border-bottom: none;
- }
-
-}
-
-#seq_content .first-purchase-offer-banner {
- max-width: $text-width-readability-max;
-}
diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss
index 9a78893ec1..9624b84803 100644
--- a/lms/static/sass/views/_verification.scss
+++ b/lms/static/sass/views/_verification.scss
@@ -2265,11 +2265,11 @@
.first-purchase-offer-banner-bold {
font-weight: bold;
- color: #393f43;
+ color: #23419f;
}
a {
- color: #23419F;
+ color: #00496f;
text-decoration: underline !important;
font-weight: bold;
border-bottom: none;
diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py
index daa49d0ec1..1e3515382c 100644
--- a/openedx/features/course_duration_limits/access.py
+++ b/openedx/features/course_duration_limits/access.py
@@ -22,7 +22,6 @@ from openedx.core.djangoapps.catalog.utils import get_course_run_details
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangolib.markup import HTML
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
-from openedx.features.discounts.utils import get_first_purchase_offer_banner_fragment
from student.models import CourseEnrollment
from util.date_utils import strftime_localized
@@ -238,28 +237,3 @@ def course_expiration_wrapper(user, block, view, frag, context): # pylint: disa
course_expiration_fragment.add_fragment_resources(frag)
return course_expiration_fragment
-
-
-def offer_banner_wrapper(user, block, view, frag, context): # pylint: disable=W0613
- """
- A wrapper that prepends the First Purchase Discount banner if
- the user hasn't upgraded yet.
- """
- if block.category != "vertical":
- return frag
-
- course = CourseOverview.get_from_id(block.course_id)
- offer_banner_fragment = get_first_purchase_offer_banner_fragment(user, course)
-
- if not offer_banner_fragment:
- return frag
-
- # Course content must be escaped to render correctly due to the way the
- # way the XBlock rendering works. Transforming the safe markup to unicode
- # escapes correctly.
- offer_banner_fragment.content = six.text_type(offer_banner_fragment.content)
-
- offer_banner_fragment.add_content(frag.content)
- offer_banner_fragment.add_fragment_resources(frag)
-
- return offer_banner_fragment
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 9f71e21641..90de4d8c70 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -413,8 +413,8 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
)
self.assertRedirects(response, expected_url)
- @mock.patch('openedx.features.discounts.utils.discount_percentage')
- @mock.patch('openedx.features.discounts.utils.can_receive_discount')
+ @mock.patch('openedx.features.course_experience.utils.discount_percentage')
+ @mock.patch('openedx.features.course_experience.utils.can_receive_discount')
@ddt.data(
[True, 15],
[True, 13],
diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py
index 1a799d5d3f..adc68ef974 100644
--- a/openedx/features/course_experience/utils.py
+++ b/openedx/features/course_experience/utils.py
@@ -213,3 +213,30 @@ def get_resume_block(block):
if resume_block:
return resume_block
return block
+
+
+def get_first_purchase_offer_banner_fragment(user, course):
+ if user and course:
+ discount_expiration_date = get_discount_expiration_date(user, course)
+ if (discount_expiration_date and
+ can_receive_discount(user=user, course=course, discount_expiration_date=discount_expiration_date)):
+ # Translator: xgettext:no-python-format
+ offer_message = _(u'{banner_open} Upgrade by {discount_expiration_date} and save {percentage}% '
+ u'[{strikeout_price}]{span_close}{br}Discount will be automatically applied at checkout. '
+ u'{a_open}Upgrade Now{a_close}{div_close}')
+ return Fragment(HTML(offer_message).format(
+ a_open=HTML(u'').format(
+ upgrade_link=verified_upgrade_deadline_link(user=user, course=course)
+ ),
+ a_close=HTML(''),
+ br=HTML('
'),
+ banner_open=HTML(
+ '