Merge pull request #28655 from edx/pshiu/REV-2325_TrackSelectionUnhappyPath
feat: Add screen for partial feature based enrollment
This commit is contained in:
@@ -4,7 +4,6 @@ Tests for course_modes views.
|
||||
|
||||
|
||||
import decimal
|
||||
import mock
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
@@ -35,6 +34,12 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from ..views import VALUE_PROP_TRACK_SELECTION_FLAG
|
||||
|
||||
# Name of the method to mock for Content Type Gating.
|
||||
GATING_METHOD_NAME = 'openedx.features.content_type_gating.models.ContentTypeGatingConfig.enabled_for_enrollment'
|
||||
|
||||
# Name of the method to mock for Course Duration Limits.
|
||||
CDL_METHOD_NAME = 'openedx.features.course_duration_limits.models.CourseDurationLimitConfig.enabled_for_enrollment'
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@@ -511,64 +516,118 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
redirect_url = reverse('dashboard') + '?course_closed=1%2F1%2F15%2C+12%3A00+AM'
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
# Value Prop TODO (REV-2378): remove waffle flag from tests once the new Track Selection template is rolled out.
|
||||
# Other tests may need to be updated/removed to reflect the new page.
|
||||
# The below test can be separated into multiple tests once un-happy path is implemented.
|
||||
def test_new_track_selection(self):
|
||||
# For the new track selection template to render, FBE must be fully on (gated_content and audit_access_deadline)
|
||||
# and happy path conditions must be met:
|
||||
# User can upgrade, FBE is fully on, and user is not an enterprise user.
|
||||
def _assert_fbe_page(self, response, min_price=None, **_):
|
||||
"""
|
||||
Assert fbe.html was rendered.
|
||||
"""
|
||||
self.assertContains(response, "Choose a path for your course in")
|
||||
|
||||
# Create the course modes and enroll the user
|
||||
# Check if it displays the upgrade price for verified track and "Free" for audit track
|
||||
self.assertContains(response, min_price)
|
||||
self.assertContains(response, "Free")
|
||||
|
||||
# Check for specific HTML elements
|
||||
self.assertContains(response, '<span class="award-icon">')
|
||||
self.assertContains(response, '<span class="popover-icon">')
|
||||
self.assertContains(response, '<span class="note-icon">')
|
||||
|
||||
# Check for upgrade button ID
|
||||
self.assertContains(response, 'track_selection_upgrade')
|
||||
|
||||
# Check for audit button ID
|
||||
self.assertContains(response, 'track_selection_audit')
|
||||
|
||||
# Check for happy path messaging - verified
|
||||
self.assertContains(response, '<li class="collapsible-item">')
|
||||
self.assertContains(response, 'access to all course activities')
|
||||
self.assertContains(response, 'Full access')
|
||||
|
||||
# Check for happy path messaging - audit
|
||||
self.assertContains(response, "discussion forums and non-graded assignments")
|
||||
self.assertContains(response, "Get temporary access")
|
||||
self.assertContains(response, "Access expires and all progress will be lost")
|
||||
|
||||
def _assert_unfbe_page(self, response, min_price=None, **_):
|
||||
"""
|
||||
Assert track_selection.html and unfbe.html were rendered.
|
||||
"""
|
||||
# Check for string unique to track_selection.html.
|
||||
self.assertContains(response, "| Upgrade Now")
|
||||
# This string only occurs in lms/templates/course_modes/track_selection.html
|
||||
# and related theme and translation files.
|
||||
|
||||
# Check for string unique to unfbe.html.
|
||||
self.assertContains(response, "Some graded content may be lost")
|
||||
# This string only occurs in lms/templates/course_modes/unfbe.html
|
||||
# and related theme and translation files.
|
||||
|
||||
# Check min_price was correctly passed in.
|
||||
self.assertContains(response, min_price)
|
||||
|
||||
def _assert_legacy_page(self, response, **_):
|
||||
"""
|
||||
Assert choose.html was rendered.
|
||||
"""
|
||||
# Check for string unique to the legacy choose.html.
|
||||
self.assertContains(response, "Choose Your Track")
|
||||
# This string only occurs in lms/templates/course_modes/choose.html
|
||||
# and related theme and translation files.
|
||||
|
||||
@ddt.data(
|
||||
# gated_content_on, course_duration_limits_on, waffle_flag_on, expected_page_assertion_function
|
||||
(True, True, True, _assert_fbe_page),
|
||||
(True, False, True, _assert_unfbe_page),
|
||||
(False, True, True, _assert_unfbe_page),
|
||||
(False, False, True, _assert_unfbe_page),
|
||||
(True, True, False, _assert_legacy_page),
|
||||
(True, False, False, _assert_legacy_page),
|
||||
(False, True, False, _assert_legacy_page),
|
||||
(False, False, False, _assert_legacy_page),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_track_selection_types(
|
||||
self,
|
||||
gated_content_on,
|
||||
course_duration_limits_on,
|
||||
waffle_flag_on,
|
||||
expected_page_assertion_function
|
||||
):
|
||||
"""
|
||||
Feature-based enrollment (FBE) is when gated content and course duration
|
||||
limits are enabled when a user is auditing a course.
|
||||
|
||||
When prompted to perform track selection (choosing between the audit and
|
||||
verified course modes), the learner may view 3 different pages:
|
||||
1. fbe.html - full FBE
|
||||
2. unfbe.html - partial or no FBE
|
||||
3. choose.html - legacy track selection page
|
||||
|
||||
This test checks that the right template is rendered.
|
||||
|
||||
"""
|
||||
# The active course mode already exists. Create verified course mode:
|
||||
verified_mode = CourseModeFactory.create(
|
||||
mode_slug='verified',
|
||||
course_id=self.course_that_started.id,
|
||||
min_price=149,
|
||||
)
|
||||
|
||||
# Enroll the test user in the audit mode:
|
||||
CourseEnrollmentFactory(
|
||||
is_active=True,
|
||||
course_id=self.course_that_started.id,
|
||||
user=self.user
|
||||
)
|
||||
|
||||
# Value Prop TODO (REV-2378): remove waffle flag from tests once the new Track Selection template is rolled out.
|
||||
# Check whether new track selection template is rendered.
|
||||
# This should *only* be shown when the waffle flag is on.
|
||||
with override_waffle_flag(VALUE_PROP_TRACK_SELECTION_FLAG, active=True):
|
||||
with mock.patch(
|
||||
'openedx.features.content_type_gating.models.ContentTypeGatingConfig.enabled_for_enrollment',
|
||||
return_value=True
|
||||
):
|
||||
with mock.patch(
|
||||
'openedx.features.course_duration_limits.models.CourseDurationLimitConfig.enabled_for_enrollment',
|
||||
return_value=True
|
||||
):
|
||||
with override_waffle_flag(VALUE_PROP_TRACK_SELECTION_FLAG, active=waffle_flag_on):
|
||||
with patch(GATING_METHOD_NAME, return_value=gated_content_on):
|
||||
with patch(CDL_METHOD_NAME, return_value=course_duration_limits_on):
|
||||
url = reverse('course_modes_choose', args=[str(self.course_that_started.id)])
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertContains(response, "Choose a path for your course in")
|
||||
|
||||
# Check if it displays the upgrade price for verified track and "Free" for audit track
|
||||
self.assertContains(response, verified_mode.min_price)
|
||||
self.assertContains(response, "Free")
|
||||
|
||||
# Check for specific HTML elements
|
||||
self.assertContains(response, '<span class="award-icon">')
|
||||
self.assertContains(response, '<span class="popover-icon">')
|
||||
self.assertContains(response, '<span class="note-icon">')
|
||||
|
||||
# Check for upgrade button ID
|
||||
self.assertContains(response, 'track_selection_upgrade')
|
||||
# Check for audit button ID
|
||||
self.assertContains(response, 'track_selection_audit')
|
||||
|
||||
# Check for happy path messaging - verified
|
||||
self.assertContains(response, '<li class="collapsible-item">')
|
||||
self.assertContains(response, 'access to all course activities')
|
||||
self.assertContains(response, 'Full access')
|
||||
# Check for happy path messaging - audit
|
||||
self.assertContains(response, "discussion forums and non-graded assignments")
|
||||
self.assertContains(response, "Get temporary access")
|
||||
self.assertContains(response, "Access expires and all progress will be lost")
|
||||
expected_page_assertion_function(self, response, min_price=verified_mode.min_price)
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
|
||||
@@ -139,14 +139,11 @@ class ChooseModeView(View):
|
||||
# If there isn't a verified mode available, then there's nothing
|
||||
# to do on this page. Send the user to the dashboard.
|
||||
if not CourseMode.has_verified_mode(modes):
|
||||
return redirect(reverse('dashboard'))
|
||||
return self._redirect_to_course_or_dashboard(course, course_key, request.user)
|
||||
|
||||
# If a user has already paid, redirect them to the dashboard.
|
||||
if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]):
|
||||
# If the course has started redirect to course home instead
|
||||
if course.has_started():
|
||||
return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key}))
|
||||
return redirect(reverse('dashboard'))
|
||||
return self._redirect_to_course_or_dashboard(course, course_key, request.user)
|
||||
|
||||
donation_for_course = request.session.get("donation_for_course", {})
|
||||
chosen_price = donation_for_course.get(str(course_key), None)
|
||||
@@ -258,15 +255,18 @@ class ChooseModeView(View):
|
||||
context['audit_access_deadline'] = formatted_audit_access_date
|
||||
fbe_is_on = deadline and gated_content
|
||||
|
||||
# Route to correct Track Selection page.
|
||||
# REV-2133 TODO Value Prop: remove waffle flag after testing is completed
|
||||
# and happy path version is ready to be rolled out to all users.
|
||||
if VALUE_PROP_TRACK_SELECTION_FLAG.is_enabled():
|
||||
# First iteration of happy path does not handle errors. If there are enrollment errors for a learner that is
|
||||
# technically considered happy path, old Track Selection page will be displayed.
|
||||
if not error:
|
||||
# Happy path conditions.
|
||||
if verified_mode and fbe_is_on and not enterprise_customer:
|
||||
return render_to_response("course_modes/track_selection.html", context)
|
||||
if not error: # TODO: Remove by executing REV-2355
|
||||
if not enterprise_customer: # TODO: Remove by executing REV-2342
|
||||
if fbe_is_on:
|
||||
return render_to_response("course_modes/fbe.html", context)
|
||||
else:
|
||||
return render_to_response("course_modes/unfbe.html", context)
|
||||
|
||||
# If error or enterprise_customer, failover to old choose.html page
|
||||
return render_to_response("course_modes/choose.html", context)
|
||||
|
||||
@method_decorator(transaction.non_atomic_requests)
|
||||
@@ -309,17 +309,11 @@ class ChooseModeView(View):
|
||||
# system, such as third-party discovery. These workflows result in learners arriving
|
||||
# directly at this screen, and they will not necessarily be pre-enrolled in the audit mode.
|
||||
CourseEnrollment.enroll(request.user, course_key, CourseMode.AUDIT)
|
||||
# If the course has started redirect to course home instead
|
||||
if course.has_started():
|
||||
return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key}))
|
||||
return redirect(reverse('dashboard'))
|
||||
return self._redirect_to_course_or_dashboard(course, course_key, user)
|
||||
|
||||
if requested_mode == 'honor':
|
||||
CourseEnrollment.enroll(user, course_key, mode=requested_mode)
|
||||
# If the course has started redirect to course home instead
|
||||
if course.has_started():
|
||||
return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key}))
|
||||
return redirect(reverse('dashboard'))
|
||||
return self._redirect_to_course_or_dashboard(course, course_key, user)
|
||||
|
||||
mode_info = allowed_modes[requested_mode]
|
||||
|
||||
@@ -366,6 +360,24 @@ class ChooseModeView(View):
|
||||
else:
|
||||
return None
|
||||
|
||||
def _redirect_to_course_or_dashboard(self, course, course_key, user):
|
||||
"""Perform a redirect to the course if the user is able to access the course.
|
||||
|
||||
If the user is not able to access the course, redirect the user to the dashboard.
|
||||
|
||||
Args:
|
||||
course: modulestore object for course
|
||||
course_key: course_id converted to a course_key
|
||||
user: request.user, the current user for the request
|
||||
|
||||
Returns:
|
||||
302 to the course if possible or the dashboard if not.
|
||||
"""
|
||||
if course.has_started() or user.is_staff:
|
||||
return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key}))
|
||||
else:
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
|
||||
def create_mode(request, course_id):
|
||||
"""Add a mode to the course corresponding to the given course ID.
|
||||
|
||||
68
lms/templates/course_modes/fbe.html
Normal file
68
lms/templates/course_modes/fbe.html
Normal file
@@ -0,0 +1,68 @@
|
||||
## This template is for track selection with Feature Based Enrollment (FBE).
|
||||
## This means a learner in the default audit track will have a limited time to
|
||||
## complete the course (course access duration) and may have some of the content
|
||||
## of the course unavailable (gated content).
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="track_selection.html" />
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
<%block name="track_selection_certificate_bullets">
|
||||
<div class="choice-bullets track-selection-type-fbe">
|
||||
<ul>
|
||||
<li>${Text(_("Showcase a {link_start}verified certificate{link_end} of completion on your resumé to advance your career")).format(
|
||||
link_start=HTML('<b><u><a class="verified" href="{track_verified_url}" target="_blank">').format(track_verified_url=track_links['verified_certificate']),
|
||||
link_end=HTML('</a></u></b>')
|
||||
)}
|
||||
</li>
|
||||
<li>${Text(_("Get {start_bold}access to all course activities{end_bold}, including both graded and non-graded assignments, while the course is running")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
<button class="btn btn-link collapsible">${_("Show more")}</button>
|
||||
<li class="collapsible-item">
|
||||
${Text(_("{start_bold}Full access{end_bold} to course content and materials, even after the course ends")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}
|
||||
<span class="popover-icon">
|
||||
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0212 6C12.0212 9.31444 9.33472 12 6.02124 12C2.70776 12 0.0212402 9.31444 0.0212402 6C0.0212402 2.68749 2.70776 0 6.02124 0C9.33472 0 12.0212 2.68749 12.0212 6ZM6.02124 7.20968C5.4066 7.20968 4.90834 7.70794 4.90834 8.32258C4.90834 8.93722 5.4066 9.43548 6.02124 9.43548C6.63588 9.43548 7.13414 8.93722 7.13414 8.32258C7.13414 7.70794 6.63588 7.20968 6.02124 7.20968ZM4.96464 3.20937L5.1441 6.49969C5.1525 6.65366 5.2798 6.77419 5.43399 6.77419H6.60849C6.76268 6.77419 6.88998 6.65366 6.89838 6.49969L7.07785 3.20937C7.08692 3.04306 6.95451 2.90323 6.78796 2.90323H5.2545C5.08795 2.90323 4.95556 3.04306 4.96464 3.20937Z" fill="#00262B"/>
|
||||
</svg>
|
||||
<span class="popover">
|
||||
${Text(_("{link_start}Learn more{link_end} about course access")).format(
|
||||
link_start=HTML('<u><a href="{track_comparison_url}" target="_blank">').format(track_comparison_url=track_links['learn_more']),
|
||||
link_end=HTML('</a></u>')
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="collapsible-item">${Text(_("Support our {start_bold}mission{end_bold} to increase access to high-quality education for everyone, everywhere")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<%block name="track_selection_audit_bullets">
|
||||
<div class="choice-bullets track-selection-type-fbe">
|
||||
<ul>
|
||||
<li>${Text(_("Get temporary access to {start_bold}non-graded{end_bold} activities, including discussion forums and non-graded assignments")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
<li>${Text(_("Get {start_bold}temporary access{end_bold} to the course material, including videos and readings")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
% if audit_access_deadline:
|
||||
<li>${_("Access expires and all progress will be lost on")} ${audit_access_deadline}</li>
|
||||
% else:
|
||||
<li>${_("Access expires and all progress will be lost")}</li>
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -95,43 +95,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
</span>
|
||||
<p class="price-display">${currency_symbol}${min_price} ${currency}</p>
|
||||
<div class="choice-title"><h4>${_("Earn a certificate")}</h4></div>
|
||||
<div class="choice-bullets">
|
||||
<ul>
|
||||
<li>
|
||||
${Text(_("Showcase a {link_start}verified certificate{link_end} of completion on your resumé to advance your career")).format(
|
||||
link_start=HTML('<b><u><a class="verified" href="{track_verified_url}" target="_blank">').format(track_verified_url=track_links['verified_certificate']),
|
||||
link_end=HTML('</a></u></b>')
|
||||
)}
|
||||
</li>
|
||||
<li>${Text(_("Get {start_bold}access to all course activities{end_bold}, including both graded and non-graded assignments, while the course is running")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
<button class="btn btn-link collapsible">${_("Show more")}</button>
|
||||
<li class="collapsible-item">
|
||||
${Text(_("{start_bold}Full access{end_bold} to course content and materials, even after the course ends")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}
|
||||
<span class="popover-icon">
|
||||
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0212 6C12.0212 9.31444 9.33472 12 6.02124 12C2.70776 12 0.0212402 9.31444 0.0212402 6C0.0212402 2.68749 2.70776 0 6.02124 0C9.33472 0 12.0212 2.68749 12.0212 6ZM6.02124 7.20968C5.4066 7.20968 4.90834 7.70794 4.90834 8.32258C4.90834 8.93722 5.4066 9.43548 6.02124 9.43548C6.63588 9.43548 7.13414 8.93722 7.13414 8.32258C7.13414 7.70794 6.63588 7.20968 6.02124 7.20968ZM4.96464 3.20937L5.1441 6.49969C5.1525 6.65366 5.2798 6.77419 5.43399 6.77419H6.60849C6.76268 6.77419 6.88998 6.65366 6.89838 6.49969L7.07785 3.20937C7.08692 3.04306 6.95451 2.90323 6.78796 2.90323H5.2545C5.08795 2.90323 4.95556 3.04306 4.96464 3.20937Z" fill="#00262B"/>
|
||||
</svg>
|
||||
<span class="popover">
|
||||
${Text(_("{link_start}Learn more{link_end} about course access")).format(
|
||||
link_start=HTML('<u><a href="{track_comparison_url}" target="_blank">').format(track_comparison_url=track_links['learn_more']),
|
||||
link_end=HTML('</a></u>')
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="collapsible-item">${Text(_("Support our {start_bold}mission{end_bold} to increase access to high-quality education for everyone, everywhere")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<%block name="track_selection_certificate_bullets"/>
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select track-selection-button">
|
||||
<input type="hidden" name="contribution" value="${price_before_discount or min_price}" />
|
||||
@@ -167,23 +131,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
<div class="track-selection-choice track-selection-audit ml-md-3">
|
||||
<p class="float-right text-uppercase price-display">${_("Free")}</p>
|
||||
<div class="choice-title"><h4>${_("Access this course")}</h4></div>
|
||||
<div class="choice-bullets">
|
||||
<ul>
|
||||
<li>${Text(_("Get temporary access to {start_bold}non-graded{end_bold} activities, including discussion forums and non-graded assignments")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
<li>${Text(_("Get {start_bold}temporary access{end_bold} to the course material, including videos and readings")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
% if audit_access_deadline:
|
||||
<li>${_("Access expires and all progress will be lost on")} ${audit_access_deadline}</li>
|
||||
% else:
|
||||
<li>${_("Access expires and all progress will be lost")}</li>
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
<%block name="track_selection_audit_bullets"/>
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select track-selection-button">
|
||||
<button id="track_selection_audit" type="submit" name="audit_mode">
|
||||
|
||||
38
lms/templates/course_modes/unfbe.html
Normal file
38
lms/templates/course_modes/unfbe.html
Normal file
@@ -0,0 +1,38 @@
|
||||
## This template is for track selection with partial or no Feature Based
|
||||
## Enrollment (FBE). In other words, it is un-FBE. FBE is when the learner in
|
||||
## the default audit track has a limited time to complete the course (course
|
||||
## access duration) and has some of the content of the course unavailable (gated
|
||||
## content). This template covers un-FBE, which is when the learner is subject
|
||||
## to either: (1) gated content but not course access duration; (2) course
|
||||
## access duration but not gated content; or (3) neither gated content nor
|
||||
## course access duration.
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
<%inherit file="track_selection.html" />
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
<%block name="track_selection_certificate_bullets">
|
||||
<div class="choice-bullets track-selection-type-unfbe">
|
||||
<ul>
|
||||
<li>${Text(_("Showcase a {link_start}verified certificate{link_end} of completion on your resumé to advance your career")).format(
|
||||
link_start=HTML('<b><u><a class="verified" href="{track_verified_url}" target="_blank">').format(track_verified_url=track_links['verified_certificate']),
|
||||
link_end=HTML('</a></u></b>'),
|
||||
)}</li>
|
||||
<li>${Text(_("Support our {start_bold}mission{end_bold} to increase access to high-quality education for everyone, everywhere")).format(
|
||||
start_bold=HTML('<b>'),
|
||||
end_bold=HTML('</b>'),
|
||||
)}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<%block name="track_selection_audit_bullets">
|
||||
<div class="choice-bullets track-selection-type-unfbe">
|
||||
<ul>
|
||||
<li>${_("Get access to the course material, including videos and readings")}</li>
|
||||
<li>${_("Some graded content may be lost")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -193,7 +193,7 @@ from common.djangoapps.pipeline_mako import render_require_js_path_overrides
|
||||
<div class="marketing-hero"><%block name="marketing_hero"></%block></div>
|
||||
|
||||
<div class="content-wrapper main-container" id="content" dir="${static.dir_rtl()}">
|
||||
${self.body()}
|
||||
${next.body()}
|
||||
<%block name="bodyextra"/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user