Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lms/djangoapps/course_home_api/outline/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class OutlineTabSerializer(DatesBannerSerializer, VerifiedModeSerializer):
"""
Serializer for the Outline Tab
"""
accessible_sequences = serializers.ListField()
access_expiration = serializers.DictField()
cert_data = CertificateDataSerializer()
course_blocks = CourseBlockSerializer()
Expand Down
1 change: 1 addition & 0 deletions lms/djangoapps/course_home_api/outline/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements
user_has_passing_grade = user_grade.passed

data = {
"accessible_sequences": [str(seq) for seq in user_course_outline.accessible_sequences],
'access_expiration': access_expiration,
'cert_data': cert_data,
'course_blocks': course_blocks,
Expand Down
22 changes: 22 additions & 0 deletions lms/djangoapps/course_home_api/toggles.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@
)


# Waffle flag to enable audit learner preview of course structure visible to verified learners.
#
# .. toggle_name: course_home.audit_learner_verified_preview
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: This toggle controls whether the the audit learners can see a preview of the course structure
# that is visible to verified learners.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2025-11-07
# .. toggle_target_removal_date: None
COURSE_HOME_AUDIT_LEARNER_VERIFIED_PREVIEW = CourseWaffleFlag(
f'{WAFFLE_FLAG_NAMESPACE}.audit_learner_verified_preview', __name__
)


def course_home_mfe_progress_tab_is_active(course_key):
# Avoiding a circular dependency
from .models import DisableProgressPageStackedConfig
Expand All @@ -73,3 +88,10 @@ def send_course_progress_analytics_for_student_is_enabled(course_key):
Returns True if the course completion analytics feature is enabled for a given course.
"""
return COURSE_HOME_SEND_COURSE_PROGRESS_ANALYTICS_FOR_STUDENT.is_enabled(course_key)


def audit_learner_verified_preview_is_enabled(course_key):
"""
Returns True if the course completion analytics feature is enabled for a given course.
"""
return COURSE_HOME_AUDIT_LEARNER_VERIFIED_PREVIEW.is_enabled(course_key)
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# lint-amnesty, pylint: disable=missing-module-docstring
import logging
from datetime import datetime
from django.conf import settings
from typing import Dict

from opaque_keys.edx.keys import CourseKey
from openedx.core import types

from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.course_home_api.toggles import audit_learner_verified_preview_is_enabled

from xmodule.partitions.enrollment_track_partition_generator import ( # lint-amnesty, pylint: disable=wrong-import-order
create_enrollment_track_partition_with_course_id
)
Expand Down Expand Up @@ -47,23 +51,34 @@ def load_data(self, full_course_outline) -> None:
# TODO: fix type annotation: https://github.com/openedx/tcril-engineering/issues/313
self.user_group = self.enrollment_track_groups.get(ENROLLMENT_TRACK_PARTITION_ID) # type: ignore

def _is_user_excluded_by_partition_group(self, user_partition_groups):
def _get_user_partition_group_access(self, user_partition_groups):
"""
Is the user part of the group to which the block is restricting content?
Get the user's partition group access for the enrollment track partition.
"""
is_accessible, is_removed = False, False

if not user_partition_groups:
return False
return is_accessible, is_removed

groups = user_partition_groups.get(ENROLLMENT_TRACK_PARTITION_ID)
if not groups:
return False
return is_accessible, is_removed

if self.user_group and self.user_group.id not in groups:
# If the user's partition group, say Masters,
# does not belong to the partition of the block, say [verified],
# the block should be removed
return True
return False
is_removed = True

if audit_learner_verified_preview_is_enabled(self.course_key):
contains_verified_mode = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.VERIFIED).get('id') in groups
is_audit_mode = self.user_group and self.user_group.id == settings.COURSE_ENROLLMENT_MODES.get(CourseMode.AUDIT).get('id')

if is_audit_mode and contains_verified_mode:
is_accessible = True
is_removed = False

return is_accessible, is_removed

def usage_keys_to_remove(self, full_course_outline):
"""
Expand All @@ -77,14 +92,29 @@ def usage_keys_to_remove(self, full_course_outline):
removed_usage_keys = set()
for section in full_course_outline.sections:
remove_all_children = False
if self._is_user_excluded_by_partition_group(
section.user_partition_groups
):
_, should_remove_section = self._get_user_partition_group_access(section.user_partition_groups)
if should_remove_section:
removed_usage_keys.add(section.usage_key)
remove_all_children = True
for seq in section.sequences:
if remove_all_children or self._is_user_excluded_by_partition_group(
seq.user_partition_groups
):
_, should_remove_sequence = self._get_user_partition_group_access(seq.user_partition_groups)
if remove_all_children or should_remove_sequence:
removed_usage_keys.add(seq.usage_key)
return removed_usage_keys

def inaccessible_sequences(self, full_course_outline):
"""
TODO
"""
inaccessible_usage_keys = set()
for section in full_course_outline.sections:
remove_all_children = False
is_section_inaccessible, _ = self._get_user_partition_group_access(section.user_partition_groups)
if is_section_inaccessible:
inaccessible_usage_keys.add(section.usage_key)
remove_all_children = True
for seq in section.sequences:
_, is_sequence_inacessible = self._get_user_partition_group_access(seq.user_partition_groups)
if remove_all_children or is_sequence_inacessible:
inaccessible_usage_keys.add(seq.usage_key)
return inaccessible_usage_keys
Loading