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):
# # of sql queries to default,
# # of mongo queries,
# )
('no_overrides', 1, True, False): (25, 1),
('no_overrides', 2, True, False): (25, 1),
('no_overrides', 3, True, False): (25, 1),
('ccx', 1, True, False): (25, 1),
('ccx', 2, True, False): (25, 1),
('ccx', 3, True, False): (25, 1),
('no_overrides', 1, False, False): (25, 1),
('no_overrides', 2, False, False): (25, 1),
('no_overrides', 3, False, False): (25, 1),
('ccx', 1, False, False): (25, 1),
('ccx', 2, False, False): (25, 1),
('ccx', 3, False, False): (25, 1),
('no_overrides', 1, True, False): (27, 1),
('no_overrides', 2, True, False): (27, 1),
('no_overrides', 3, True, False): (27, 1),
('ccx', 1, True, False): (27, 1),
('ccx', 2, True, False): (27, 1),
('ccx', 3, True, False): (27, 1),
('no_overrides', 1, False, False): (27, 1),
('no_overrides', 2, False, False): (27, 1),
('no_overrides', 3, False, False): (27, 1),
('ccx', 1, False, False): (27, 1),
('ccx', 2, False, False): (27, 1),
('ccx', 3, False, False): (27, 1),
}
......@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__ = True
TEST_DATA = {
('no_overrides', 1, True, False): (25, 3),
('no_overrides', 2, True, False): (25, 3),
('no_overrides', 3, True, False): (25, 3),
('ccx', 1, True, False): (25, 3),
('ccx', 2, True, False): (25, 3),
('ccx', 3, True, False): (25, 3),
('ccx', 1, True, True): (26, 3),
('ccx', 2, True, True): (26, 3),
('ccx', 3, True, True): (26, 3),
('no_overrides', 1, False, False): (25, 3),
('no_overrides', 2, False, False): (25, 3),
('no_overrides', 3, False, False): (25, 3),
('ccx', 1, False, False): (25, 3),
('ccx', 2, False, False): (25, 3),
('ccx', 3, False, False): (25, 3),
('no_overrides', 1, True, False): (27, 3),
('no_overrides', 2, True, False): (27, 3),
('no_overrides', 3, True, False): (27, 3),
('ccx', 1, True, False): (27, 3),
('ccx', 2, True, False): (27, 3),
('ccx', 3, True, False): (27, 3),
('ccx', 1, True, True): (28, 3),
('ccx', 2, True, True): (28, 3),
('ccx', 3, True, True): (28, 3),
('no_overrides', 1, False, False): (27, 3),
('no_overrides', 2, False, False): (27, 3),
('no_overrides', 3, False, False): (27, 3),
('ccx', 1, False, False): (27, 3),
('ccx', 2, False, False): (27, 3),
('ccx', 3, False, False): (27, 3),
}
......@@ -72,9 +72,10 @@ class DateSummary(object):
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.user = user
self.course_id = course_id or self.course.id
@property
def relative_datestring(self):
......@@ -174,7 +175,7 @@ class CourseEndDate(DateSummary):
@property
def description(self):
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):
return _('To earn a certificate, you must complete all requirements before this date.')
else:
......@@ -204,10 +205,10 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
ecommerce_service = EcommerceService()
if ecommerce_service.is_enabled(self.user):
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 reverse('verify_student_upgrade_and_verify', args=(self.course.id,))
return reverse('verify_student_upgrade_and_verify', args=(self.course_id,))
@property
def is_enabled(self):
......@@ -221,7 +222,7 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
if not is_enabled:
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
if enrollment_mode is None and is_active is None:
......@@ -234,7 +235,7 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
def date(self):
try:
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
except CourseMode.DoesNotExist:
......@@ -273,7 +274,7 @@ class VerificationDeadlineDate(DateSummary):
'verification-deadline-retry': (_('Retry Verification'), reverse('verify_student_reverify')),
'verification-deadline-upcoming': (
_('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):
@lazy
def date(self):
return VerificationDeadline.deadline_for_course(self.course.id)
return VerificationDeadline.deadline_for_course(self.course_id)
@lazy
def is_enabled(self):
if self.date is None:
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':
return self.verification_status in ('expired', 'none', 'must_reverify')
return False
......
......@@ -367,7 +367,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
self.assertEqual(resp.status_code, 200)
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):
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):
)
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):
"""Verify the block link redirects to ecommerce checkout if it's enabled."""
sku = 'TESTSKU'
......
......@@ -210,8 +210,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS = 20
@ddt.data(
(ModuleStoreEnum.Type.mongo, 10, 145),
(ModuleStoreEnum.Type.split, 4, 145),
(ModuleStoreEnum.Type.mongo, 10, 147),
(ModuleStoreEnum.Type.split, 4, 147),
)
@ddt.unpack
def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
......@@ -572,16 +572,18 @@ class ViewsTestCase(ModuleStoreTestCase):
"""
registration_price = 99
self.course.cosmetic_display_price = 10
with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
# Since registration_price is set, it overrides the cosmetic_display_price and should be returned
self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "$99")
self.assertEqual(views.get_cosmetic_display_price(self.course), "$99")
registration_price = 0
with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
# Since registration_price is not set, cosmetic_display_price should be returned
self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "$10")
self.assertEqual(views.get_cosmetic_display_price(self.course), "$10")
self.course.cosmetic_display_price = 0
# 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):
# TODO add a test for invalid location
......@@ -1420,12 +1422,12 @@ class ProgressPageTests(ProgressPageBaseTests):
"""Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration(enabled=self_paced_enabled).save()
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()
@ddt.data(
(False, 41, 27),
(True, 34, 23)
(False, 43, 29),
(True, 36, 25)
)
@ddt.unpack
def test_progress_queries(self, enable_waffle, initial, subsequent):
......
......@@ -55,7 +55,10 @@ from ..entrance_exams import (
from ..masquerade import setup_masquerade
from ..model_data import FieldDataCache
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}
......@@ -149,7 +152,7 @@ class CoursewareIndex(View):
self._save_positions()
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):
"""
......@@ -319,12 +322,11 @@ class CoursewareIndex(View):
save_child_position(self.course, self.chapter_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.
Also returns the table of contents for the courseware.
"""
request = RequestCache.get_current_request()
course_url_name = default_course_url_name(request)
course_url = reverse(course_url_name, kwargs={'course_id': unicode(self.course.id)})
courseware_context = {
......@@ -346,6 +348,8 @@ class CoursewareIndex(View):
'section_title': None,
'sequence_title': None,
'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(
self.effective_user,
......
......@@ -320,30 +320,12 @@ def course_info(request, course_id):
if settings.FEATURES.get('ENABLE_MKTG_SITE'):
url_to_enroll = marketing_link('COURSES')
store_upgrade_cookie = False
upgrade_cookie_name = 'show_upgrade_notification'
upgrade_link = None
# Construct the dates fragment
dates_fragment = None
if request.user.is_authenticated():
if SelfPacedConfiguration.current().enable_course_home_improvements:
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 = {
'request': request,
......@@ -358,7 +340,8 @@ def course_info(request, course_id):
'show_enroll_banner': show_enroll_banner,
'dates_fragment': dates_fragment,
'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
......@@ -375,25 +358,22 @@ def course_info(request, course_id):
if CourseEnrollment.is_enrolled(request.user, course.id):
inject_coursetalk_keys_into_context(context, course_key)
response = 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])
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:
cookie_value = course_id
if cookie_value is not None:
response.set_cookie(
upgrade_cookie_name,
cookie_value,
max_age=10 * 24 * 60 * 60, # set for 10 days
domain=settings.SESSION_COOKIE_DOMAIN,
httponly=True # no use case for accessing from JavaScript
)
return render_to_response('courseware/info.html', context)
UPGRADE_COOKIE_NAME = 'show_upgrade_notification'
return response
def check_and_get_upgrade_link(request, user, course_id):
upgrade_link = None
if request.user.is_authenticated():
upgrade_data = VerifiedUpgradeDeadlineDate(None, user, course_id=course_id)
if upgrade_data.is_enabled:
upgrade_link = upgrade_data.link
request.need_to_set_upgrade_cookie = True
return upgrade_link
class StaticCourseTabView(EdxFragmentView):
......@@ -518,6 +498,8 @@ class CourseTabView(EdxFragmentView):
'supports_preview_menu': supports_preview_menu,
'uses_pattern_library': 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):
......@@ -569,23 +551,57 @@ def registered_for_course(course, user):
return False
def get_cosmetic_display_price(course, registration_price):
def get_cosmetic_verified_display_price(course):
"""
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 Course Price as a string preceded by correct currency, or 'Free'
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]
price = course.cosmetic_display_price
if registration_price > 0:
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:
# 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.
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:
# 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):
......@@ -720,12 +736,7 @@ def course_about(request, course_id):
if 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 = CourseMode.min_course_price_for_currency(
course_key,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
course_price = get_cosmetic_display_price(course, registration_price)
registration_price, course_price = get_course_prices(course)
# 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
......@@ -889,6 +900,8 @@ def _progress(request, course_key, student_id):
'passed': is_course_passed(course, grade_summary),
'credit_course_requirements': _credit_course_requirements(course_key, student),
'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():
......
......@@ -39,6 +39,8 @@ from django_comment_client.utils import (
strip_none
)
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 openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from rest_framework import status
......@@ -440,7 +442,9 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
'sort_preference': cc_user.default_sort_key,
'category_map': course_settings["category_map"],
'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
......
......@@ -82,26 +82,6 @@ from openedx.core.djangolib.markup import HTML, Text
</div>
% 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>
${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
<%block name="headextra"/>
<%block name="head_extra"/>
<%include file="/courseware/upgrade.html"/>
<%static:optional_include_mako file="head-extra.html" is_theming_enabled="True" />
<%include file="widgets/optimizely.html" />
......
......@@ -89,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
course_home_url(self.course)
# Fetch the view and verify the query counts
with self.assertNumQueries(42):
with self.assertNumQueries(45):
with check_mongo_calls(5):
url = course_home_url(self.course)
self.client.get(url)
......@@ -124,7 +124,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
course_updates_url(self.course)
# 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):
url = course_updates_url(self.course)
self.client.get(url)
......@@ -8,6 +8,7 @@ from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment
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 ..utils import get_course_outline_block_tree
......@@ -30,7 +31,9 @@ class CourseOutlineFragmentView(EdxFragmentView):
context = {
'csrf': csrf(request)['csrf_token'],
'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)
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