Commit fdafd53a by Nimisha Asthagiri Committed by Calen Pennington

Upsell Courses in Courseware, CourseOutline, Discussions, Progress

parent 4e9d41f2
...@@ -231,18 +231,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): ...@@ -231,18 +231,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of sql queries to default, # # of sql queries to default,
# # of mongo queries, # # of mongo queries,
# ) # )
('no_overrides', 1, True, False): (25, 1), ('no_overrides', 1, True, False): (27, 1),
('no_overrides', 2, True, False): (25, 1), ('no_overrides', 2, True, False): (27, 1),
('no_overrides', 3, True, False): (25, 1), ('no_overrides', 3, True, False): (27, 1),
('ccx', 1, True, False): (25, 1), ('ccx', 1, True, False): (27, 1),
('ccx', 2, True, False): (25, 1), ('ccx', 2, True, False): (27, 1),
('ccx', 3, True, False): (25, 1), ('ccx', 3, True, False): (27, 1),
('no_overrides', 1, False, False): (25, 1), ('no_overrides', 1, False, False): (27, 1),
('no_overrides', 2, False, False): (25, 1), ('no_overrides', 2, False, False): (27, 1),
('no_overrides', 3, False, False): (25, 1), ('no_overrides', 3, False, False): (27, 1),
('ccx', 1, False, False): (25, 1), ('ccx', 1, False, False): (27, 1),
('ccx', 2, False, False): (25, 1), ('ccx', 2, False, False): (27, 1),
('ccx', 3, False, False): (25, 1), ('ccx', 3, False, False): (27, 1),
} }
...@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): ...@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__ = True __test__ = True
TEST_DATA = { TEST_DATA = {
('no_overrides', 1, True, False): (25, 3), ('no_overrides', 1, True, False): (27, 3),
('no_overrides', 2, True, False): (25, 3), ('no_overrides', 2, True, False): (27, 3),
('no_overrides', 3, True, False): (25, 3), ('no_overrides', 3, True, False): (27, 3),
('ccx', 1, True, False): (25, 3), ('ccx', 1, True, False): (27, 3),
('ccx', 2, True, False): (25, 3), ('ccx', 2, True, False): (27, 3),
('ccx', 3, True, False): (25, 3), ('ccx', 3, True, False): (27, 3),
('ccx', 1, True, True): (26, 3), ('ccx', 1, True, True): (28, 3),
('ccx', 2, True, True): (26, 3), ('ccx', 2, True, True): (28, 3),
('ccx', 3, True, True): (26, 3), ('ccx', 3, True, True): (28, 3),
('no_overrides', 1, False, False): (25, 3), ('no_overrides', 1, False, False): (27, 3),
('no_overrides', 2, False, False): (25, 3), ('no_overrides', 2, False, False): (27, 3),
('no_overrides', 3, False, False): (25, 3), ('no_overrides', 3, False, False): (27, 3),
('ccx', 1, False, False): (25, 3), ('ccx', 1, False, False): (27, 3),
('ccx', 2, False, False): (25, 3), ('ccx', 2, False, False): (27, 3),
('ccx', 3, False, False): (25, 3), ('ccx', 3, False, False): (27, 3),
} }
...@@ -72,9 +72,10 @@ class DateSummary(object): ...@@ -72,9 +72,10 @@ class DateSummary(object):
self.user.preferences.model.get_value(self.user, "time_zone", "UTC") self.user.preferences.model.get_value(self.user, "time_zone", "UTC")
) )
def __init__(self, course, user): def __init__(self, course, user, course_id=None):
self.course = course self.course = course
self.user = user self.user = user
self.course_id = course_id or self.course.id
@property @property
def relative_datestring(self): def relative_datestring(self):
...@@ -174,7 +175,7 @@ class CourseEndDate(DateSummary): ...@@ -174,7 +175,7 @@ class CourseEndDate(DateSummary):
@property @property
def description(self): def description(self):
if datetime.now(utc) <= self.date: if datetime.now(utc) <= self.date:
mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
if is_active and CourseMode.is_eligible_for_certificate(mode): if is_active and CourseMode.is_eligible_for_certificate(mode):
return _('To earn a certificate, you must complete all requirements before this date.') return _('To earn a certificate, you must complete all requirements before this date.')
else: else:
...@@ -204,10 +205,10 @@ class VerifiedUpgradeDeadlineDate(DateSummary): ...@@ -204,10 +205,10 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
ecommerce_service = EcommerceService() ecommerce_service = EcommerceService()
if ecommerce_service.is_enabled(self.user): if ecommerce_service.is_enabled(self.user):
course_mode = CourseMode.objects.get( course_mode = CourseMode.objects.get(
course_id=self.course.id, mode_slug=CourseMode.VERIFIED course_id=self.course_id, mode_slug=CourseMode.VERIFIED
) )
return ecommerce_service.checkout_page_url(course_mode.sku) return ecommerce_service.checkout_page_url(course_mode.sku)
return reverse('verify_student_upgrade_and_verify', args=(self.course.id,)) return reverse('verify_student_upgrade_and_verify', args=(self.course_id,))
@property @property
def is_enabled(self): def is_enabled(self):
...@@ -221,7 +222,7 @@ class VerifiedUpgradeDeadlineDate(DateSummary): ...@@ -221,7 +222,7 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
if not is_enabled: if not is_enabled:
return False return False
enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
# Return `true` if user is not enrolled in course # Return `true` if user is not enrolled in course
if enrollment_mode is None and is_active is None: if enrollment_mode is None and is_active is None:
...@@ -234,7 +235,7 @@ class VerifiedUpgradeDeadlineDate(DateSummary): ...@@ -234,7 +235,7 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
def date(self): def date(self):
try: try:
verified_mode = CourseMode.objects.get( verified_mode = CourseMode.objects.get(
course_id=self.course.id, mode_slug=CourseMode.VERIFIED course_id=self.course_id, mode_slug=CourseMode.VERIFIED
) )
return verified_mode.expiration_datetime return verified_mode.expiration_datetime
except CourseMode.DoesNotExist: except CourseMode.DoesNotExist:
...@@ -273,7 +274,7 @@ class VerificationDeadlineDate(DateSummary): ...@@ -273,7 +274,7 @@ class VerificationDeadlineDate(DateSummary):
'verification-deadline-retry': (_('Retry Verification'), reverse('verify_student_reverify')), 'verification-deadline-retry': (_('Retry Verification'), reverse('verify_student_reverify')),
'verification-deadline-upcoming': ( 'verification-deadline-upcoming': (
_('Verify My Identity'), _('Verify My Identity'),
reverse('verify_student_verify_now', args=(self.course.id,)) reverse('verify_student_verify_now', args=(self.course_id,))
) )
} }
...@@ -297,13 +298,13 @@ class VerificationDeadlineDate(DateSummary): ...@@ -297,13 +298,13 @@ class VerificationDeadlineDate(DateSummary):
@lazy @lazy
def date(self): def date(self):
return VerificationDeadline.deadline_for_course(self.course.id) return VerificationDeadline.deadline_for_course(self.course_id)
@lazy @lazy
def is_enabled(self): def is_enabled(self):
if self.date is None: if self.date is None:
return False return False
(mode, is_active) = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) (mode, is_active) = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
if is_active and mode == 'verified': if is_active and mode == 'verified':
return self.verification_status in ('expired', 'none', 'must_reverify') return self.verification_status in ('expired', 'none', 'must_reverify')
return False return False
......
...@@ -367,7 +367,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest ...@@ -367,7 +367,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
def test_num_queries_instructor_paced(self): def test_num_queries_instructor_paced(self):
self.fetch_course_info_with_queries(self.instructor_paced_course, 21, 4) self.fetch_course_info_with_queries(self.instructor_paced_course, 23, 4)
def test_num_queries_self_paced(self): def test_num_queries_self_paced(self):
self.fetch_course_info_with_queries(self.self_paced_course, 21, 4) self.fetch_course_info_with_queries(self.self_paced_course, 23, 4)
...@@ -310,89 +310,6 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): ...@@ -310,89 +310,6 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
) )
self.assertEqual(block.title, 'Course End') self.assertEqual(block.title, 'Course End')
# Tests Verified Upgrade Deadline Date Block
def check_upgrade_banner(
self,
banner_expected=True,
include_url_parameter=True,
expected_cookie_value=None
):
"""
Helper method to check for the presence of the Upgrade Banner
"""
url = reverse('info', args=[self.course.id.to_deprecated_string()])
if include_url_parameter:
url += '?upgrade=true'
resp = self.client.get(url)
upgrade_cookie_name = 'show_upgrade_notification'
expected_banner_text = "Give yourself an additional incentive to complete"
if banner_expected:
self.assertIn(expected_banner_text, resp.content)
self.assertIn(str(self.course.id), self.client.cookies[upgrade_cookie_name].value)
else:
self.assertNotIn(expected_banner_text, resp.content)
if upgrade_cookie_name in self.client.cookies:
self.assertNotIn(str(self.course.id), self.client.cookies[upgrade_cookie_name].value)
if expected_cookie_value is not None:
self.assertIn(str(expected_cookie_value), self.client.cookies[upgrade_cookie_name].value)
def test_verified_upgrade_deadline_date(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_upgrade_deadline=1, user_enrollment_mode=CourseMode.AUDIT)
self.client.login(username='mrrobot', password='test')
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertEqual(block.date, datetime.now(utc) + timedelta(days=1))
self.assertTrue(block.is_enabled)
self.assertEqual(block.link, reverse('verify_student_upgrade_and_verify', args=(self.course.id,)))
self.check_upgrade_banner()
def test_without_upgrade_deadline(self):
self.setup_course_and_user(enrollment_mode=None)
self.client.login(username='mrrobot', password='test')
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertFalse(block.is_enabled)
self.assertIsNone(block.date)
self.check_upgrade_banner(banner_expected=False)
def test_verified_upgrade_banner_not_present_past_deadline(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_upgrade_deadline=-1, user_enrollment_mode=CourseMode.AUDIT)
self.client.login(username='mrrobot', password='test')
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertFalse(block.is_enabled)
self.check_upgrade_banner(banner_expected=False)
def test_verified_upgrade_banner_cookie(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_upgrade_deadline=1, user_enrollment_mode=CourseMode.AUDIT)
self.client.login(username='mrrobot', password='test')
# No URL parameter or cookie present, notification should not be shown.
self.check_upgrade_banner(include_url_parameter=False, banner_expected=False)
# Now pass URL parameter-- notification should be shown.
self.check_upgrade_banner(include_url_parameter=True)
# A cookie should be set in the previous call, so it is no longer necessary to pass
# the URL parameter in order to see the notification.
self.check_upgrade_banner(include_url_parameter=False)
# Store the current course_id for testing
old_course_id = self.course.id
# Change to another course
self.setup_course_and_user(days_till_upgrade_deadline=1,
user_enrollment_mode=CourseMode.AUDIT,
create_user=False)
# Banner should not be present in the newly created course
self.check_upgrade_banner(include_url_parameter=False,
banner_expected=False,
expected_cookie_value=old_course_id)
# Unfortunately (according to django doc), it is not possible to test expiration of the cookie.
def test_ecommerce_checkout_redirect(self): def test_ecommerce_checkout_redirect(self):
"""Verify the block link redirects to ecommerce checkout if it's enabled.""" """Verify the block link redirects to ecommerce checkout if it's enabled."""
sku = 'TESTSKU' sku = 'TESTSKU'
......
...@@ -210,8 +210,8 @@ class IndexQueryTestCase(ModuleStoreTestCase): ...@@ -210,8 +210,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS = 20 NUM_PROBLEMS = 20
@ddt.data( @ddt.data(
(ModuleStoreEnum.Type.mongo, 10, 145), (ModuleStoreEnum.Type.mongo, 10, 147),
(ModuleStoreEnum.Type.split, 4, 145), (ModuleStoreEnum.Type.split, 4, 147),
) )
@ddt.unpack @ddt.unpack
def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count): def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
...@@ -572,16 +572,18 @@ class ViewsTestCase(ModuleStoreTestCase): ...@@ -572,16 +572,18 @@ class ViewsTestCase(ModuleStoreTestCase):
""" """
registration_price = 99 registration_price = 99
self.course.cosmetic_display_price = 10 self.course.cosmetic_display_price = 10
# Since registration_price is set, it overrides the cosmetic_display_price and should be returned with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "$99") # Since registration_price is set, it overrides the cosmetic_display_price and should be returned
self.assertEqual(views.get_cosmetic_display_price(self.course), "$99")
registration_price = 0 registration_price = 0
# Since registration_price is not set, cosmetic_display_price should be returned with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "$10") # Since registration_price is not set, cosmetic_display_price should be returned
self.assertEqual(views.get_cosmetic_display_price(self.course), "$10")
self.course.cosmetic_display_price = 0 self.course.cosmetic_display_price = 0
# Since both prices are not set, there is no price, thus "Free" # Since both prices are not set, there is no price, thus "Free"
self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "Free") self.assertEqual(views.get_cosmetic_display_price(self.course), "Free")
def test_jump_to_invalid(self): def test_jump_to_invalid(self):
# TODO add a test for invalid location # TODO add a test for invalid location
...@@ -1420,12 +1422,12 @@ class ProgressPageTests(ProgressPageBaseTests): ...@@ -1420,12 +1422,12 @@ class ProgressPageTests(ProgressPageBaseTests):
"""Test that query counts remain the same for self-paced and instructor-paced courses.""" """Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration(enabled=self_paced_enabled).save() SelfPacedConfiguration(enabled=self_paced_enabled).save()
self.setup_course(self_paced=self_paced) self.setup_course(self_paced=self_paced)
with self.assertNumQueries(41), check_mongo_calls(1): with self.assertNumQueries(43), check_mongo_calls(1):
self._get_progress_page() self._get_progress_page()
@ddt.data( @ddt.data(
(False, 41, 27), (False, 43, 29),
(True, 34, 23) (True, 36, 25)
) )
@ddt.unpack @ddt.unpack
def test_progress_queries(self, enable_waffle, initial, subsequent): def test_progress_queries(self, enable_waffle, initial, subsequent):
......
...@@ -55,7 +55,10 @@ from ..entrance_exams import ( ...@@ -55,7 +55,10 @@ from ..entrance_exams import (
from ..masquerade import setup_masquerade from ..masquerade import setup_masquerade
from ..model_data import FieldDataCache from ..model_data import FieldDataCache
from ..module_render import toc_for_course, get_module_for_descriptor from ..module_render import toc_for_course, get_module_for_descriptor
from .views import CourseTabView, check_access_to_course from .views import (
CourseTabView, check_access_to_course, check_and_get_upgrade_link,
get_cosmetic_verified_display_price
)
TEMPLATE_IMPORTS = {'urllib': urllib} TEMPLATE_IMPORTS = {'urllib': urllib}
...@@ -149,7 +152,7 @@ class CoursewareIndex(View): ...@@ -149,7 +152,7 @@ class CoursewareIndex(View):
self._save_positions() self._save_positions()
self._prefetch_and_bind_section() self._prefetch_and_bind_section()
return render_to_response('courseware/courseware.html', self._create_courseware_context()) return render_to_response('courseware/courseware.html', self._create_courseware_context(request))
def _redirect_if_not_requested_section(self): def _redirect_if_not_requested_section(self):
""" """
...@@ -319,12 +322,11 @@ class CoursewareIndex(View): ...@@ -319,12 +322,11 @@ class CoursewareIndex(View):
save_child_position(self.course, self.chapter_url_name) save_child_position(self.course, self.chapter_url_name)
save_child_position(self.chapter, self.section_url_name) save_child_position(self.chapter, self.section_url_name)
def _create_courseware_context(self): def _create_courseware_context(self, request):
""" """
Returns and creates the rendering context for the courseware. Returns and creates the rendering context for the courseware.
Also returns the table of contents for the courseware. Also returns the table of contents for the courseware.
""" """
request = RequestCache.get_current_request()
course_url_name = default_course_url_name(request) course_url_name = default_course_url_name(request)
course_url = reverse(course_url_name, kwargs={'course_id': unicode(self.course.id)}) course_url = reverse(course_url_name, kwargs={'course_id': unicode(self.course.id)})
courseware_context = { courseware_context = {
...@@ -346,6 +348,8 @@ class CoursewareIndex(View): ...@@ -346,6 +348,8 @@ class CoursewareIndex(View):
'section_title': None, 'section_title': None,
'sequence_title': None, 'sequence_title': None,
'disable_accordion': waffle.flag_is_active(request, UNIFIED_COURSE_VIEW_FLAG), 'disable_accordion': waffle.flag_is_active(request, UNIFIED_COURSE_VIEW_FLAG),
'upgrade_link': check_and_get_upgrade_link(request, self.effective_user, self.course.id),
'upgrade_price': get_cosmetic_verified_display_price(self.course),
} }
table_of_contents = toc_for_course( table_of_contents = toc_for_course(
self.effective_user, self.effective_user,
......
...@@ -320,30 +320,12 @@ def course_info(request, course_id): ...@@ -320,30 +320,12 @@ def course_info(request, course_id):
if settings.FEATURES.get('ENABLE_MKTG_SITE'): if settings.FEATURES.get('ENABLE_MKTG_SITE'):
url_to_enroll = marketing_link('COURSES') url_to_enroll = marketing_link('COURSES')
store_upgrade_cookie = False
upgrade_cookie_name = 'show_upgrade_notification'
upgrade_link = None
# Construct the dates fragment # Construct the dates fragment
dates_fragment = None dates_fragment = None
if request.user.is_authenticated(): if request.user.is_authenticated():
if SelfPacedConfiguration.current().enable_course_home_improvements: if SelfPacedConfiguration.current().enable_course_home_improvements:
dates_fragment = CourseDatesFragmentView().render_to_fragment(request, course_id=course_id) dates_fragment = CourseDatesFragmentView().render_to_fragment(request, course_id=course_id)
show_upgrade_notification = False
if request.GET.get('upgrade', 'false') == 'true':
store_upgrade_cookie = True
show_upgrade_notification = True
elif upgrade_cookie_name in request.COOKIES and course_id in request.COOKIES[upgrade_cookie_name]:
show_upgrade_notification = True
if show_upgrade_notification:
upgrade_data = VerifiedUpgradeDeadlineDate(course, user)
if upgrade_data.is_enabled:
upgrade_link = upgrade_data.link
else:
# The upgrade is not enabled so the cookie does not need to be stored
store_upgrade_cookie = False
context = { context = {
'request': request, 'request': request,
...@@ -358,7 +340,8 @@ def course_info(request, course_id): ...@@ -358,7 +340,8 @@ def course_info(request, course_id):
'show_enroll_banner': show_enroll_banner, 'show_enroll_banner': show_enroll_banner,
'dates_fragment': dates_fragment, 'dates_fragment': dates_fragment,
'url_to_enroll': url_to_enroll, 'url_to_enroll': url_to_enroll,
'upgrade_link': upgrade_link, 'upgrade_link': check_and_get_upgrade_link(request, user, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
} }
# Get the URL of the user's last position in order to display the 'where you were last' message # Get the URL of the user's last position in order to display the 'where you were last' message
...@@ -375,25 +358,22 @@ def course_info(request, course_id): ...@@ -375,25 +358,22 @@ def course_info(request, course_id):
if CourseEnrollment.is_enrolled(request.user, course.id): if CourseEnrollment.is_enrolled(request.user, course.id):
inject_coursetalk_keys_into_context(context, course_key) inject_coursetalk_keys_into_context(context, course_key)
response = render_to_response('courseware/info.html', context) return render_to_response('courseware/info.html', context)
if store_upgrade_cookie:
if upgrade_cookie_name in request.COOKIES and str(course_id) not in request.COOKIES[upgrade_cookie_name]:
cookie_value = '%s,%s' % (course_id, request.COOKIES[upgrade_cookie_name]) UPGRADE_COOKIE_NAME = 'show_upgrade_notification'
elif upgrade_cookie_name in request.COOKIES and str(course_id) in request.COOKIES[upgrade_cookie_name]:
cookie_value = request.COOKIES[upgrade_cookie_name]
else: def check_and_get_upgrade_link(request, user, course_id):
cookie_value = course_id upgrade_link = None
if cookie_value is not None: if request.user.is_authenticated():
response.set_cookie( upgrade_data = VerifiedUpgradeDeadlineDate(None, user, course_id=course_id)
upgrade_cookie_name, if upgrade_data.is_enabled:
cookie_value, upgrade_link = upgrade_data.link
max_age=10 * 24 * 60 * 60, # set for 10 days request.need_to_set_upgrade_cookie = True
domain=settings.SESSION_COOKIE_DOMAIN,
httponly=True # no use case for accessing from JavaScript
)
return response return upgrade_link
class StaticCourseTabView(EdxFragmentView): class StaticCourseTabView(EdxFragmentView):
...@@ -518,6 +498,8 @@ class CourseTabView(EdxFragmentView): ...@@ -518,6 +498,8 @@ class CourseTabView(EdxFragmentView):
'supports_preview_menu': supports_preview_menu, 'supports_preview_menu': supports_preview_menu,
'uses_pattern_library': True, 'uses_pattern_library': True,
'disable_courseware_js': True, 'disable_courseware_js': True,
'upgrade_link': check_and_get_upgrade_link(request, request.user, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
} }
def render_to_fragment(self, request, course=None, page_context=None, **kwargs): def render_to_fragment(self, request, course=None, page_context=None, **kwargs):
...@@ -569,23 +551,57 @@ def registered_for_course(course, user): ...@@ -569,23 +551,57 @@ def registered_for_course(course, user):
return False return False
def get_cosmetic_display_price(course, registration_price): def get_cosmetic_verified_display_price(course):
""" """
Return Course Price as a string preceded by correct currency, or 'Free' Returns the minimum verified cert course price as a string preceded by correct currency, or 'Free'.
"""
return get_course_prices(course, verified_only=True)[1]
def get_cosmetic_display_price(course):
"""
Returns the course price as a string preceded by correct currency, or 'Free'.
"""
return get_course_prices(course)[1]
def get_course_prices(course, verified_only=False):
""" """
Return registration_price and cosmetic_display_prices.
registration_price is the minimum price for the course across all course modes.
cosmetic_display_prices is the course price as a string preceded by correct currency, or 'Free'.
"""
# Find the
if verified_only:
registration_price = CourseMode.min_course_price_for_verified_for_currency(
course.id,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
else:
registration_price = CourseMode.min_course_price_for_currency(
course.id,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
currency_symbol = settings.PAID_COURSE_REGISTRATION_CURRENCY[1] currency_symbol = settings.PAID_COURSE_REGISTRATION_CURRENCY[1]
price = course.cosmetic_display_price
if registration_price > 0: if registration_price > 0:
price = registration_price price = registration_price
# Handle course overview objects which have no cosmetic_display_price
elif hasattr(course, 'cosmetic_display_price'):
price = course.cosmetic_display_price
else:
price = None
if price: if price:
# Translators: This will look like '$50', where {currency_symbol} is a symbol such as '$' and {price} is a # Translators: This will look like '$50', where {currency_symbol} is a symbol such as '$' and {price} is a
# numerical amount in that currency. Adjust this display as needed for your language. # numerical amount in that currency. Adjust this display as needed for your language.
return _("{currency_symbol}{price}").format(currency_symbol=currency_symbol, price=price) cosmetic_display_price = _("{currency_symbol}{price}").format(currency_symbol=currency_symbol, price=price)
else: else:
# Translators: This refers to the cost of the course. In this case, the course costs nothing so it is free. # Translators: This refers to the cost of the course. In this case, the course costs nothing so it is free.
return _('Free') cosmetic_display_price = _('Free')
return registration_price, cosmetic_display_price
class EnrollStaffView(View): class EnrollStaffView(View):
...@@ -720,12 +736,7 @@ def course_about(request, course_id): ...@@ -720,12 +736,7 @@ def course_about(request, course_id):
if professional_mode.bulk_sku: if professional_mode.bulk_sku:
ecommerce_bulk_checkout_link = ecomm_service.checkout_page_url(professional_mode.bulk_sku) ecommerce_bulk_checkout_link = ecomm_service.checkout_page_url(professional_mode.bulk_sku)
# Find the minimum price for the course across all course modes registration_price, course_price = get_course_prices(course)
registration_price = CourseMode.min_course_price_for_currency(
course_key,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
course_price = get_cosmetic_display_price(course, registration_price)
# Determine which checkout workflow to use -- LMS shoppingcart or Otto basket # Determine which checkout workflow to use -- LMS shoppingcart or Otto basket
can_add_course_to_cart = _is_shopping_cart_enabled and registration_price and not ecommerce_checkout_link can_add_course_to_cart = _is_shopping_cart_enabled and registration_price and not ecommerce_checkout_link
...@@ -889,6 +900,8 @@ def _progress(request, course_key, student_id): ...@@ -889,6 +900,8 @@ def _progress(request, course_key, student_id):
'passed': is_course_passed(course, grade_summary), 'passed': is_course_passed(course, grade_summary),
'credit_course_requirements': _credit_course_requirements(course_key, student), 'credit_course_requirements': _credit_course_requirements(course_key, student),
'certificate_data': _get_cert_data(student, course, course_key, is_active, enrollment_mode), 'certificate_data': _get_cert_data(student, course, course_key, is_active, enrollment_mode),
'upgrade_link': check_and_get_upgrade_link(request, student, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
} }
with outer_atomic(): with outer_atomic():
......
...@@ -39,6 +39,8 @@ from django_comment_client.utils import ( ...@@ -39,6 +39,8 @@ from django_comment_client.utils import (
strip_none strip_none
) )
from django_comment_common.utils import ThreadContext, get_course_discussion_settings, set_course_discussion_settings from django_comment_common.utils import ThreadContext, get_course_discussion_settings, set_course_discussion_settings
from lms.djangoapps.courseware.views.views import check_and_get_upgrade_link, get_cosmetic_verified_display_price
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from rest_framework import status from rest_framework import status
...@@ -440,7 +442,9 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th ...@@ -440,7 +442,9 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
'sort_preference': cc_user.default_sort_key, 'sort_preference': cc_user.default_sort_key,
'category_map': course_settings["category_map"], 'category_map': course_settings["category_map"],
'course_settings': course_settings, 'course_settings': course_settings,
'is_commentable_divided': is_commentable_divided(course_key, discussion_id, course_discussion_settings) 'is_commentable_divided': is_commentable_divided(course_key, discussion_id, course_discussion_settings),
'upgrade_link': check_and_get_upgrade_link(request, user, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
}) })
return context return context
......
...@@ -82,26 +82,6 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -82,26 +82,6 @@ from openedx.core.djangolib.markup import HTML, Text
</div> </div>
% endif % endif
% if upgrade_link:
<div class="upgrade-banner">
<div class="notification-color-border"></div>
<div class="notification-content">
<div class="upgrade-icon">
<img src="${STATIC_URL}images/edx-verified-mini-cert.png">
</div>
<div class="upgrade-msg">
<h3 class="msg-title">${_("Give yourself an additional incentive to complete")}</h3>
<p class="view-verified-info">${_("Earn a verified certificate.")}
<a href="https://www.edx.org/verified-certificate" target="_blank">${_("Learn More")}</a>
</p>
</div>
<div class="upgrade-banner-button">
<a href="${upgrade_link}" class="btn-upgrade">${_("Upgrade Now")}</a>
</div>
</div>
</div>
% endif
<h3 class="hd hd-3">${_("Course Updates and News")}</h3> <h3 class="hd hd-3">${_("Course Updates and News")}</h3>
${HTML(get_course_info_section(request, masquerade_user, course, 'updates'))} ${HTML(get_course_info_section(request, masquerade_user, course, 'updates'))}
......
% if upgrade_link:
<script type="text/plain"
id="upgrade_user"
data-link="${upgrade_link}"
data-price="${upgrade_price}">
</script>
% endif
...@@ -98,6 +98,7 @@ from pipeline_mako import render_require_js_path_overrides ...@@ -98,6 +98,7 @@ from pipeline_mako import render_require_js_path_overrides
<%block name="headextra"/> <%block name="headextra"/>
<%block name="head_extra"/> <%block name="head_extra"/>
<%include file="/courseware/upgrade.html"/>
<%static:optional_include_mako file="head-extra.html" is_theming_enabled="True" /> <%static:optional_include_mako file="head-extra.html" is_theming_enabled="True" />
<%include file="widgets/optimizely.html" /> <%include file="widgets/optimizely.html" />
......
...@@ -89,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase): ...@@ -89,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
course_home_url(self.course) course_home_url(self.course)
# Fetch the view and verify the query counts # Fetch the view and verify the query counts
with self.assertNumQueries(42): with self.assertNumQueries(45):
with check_mongo_calls(5): with check_mongo_calls(5):
url = course_home_url(self.course) url = course_home_url(self.course)
self.client.get(url) self.client.get(url)
...@@ -124,7 +124,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase): ...@@ -124,7 +124,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
course_updates_url(self.course) course_updates_url(self.course)
# Fetch the view and verify that the query counts haven't changed # Fetch the view and verify that the query counts haven't changed
with self.assertNumQueries(32): with self.assertNumQueries(34):
with check_mongo_calls(4): with check_mongo_calls(4):
url = course_updates_url(self.course) url = course_updates_url(self.course)
self.client.get(url) self.client.get(url)
...@@ -8,6 +8,7 @@ from opaque_keys.edx.keys import CourseKey ...@@ -8,6 +8,7 @@ from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment from web_fragments.fragment import Fragment
from courseware.courses import get_course_overview_with_access from courseware.courses import get_course_overview_with_access
from lms.djangoapps.courseware.views.views import check_and_get_upgrade_link, get_cosmetic_verified_display_price
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from ..utils import get_course_outline_block_tree from ..utils import get_course_outline_block_tree
...@@ -30,7 +31,9 @@ class CourseOutlineFragmentView(EdxFragmentView): ...@@ -30,7 +31,9 @@ class CourseOutlineFragmentView(EdxFragmentView):
context = { context = {
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'course': course_overview, 'course': course_overview,
'blocks': course_block_tree 'blocks': course_block_tree,
'upgrade_link': check_and_get_upgrade_link(request, request.user, course_key),
'upgrade_price': get_cosmetic_verified_display_price(course_overview),
} }
html = render_to_string('course_experience/course-outline-fragment.html', context) html = render_to_string('course_experience/course-outline-fragment.html', context)
return Fragment(html) return Fragment(html)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment