feat!: Remove the course sock and related APIs.

DEPR: https://github.com/openedx/edx-platform/issues/36429

This change removes the course_sock and related API data.  The UI it
removes is on the Legacy Courseware pages which have also been replaced
and have their own [deprecation ticket](https://github.com/openedx/edx-platform/issues/35803)

Before this can be merged, we will need to update the
frontend-app-learning MFE to no longer consume the
`can_show_upgrade_sock` attribute.

BREAKING CHANGE: CourseHomeMetadata, ProgressTab, OutlineTab and
VerifiedMode APIs will no longer have a `can_show_upgrade_sock`
attribute.
This commit is contained in:
Feanil Patel
2025-03-24 10:35:13 -04:00
parent 94468ef4b3
commit 1829eb7d71
17 changed files with 7 additions and 614 deletions

View File

@@ -31,7 +31,6 @@ from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactor
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from openedx.features.course_experience import (
COURSE_ENABLE_UNENROLLED_ACCESS_FLAG,
DISPLAY_COURSE_SOCK_FLAG,
ENABLE_COURSE_GOALS
)
from openedx.features.discounts.applicability import (
@@ -362,12 +361,6 @@ class OutlineTabTestViews(BaseCourseHomeTests):
assert (data['access_expiration'] is not None) == show_enrolled
assert (data['resume_course']['url'] is not None) == show_enrolled
@ddt.data(True, False)
def test_can_show_upgrade_sock(self, sock_enabled):
with override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=sock_enabled):
response = self.client.get(self.url)
assert response.data['can_show_upgrade_sock'] == sock_enabled
def test_verified_mode(self):
enrollment = CourseEnrollment.enroll(self.user, self.course.id)
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))

View File

@@ -9,7 +9,6 @@ from rest_framework import serializers
from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
from openedx.core.djangoapps.courseware_api.utils import serialize_upgrade_info
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG
from openedx.features.course_experience.utils import dates_banner_should_display
@@ -59,13 +58,8 @@ class VerifiedModeSerializer(ReadOnlySerializer):
Requires 'course_overview', 'enrollment', and 'request' from self.context.
"""
can_show_upgrade_sock = serializers.SerializerMethodField()
verified_mode = serializers.SerializerMethodField()
def get_can_show_upgrade_sock(self, _):
course_overview = self.context['course_overview']
return DISPLAY_COURSE_SOCK_FLAG.is_enabled(course_overview.id)
def get_verified_mode(self, _):
"""Return verified mode information, or None."""
course_overview = self.context['course_overview']

View File

@@ -341,7 +341,7 @@ class IndexQueryTestCase(ModuleStoreTestCase):
self.client.login(username=self.user.username, password=self.user_password)
CourseEnrollment.enroll(self.user, course.id)
with self.assertNumQueries(154, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST):
with self.assertNumQueries(152, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST):
with check_mongo_calls(3):
url = reverse(
'courseware_section',

View File

@@ -48,7 +48,6 @@ from openedx.features.course_experience import (
default_course_url
)
from openedx.features.course_experience.url_helpers import make_learning_mfe_courseware_url
from openedx.features.course_experience.views.course_sock import CourseSockFragmentView
from openedx.features.enterprise_support.api import data_sharing_consent_required
from ..access import has_access
@@ -454,9 +453,6 @@ class CoursewareIndex(View):
table_of_contents['chapters'],
)
courseware_context['course_sock_fragment'] = CourseSockFragmentView().render_to_fragment(
request, course=self.course)
# entrance exam data
self._add_entrance_exam_to_context(courseware_context)

View File

@@ -66,6 +66,3 @@
// responsive
@import 'base/layouts'; // temporary spot for responsive course
@import 'header';
// features
@import 'features/course-sock';

View File

@@ -28,7 +28,6 @@ $static-path: '../..';
@import 'features/bookmarks';
@import 'features/course-experience';
@import 'features/course-search';
@import 'features/course-sock';
@import 'features/course-upgrade-message';
@import 'features/course-duration-limits';

View File

@@ -1,170 +0,0 @@
.verification-sock {
display: inline-block;
position: relative;
width: 100%;
max-width: map-get($container-max-widths, xl);
margin: $baseline auto 0;
-webkit-transition: all 0.4s ease-out;
-moz-transition: all 0.4s ease-out;
-o-transition: all 0.4s ease-out;
-ms-transition: all 0.4s ease-out;
transition: all 0.4s ease-out;
.action-toggle-verification-sock {
@include left(50%);
@include margin-left(-1 * $baseline * 15/2);
position: absolute;
top: (-1 * $baseline);
width: ($baseline * 15);
color: theme-color("inverse");
background-color: theme-color("success");
border-color: theme-color("success");
background-image: none;
box-shadow: none;
-webkit-transition: background-color 0.5s;
transition: background-color 0.5s;
cursor: pointer;
&.active,
&:focus,
&:hover {
color: theme-color("success");
background-color: theme-color("inverse");
border-color: theme-color("success");
background-image: none;
box-shadow: none;
}
}
.verification-main-panel {
display: none;
overflow: hidden;
border-top: 1px solid $border-color;
padding: ($baseline * 5/2) ($baseline * 2);
-webkit-transition: height ease-out;
transition: height ease-out;
.verification-desc-panel {
color: $black-t3;
position: relative;
h2 {
font-size: 1.5rem;
font-weight: $font-weight-bold;
}
h3 {
font-size: 1.25rem;
}
@media (max-width: 960px) {
.mini-cert {
display: none;
border: 1px solid $black-t0;
}
}
.mini-cert {
@include right($baseline);
position: absolute;
top: $baseline;
width: ($baseline * 13);
}
.learner-story-container {
display: flex;
max-width: 630px;
.student-image {
margin: ($baseline / 4) $baseline 0 0;
height: ($baseline * 5/2);
width: ($baseline * 5/2);
}
.story-quote > .author {
display: block;
margin-top: ($baseline / 4);
font-weight: 600;
}
&:not(:first-child) {
margin-top: ($baseline * 2);
}
}
.action-upgrade-certificate {
position: absolute;
right: $baseline;
background-color: theme-color("success");
border-color: theme-color("success");
color: theme-color("inverse");
background-image: none;
box-shadow: none;
cursor: pointer;
&:hover {
background-color: theme-color("inverse");
color: theme-color("success");
}
@media (max-width: 960px) {
& {
position: relative;
margin-top: ($baseline * 2);
}
}
@media (min-width: 960px) {
&.stuck-top {
bottom: auto;
top: $baseline * (52 / 5);
}
&.stuck-bottom {
top: auto;
bottom: $baseline * (-1 * 3/2);
}
&.attached {
@include right($baseline);
position: fixed;
bottom: $baseline;
top: auto;
}
}
}
}
}
}
// Overrides for the courseware page.
.view-courseware {
.verification-sock {
margin-top: 0;
border-top: none;
border-bottom: none;
.action-toggle-verification-sock {
top: (-1 * $baseline * 5/4);
&:not(.active) {
color: theme-color("inverse");
background-color: theme-color("success");
box-shadow: none;
border: 1px solid theme-color("success");
&:hover {
background-color: $success-color-hover;
}
}
}
.verification-main-panel {
border-top: 0;
border-bottom: 1px solid $border-color;
}
}
}

View File

@@ -290,7 +290,6 @@ ${HTML(fragment.foot_html())}
</section>
</div>
${HTML(course_sock_fragment.body_html())}
</div>
<div class="container-footer">
% if settings.FEATURES.get("LICENSING", False):
@@ -317,36 +316,3 @@ ${HTML(fragment.foot_html())}
% endif
</nav>
% endif
<%static:require_module_async module_name="js/commerce/track_ecommerce_events" class_name="TrackECommerceEvents">
var fbeLink = $("#FBE_banner");
var welcomeLink = $("#welcome");
var accessDeniedUpsellLink = $("#accessDeniedUpsell");
var sockLink = $("#sock");
TrackECommerceEvents.trackUpsellClick(fbeLink, 'in_course_audit_access_expires', {
pageName: "in_course",
linkType: "link",
linkCategory: "FBE_banner"
});
TrackECommerceEvents.trackUpsellClick(welcomeLink, 'in_course_welcome', {
pageName: "in_course",
linkType: "link",
linkCategory: "welcome"
});
TrackECommerceEvents.trackUpsellClick(accessDeniedUpsellLink, 'in_course_upgrade', {
pageName: "in_course",
linkType: "link",
linkCategory: "(none)"
});
TrackECommerceEvents.trackUpsellClick(sockLink, 'in_course_sock', {
pageName: "in_course",
linkType: "button",
linkCategory: "green_upgrade"
});
</%static:require_module_async>

View File

@@ -80,7 +80,6 @@ class CourseInfoSerializer(serializers.Serializer): # pylint: disable=abstract-
"""
access_expiration = serializers.DictField()
can_show_upgrade_sock = serializers.BooleanField()
content_type_gating_enabled = serializers.BooleanField()
course_goals = CourseGoalsSerializer()
effort = serializers.CharField()

View File

@@ -55,7 +55,6 @@ from openedx.core.djangoapps.programs.utils import ProgramProgressMeter
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
from openedx.core.lib.courses import get_course_by_id
from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG
from openedx.features.course_experience import ENABLE_COURSE_GOALS
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.access import get_access_expiration_data
@@ -127,10 +126,6 @@ class CoursewareMeta:
course_key=self.course_key,
)
@property
def can_show_upgrade_sock(self):
return DISPLAY_COURSE_SOCK_FLAG.is_enabled(self.course_key)
@property
def license(self):
return self.course.license

View File

@@ -18,9 +18,6 @@ DISABLE_COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: di
f'{WAFFLE_FLAG_NAMESPACE}.disable_course_outline_page', __name__
)
# Waffle flag to enable the sock on the footer of the home and courseware pages.
DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.display_course_sock', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation
# Waffle flag to let learners access a course before its start date.
COURSE_PRE_START_ACCESS_FLAG = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.pre_start_access', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation

View File

@@ -1,90 +0,0 @@
/* globals Logger */
export class CourseSock { // eslint-disable-line import/prefer-default-export
constructor() {
// eslint-disable-next-line no-undef
const $toggleActionButton = $('.action-toggle-verification-sock');
// eslint-disable-next-line no-undef
const $verificationSock = $('.verification-sock .verification-main-panel');
// eslint-disable-next-line no-undef
const $upgradeToVerifiedButton = $('.verification-sock .action-upgrade-certificate');
// eslint-disable-next-line no-undef
const $miniCert = $('.mini-cert');
const pageLocation = window.location.href.indexOf('courseware') > -1
? 'Course Content Page' : 'Home Page';
// Behavior to fix button to bottom of screen on scroll
const fixUpgradeButton = () => {
if (!$upgradeToVerifiedButton.is(':visible')) { return; }
// Grab the current scroll location
// eslint-disable-next-line no-undef
const documentBottom = $(window).scrollTop() + $(window).height();
// Establish a sliding window in which the button is fixed
const startFixed = $verificationSock.offset().top + 320;
const endFixed = (startFixed + $verificationSock.height()) - 220;
// Ensure update button stays in sock even when max-width is exceeded
const distRight = window.outerWidth - ($miniCert.offset().left + $miniCert.width());
// Update positioning when scrolling is in fixed window and screen width is sufficient
if ((documentBottom > startFixed && documentBottom < endFixed
// eslint-disable-next-line no-undef
&& $(window).width() > 960)) {
$upgradeToVerifiedButton.addClass('attached');
$upgradeToVerifiedButton.css('right', `${distRight}px`);
} else {
// If outside sliding window, reset to un-attached state
$upgradeToVerifiedButton.removeClass('attached');
$upgradeToVerifiedButton.css('right', '20px');
// Add class to define absolute location
if (documentBottom < startFixed) {
$upgradeToVerifiedButton.addClass('stuck-top');
$upgradeToVerifiedButton.removeClass('stuck-bottom');
} else if (documentBottom > endFixed) {
$upgradeToVerifiedButton.addClass('stuck-bottom');
$upgradeToVerifiedButton.removeClass('stuck-top');
}
}
};
// Fix the sock to the screen on scroll and resize events
if ($upgradeToVerifiedButton.length) {
// eslint-disable-next-line no-undef
$(window).scroll(fixUpgradeButton).resize(fixUpgradeButton);
}
// Open the sock when user clicks to Learn More
$toggleActionButton.on('click', () => {
const toggleSpeed = 400;
$toggleActionButton.toggleClass('active');
$verificationSock.slideToggle(toggleSpeed, fixUpgradeButton);
// Toggle aria-expanded attribute
const newAriaExpandedState = $toggleActionButton.attr('aria-expanded') === 'false';
$toggleActionButton.attr('aria-expanded', newAriaExpandedState);
// Log open and close events
const isOpening = $toggleActionButton.hasClass('active');
const logMessage = isOpening ? 'edx.bi.course.sock.toggle_opened'
: 'edx.bi.course.sock.toggle_closed';
window.analytics.track(
logMessage,
{
from_page: pageLocation,
},
);
});
$upgradeToVerifiedButton.on('click', () => {
Logger.log(
'edx.course.enrollment.upgrade.clicked',
{
location: 'sock',
},
);
});
}
}

View File

@@ -1,76 +0,0 @@
## mako
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/>
<%!
from django.utils.translation import gettext as _
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG
%>
<%block name="content">
% if show_course_sock:
<div class="verification-sock"
% if not DISPLAY_COURSE_SOCK_FLAG.is_enabled(course_id):
style="display: none"
%endif
>
<button type="button" aria-expanded="false" class="btn btn-primary focusable action-toggle-verification-sock">
${_('Learn About Verified Certificates')}
</button>
<div class="verification-main-panel">
<div class="verification-desc-panel content-main">
<h2>${_('{platform_name} Verified Certificate').format(platform_name=settings.PLATFORM_NAME)}</h2>
<h3>${_('Why upgrade?')}</h3>
<ul>
<li>${_('Official proof of completion')}</li>
<li>${_('Easily shareable certificate')}</li>
<li>${_('Proven motivator to complete the course')}</li>
<li>${_('Certificate purchases help {platform_name} continue to offer free courses').format(platform_name=settings.PLATFORM_NAME)}</li>
</ul>
<h3>${_('How it works')}</h3>
<ul>
<li>${_('Pay the Verified Certificate upgrade fee')}</li>
<li>${_('Verify your identity with a webcam and government-issued ID')}</li>
<li>${_('Study hard and pass the course')}</li>
<li>${_('Share your certificate with friends, employers, and others')}</li>
</ul>
% if settings.PLATFORM_NAME == 'edX':
<h3>${_('edX Learner Stories')}</h3>
<div class="learner-story-container">
<img class="student-image" alt="Student Image" src="${static.url('course_experience/images/learner-quote.png')}" />
<div class="story-quote">
${_('My certificate has helped me showcase my knowledge on my \
resumé - I feel like this certificate could really help me land \
my dream job!')}
<span class="author">- ${_('{learner_name}, edX Learner').format(learner_name='Christina Fong')}</span>
</div>
</div>
<div class="learner-story-container">
<img class="student-image" alt="Student Image" src="${static.url('course_experience/images/learner-quote2.png')}" />
<div class="story-quote">
${_('I wanted to include a verified certificate on my resumé and my profile to \
illustrate that I am working towards this goal I have and that I have \
achieved something while I was unemployed.')}<br/>
<span class="author">- ${_('{learner_name}, edX Learner').format(learner_name='Cheryl Troell')}</span>
</div>
</div>
% endif
<img class="mini-cert" alt="Example Certificate Image" src="${static.url('course_experience/images/verified-cert.png')}"/>
<a id="sock" href="${upgrade_url}">
<div class="btn btn-upgrade stuck-top focusable action-upgrade-certificate" data-creative="original_sock" data-position="sock">
${Text(_('Upgrade ({course_price})')).format(course_price=HTML(course_price))}
</div>
</a>
</div>
</div>
</div>
%endif
</%block>
<%static:webpack entry="CourseSock">
new CourseSock({
el:'.verification-sock'
});
</%static:webpack>

View File

