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( + '
' + ), + discount_expiration_date=discount_expiration_date.strftime(u'%B %d'), + percentage=discount_percentage(), + span_close=HTML(''), + div_close=HTML('
'), + strikeout_price=HTML(format_strikeout_price(user, course, check_for_discount=False)[0]) + )) + return None diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py index f56fe26066..aeace743fa 100644 --- a/openedx/features/course_experience/views/course_home.py +++ b/openedx/features/course_experience/views/course_home.py @@ -30,7 +30,7 @@ from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.util.maintenance_banner import add_maintenance_banner from openedx.features.course_duration_limits.access import generate_course_expired_fragment from openedx.features.course_experience.course_tools import CourseToolsPluginManager -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.utils import format_strikeout_price from student.models import CourseEnrollment from util.views import ensure_valid_course_key diff --git a/openedx/features/discounts/applicability.py b/openedx/features/discounts/applicability.py index 4a5eac5cd9..6807c01a25 100644 --- a/openedx/features/discounts/applicability.py +++ b/openedx/features/discounts/applicability.py @@ -49,10 +49,6 @@ def get_discount_expiration_date(user, course): Returns the date when the discount expires for the user. Returns none if the user is not enrolled. """ - # anonymous users should never get the discount - if user.is_anonymous: - return None - course_enrollment = CourseEnrollment.objects.filter( user=user, course=course.id, @@ -103,6 +99,10 @@ def can_receive_discount(user, course, discount_expiration_date=None): # TODO: Add additional conditions to return False here + # anonymous users should never get the discount + if user.is_anonymous: + return False + # Check if discount has expired if not discount_expiration_date: discount_expiration_date = get_discount_expiration_date(user, course) diff --git a/openedx/features/discounts/utils.py b/openedx/features/discounts/utils.py index 9d43906e5e..bdd6bda86d 100644 --- a/openedx/features/discounts/utils.py +++ b/openedx/features/discounts/utils.py @@ -4,14 +4,9 @@ Utility functions for working with discounts and discounted pricing. from django.utils.translation import ugettext as _ from course_modes.models import get_course_prices, format_course_price -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link from openedx.core.djangolib.markup import HTML -from web_fragments.fragment import Fragment -from openedx.features.discounts.applicability import ( - can_receive_discount, - get_discount_expiration_date, - discount_percentage -) + +from .applicability import can_receive_discount, discount_percentage def format_strikeout_price(user, course, base_price=None, check_for_discount=True): @@ -60,35 +55,3 @@ def format_strikeout_price(user, course, base_price=None, check_for_discount=Tru ) else: return (HTML(u"{}").format(original_price), False) - - -def get_first_purchase_offer_banner_fragment(user, course): - """ - Return an HTML Fragment with First Purcahse Discount message, - which has the discount_expiration_date, price, - discount percentage and a link to upgrade. - """ - 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( - '
' - ), - discount_expiration_date=discount_expiration_date.strftime(u'%B %d'), - percentage=discount_percentage(), - span_close=HTML(''), - div_close=HTML('
'), - strikeout_price=HTML(format_strikeout_price(user, course, check_for_discount=False)[0]) - )) - return None