feat: add optional API for hiding the dates tab (#37923)

This commit is contained in:
Muhammad Anas
2026-02-26 05:27:49 +05:00
committed by GitHub
parent d9293afde4
commit b968eed88b
7 changed files with 112 additions and 3 deletions

View File

@@ -256,6 +256,15 @@ IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT = True
# .. toggle_tickets: 'https://openedx.atlassian.net/browse/VAN-622'
ENABLE_COPPA_COMPLIANCE = False
# .. toggle_name: ENABLE_DATES_COURSE_APP
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Controls whether the Dates course app is surfaced via the course apps API/UI.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2026-02-02
# .. toggle_tickets: https://github.com/openedx/platform-roadmap/issues/392
ENABLE_DATES_COURSE_APP = False
ENABLE_JASMINE = False
MARKETING_EMAILS_OPT_IN = False

View File

@@ -19,6 +19,7 @@ from lms.djangoapps.courseware.context_processor import user_timezone_locale_pre
from lms.djangoapps.courseware.courses import get_course_date_blocks
from lms.djangoapps.courseware.date_summary import TodaysDate
from lms.djangoapps.courseware.masquerade import setup_masquerade
from lms.djangoapps.courseware.tabs import DatesTab
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
@@ -110,13 +111,19 @@ class DatesTabView(RetrieveAPIView):
course_key=course_key,
)
course_date_blocks = (
[block for block in blocks if not isinstance(block, TodaysDate)]
if DatesTab.is_enabled(course, request.user)
else []
)
# User locale settings
user_timezone_locale = user_timezone_locale_prefs(request)
user_timezone = user_timezone_locale['user_timezone']
data = {
'has_ended': course.has_ended(),
'course_date_blocks': [block for block in blocks if not isinstance(block, TodaysDate)],
'course_date_blocks': course_date_blocks,
'learner_is_full_access': learner_is_full_access,
'user_timezone': user_timezone,
}

View File

@@ -42,6 +42,7 @@ from lms.djangoapps.courseware.context_processor import user_timezone_locale_pre
from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_info_section
from lms.djangoapps.courseware.date_summary import TodaysDate
from lms.djangoapps.courseware.masquerade import is_masquerading, setup_masquerade
from lms.djangoapps.courseware.tabs import DatesTab
from lms.djangoapps.courseware.toggles import courseware_disable_navigation_sidebar_blocks_caching
from lms.djangoapps.courseware.views.views import get_cert_data
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
@@ -249,7 +250,12 @@ class OutlineTabView(RetrieveAPIView):
if show_enrolled:
course_blocks = get_course_outline_block_tree(request, course_key_string, request.user)
date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1)
dates_widget['course_date_blocks'] = [block for block in date_blocks if not isinstance(block, TodaysDate)]
course_date_blocks = (
[block for block in date_blocks if not isinstance(block, TodaysDate)]
if DatesTab.is_enabled(course, request.user)
else []
)
dates_widget['course_date_blocks'] = course_date_blocks
handouts_html = get_course_info_section(request, request.user, course, 'handouts')
welcome_message_html = get_current_update_for_user(request, course)

View File