@@ -1,117 +0,0 @@
"""
Tests for course verification sock
"""
from unittest import mock
import ddt
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.commerce.models import CommerceConfiguration
from lms.djangoapps.courseware.tests.helpers import set_preview_mode
from openedx.core.djangolib.markup import HTML
from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
from .helpers import add_course_mode
TEST_PASSWORD = 'Password1234'
TEST_VERIFICATION_SOCK_LOCATOR = '<div class="verification-sock"'
@ddt.ddt
@set_preview_mode(True)
class TestCourseSockView(SharedModuleStoreTestCase):
"""
Tests for the course verification sock fragment view.
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
# Create four courses
cls.standard_course = CourseFactory.create()
cls.verified_course = CourseFactory.create()
cls.verified_course_update_expired = CourseFactory.create()
cls.verified_course_already_enrolled = CourseFactory.create()
# Assign each verifiable course an upgrade deadline
add_course_mode(cls.verified_course, upgrade_deadline_expired=False)
add_course_mode(cls.verified_course_update_expired, upgrade_deadline_expired=True)
add_course_mode(cls.verified_course_already_enrolled, upgrade_deadline_expired=False)
def setUp(self):
super().setUp()
self.user = UserFactory.create(is_staff=True)
# Enroll the user in the four courses
CourseEnrollmentFactory.create(user=self.user, course_id=self.standard_course.id)
CourseEnrollmentFactory.create(user=self.user, course_id=self.verified_course.id)
CourseEnrollmentFactory.create(user=self.user, course_id=self.verified_course_update_expired.id)
CourseEnrollmentFactory.create(
user=self.user, course_id=self.verified_course_already_enrolled.id, mode=CourseMode.VERIFIED
)
CommerceConfiguration.objects.create(enabled=True, checkout_on_ecommerce_service=True)
# Log the user in
self.client.login(username=self.user.username, password=TEST_PASSWORD)
def get_courseware(self, course):
return self.client.get(reverse('courseware', kwargs={'course_id': str(course.id)}))
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
def test_standard_course(self):
"""
Ensure that a course that cannot be verified does
not have a visible verification sock.
"""
response = self.get_courseware(self.standard_course)
self.assert_verified_sock_is_not_visible(self.standard_course, response)
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
def test_verified_course(self):
"""
Ensure that a course that can be verified has a
visible verification sock.
"""
response = self.get_courseware(self.verified_course)
self.assert_verified_sock_is_visible(self.verified_course, response)
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
def test_verified_course_updated_expired(self):
"""
Ensure that a course that has an expired upgrade
date does not display the verification sock.
"""
response = self.get_courseware(self.verified_course_update_expired)
self.assert_verified_sock_is_not_visible(self.verified_course_update_expired, response)
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
def test_verified_course_user_already_upgraded(self):
"""
Ensure that a user that has already upgraded to a
verified status cannot see the verification sock.
"""
response = self.get_courseware(self.verified_course_already_enrolled)
self.assert_verified_sock_is_not_visible(self.verified_course_already_enrolled, response)
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
@mock.patch(
'openedx.features.course_experience.views.course_sock.format_strikeout_price',
mock.Mock(return_value=(HTML("<span>DISCOUNT_PRICE</span>"), True))
)
def test_upgrade_message_discount(self):
response = self.get_courseware(self.verified_course)
self.assertContains(response, "<span>DISCOUNT_PRICE</span>")
def assert_verified_sock_is_visible(self, course, response): # lint-amnesty, pylint: disable=unused-argument
return self.assertContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)
def assert_verified_sock_is_not_visible(self, course, response): # lint-amnesty, pylint: disable=unused-argument
return self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)

View File

@@ -2,11 +2,7 @@
Tests for masquerading functionality on course_experience
"""
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin, set_preview_mode
from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG
from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin
from common.djangoapps.student.roles import CourseStaffRole
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
@@ -15,7 +11,6 @@ from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID # lint-
from xmodule.partitions.partitions_service import PartitionService # lint-amnesty, pylint: disable=wrong-import-order
from .helpers import add_course_mode
from .test_course_sock import TEST_VERIFICATION_SOCK_LOCATOR
TEST_PASSWORD = 'Password1234'
@@ -59,40 +54,3 @@ class MasqueradeTestBase(SharedModuleStoreTestCase, MasqueradeMixin):
if group.name == mode_name:
return group.id
return None
@set_preview_mode(True)
class TestVerifiedUpgradesWithMasquerade(MasqueradeTestBase):
"""
Tests for the course verification upgrade messages while the user is being masqueraded.
"""
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
def test_masquerade_as_student(self):
# Elevate the staff user to be student
self.update_masquerade(course=self.verified_course, user_partition_id=ENROLLMENT_TRACK_PARTITION_ID)
response = self.client.get(reverse('courseware', kwargs={'course_id': str(self.verified_course.id)}))
self.assertContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
def test_masquerade_as_verified_student(self):
user_group_id = self.get_group_id_by_course_mode_name(
self.verified_course.id,
'Verified Certificate'
)
self.update_masquerade(course=self.verified_course, group_id=user_group_id,
user_partition_id=ENROLLMENT_TRACK_PARTITION_ID)
response = self.client.get(reverse('courseware', kwargs={'course_id': str(self.verified_course.id)}))
self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)
@override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
def test_masquerade_as_masters_student(self):
user_group_id = self.get_group_id_by_course_mode_name(
self.masters_course.id,
'Masters'
)
self.update_masquerade(course=self.masters_course, group_id=user_group_id,
user_partition_id=ENROLLMENT_TRACK_PARTITION_ID)
response = self.client.get(reverse('courseware', kwargs={'course_id': str(self.masters_course.id)}))
self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)

View File

@@ -1,47 +0,0 @@
"""
Fragment for rendering the course's sock and associated toggle button.
"""
from django.template.loader import render_to_string
from web_fragments.fragment import Fragment
from lms.djangoapps.courseware.utils import (
can_show_verified_upgrade,
verified_upgrade_deadline_link
)
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.features.discounts.utils import format_strikeout_price
from common.djangoapps.student.models import CourseEnrollment
class CourseSockFragmentView(EdxFragmentView):
"""
A fragment to provide extra functionality in a dropdown sock.
"""
def render_to_fragment(self, request, course, **kwargs): # lint-amnesty, pylint: disable=arguments-differ
"""
Render the course's sock fragment.
"""
context = self.get_verification_context(request, course)
html = render_to_string('course_experience/course-sock-fragment.html', context)
return Fragment(html)
@staticmethod
def get_verification_context(request, course): # lint-amnesty, pylint: disable=missing-function-docstring
enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
show_course_sock = can_show_verified_upgrade(request.user, enrollment, course)
if show_course_sock:
upgrade_url = verified_upgrade_deadline_link(request.user, course=course)
course_price, _ = format_strikeout_price(request.user, course)
else:
upgrade_url = ''
course_price = ''
context = {
'show_course_sock': show_course_sock,
'course_price': course_price,
'course_id': course.id,
'upgrade_url': upgrade_url,
}
return context

View File

@@ -130,7 +130,6 @@ module.exports = Merge.smart({
CompletionOnViewService: './lms/static/completion/js/CompletionOnViewService.js',
// Features
CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js',
Currency: './openedx/features/course_experience/static/course_experience/js/currency.js',
AnnouncementsView: './openedx/features/announcements/static/announcements/jsx/Announcements.jsx',
@@ -194,19 +193,19 @@ module.exports = Merge.smart({
multiple: [
{ search: defineHeader, replace: '' },
{ search: defineFooter, replace: '' },
{
{
search: /(\/\* RequireJS) \*\//g,
replace(match, p1, offset, string) {
return p1;
}
},
{
{
search: /\/\* Webpack/g,
replace(match, p1, offset, string) {
return match + ' */';
}
},
{
{
search: /text!(.*?\.underscore)/g,
replace(match, p1, offset, string) {
return p1;
@@ -657,13 +656,13 @@ module.exports = Merge.smart({
// We used to have node: { fs: 'empty' } in this file,
// that is no longer supported. Adding this based on the recommendation in
// https://stackoverflow.com/questions/64361940/webpack-error-configuration-node-has-an-unknown-property-fs
//
//
// With this uncommented tests fail
// Tests failed in the following suites:
// * lms javascript
// * xmodule-webpack javascript
// Error: define cannot be used indirect
//
//
// fallback: {
// fs: false
// }