@@ -8,8 +8,10 @@ from django.utils.translation import gettext_noop as _
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore
from xmodule.tabs import CourseTabList
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview, CourseTab
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.course_apps.plugins import CourseApp
from openedx.core.lib.courses import get_course_by_id
@@ -65,6 +67,58 @@ class ProgressCourseApp(CourseApp):
}
class DatesCourseApp(CourseApp):
"""Course app stub for course dates."""
app_id = "dates"
name = _("Dates")
description = _("Provide learners a summary of important course dates.")
documentation_links = {
"learn_more_configuration": getattr(settings, "DATES_HELP_URL", ""),
}
@classmethod
def is_available(cls, course_key: CourseKey) -> bool: # pylint: disable=unused-argument
"""
Dates app is available when explicitly enabled via settings.
"""
return settings.ENABLE_DATES_COURSE_APP
@classmethod
def is_enabled(cls, course_key: CourseKey) -> bool:
"""
The dates course status is stored in the course block.
"""
course = get_course_by_id(course_key)
dates_tab = CourseTabList.get_tab_by_id(course.tabs, 'dates')
return bool(dates_tab and not dates_tab.is_hidden)
@classmethod
def set_enabled(cls, course_key: CourseKey, enabled: bool, user: 'User') -> bool:
"""
The dates course enabled/disabled status is stored in the course block.
"""
course = get_course_by_id(course_key)
dates_tab = CourseTabList.get_tab_by_id(course.tabs, 'dates')
if enabled and dates_tab is None:
dates_tab = CourseTab.load("dates")
course.tabs.append(dates_tab)
if dates_tab is not None:
dates_tab.is_hidden = not enabled
modulestore().update_item(course, user.id)
return enabled
@classmethod
def get_allowed_operations(cls, course_key: CourseKey, user: Optional[User] = None) -> Dict[str, bool]: # pylint: disable=unused-argument
"""
Returns the allowed operations for the app.
"""
return {
"enable": True,
"configure": True,
}
class TextbooksCourseApp(CourseApp):
"""
Course app config for textbooks app.

View File

@@ -307,6 +307,7 @@ class DatesTab(EnrolledTab):
title = gettext_noop("Dates")
priority = 30
view_name = "dates"
is_hideable = True
def __init__(self, tab_dict):
def link_func(course, _reverse_func):
@@ -315,6 +316,13 @@ class DatesTab(EnrolledTab):
tab_dict['link_func'] = link_func
super().__init__(tab_dict)
@classmethod
def is_enabled(cls, course, user=None):
if not super().is_enabled(course, user=user):
return False
dates_tab = CourseTabList.get_tab_by_id(course.tabs, 'dates')
return bool(dates_tab and not dates_tab.is_hidden)
def get_course_tab_list(user, course):
"""

View File

@@ -870,6 +870,12 @@ class DatesTabTestCase(TabListTestCase):
"""Test cases for making sure no persisted dates tab is surfaced"""
user = self.create_mock_user()
self.course.tabs = self.all_valid_tab_list
# Ensure hidden state from other tests does not affect this test's intent.
dates_tab = xmodule_tabs.CourseTabList.get_tab_by_id(self.course.tabs, 'dates')
assert dates_tab is not None
dates_tab.is_hidden = False
self.course.save()
# Verify that there is a dates tab in the modulestore
@@ -886,3 +892,21 @@ class DatesTabTestCase(TabListTestCase):
if tab.type == 'dates':
num_dates_tabs += 1
assert num_dates_tabs == 1
@patch('common.djangoapps.student.models.course_enrollment.CourseEnrollment.is_enrolled')
def test_dates_tab_respects_hide_flag(self, is_enrolled):
"""Test that the dates tab respects the hide flag."""
is_enrolled.return_value = True
user = self.create_mock_user(is_staff=False, is_enrolled=True)
self.course.tabs = self.all_valid_tab_list
dates_tab = xmodule_tabs.CourseTabList.get_tab_by_id(self.course.tabs, 'dates')
assert dates_tab is not None
dates_tab.is_hidden = False
self.course.save()
tabs = get_course_tab_list(user, self.course)
assert any(tab.type == 'dates' for tab in tabs)
dates_tab.is_hidden = True
tabs = get_course_tab_list(user, self.course)
assert not any(tab.type == 'dates' for tab in tabs)

View File

@@ -86,6 +86,7 @@ setup(
"openedx.course_app": [
"calculator = lms.djangoapps.courseware.plugins:CalculatorCourseApp",
"custom_pages = lms.djangoapps.courseware.plugins:CustomPagesCourseApp",
"dates = lms.djangoapps.courseware.plugins:DatesCourseApp",
"discussion = openedx.core.djangoapps.discussions.plugins:DiscussionCourseApp",
"edxnotes = lms.djangoapps.edxnotes.plugins:EdxNotesCourseApp",
"live = openedx.core.djangoapps.course_live.plugins:LiveCourseApp",