From 9ea9f46ccbd9f0ec9f323d0018ef5024bd8b37b0 Mon Sep 17 00:00:00 2001 From: Muhammad Anas Date: Wed, 21 Jan 2026 07:36:32 +0000 Subject: [PATCH 1/2] feat: add card in Pages and Resources to allow hiding the dates tab --- lms/djangoapps/courseware/plugins.py | 45 +++++++++++++++++++ lms/djangoapps/courseware/tabs.py | 7 +++ lms/djangoapps/courseware/tests/test_tabs.py | 13 ++++++ lms/envs/common.py | 9 ++++ .../content/course_overviews/models.py | 7 +++ setup.py | 1 + xmodule/course_block.py | 7 +++ 7 files changed, 89 insertions(+) diff --git a/lms/djangoapps/courseware/plugins.py b/lms/djangoapps/courseware/plugins.py index f16423c7695b..fbe53c365c12 100644 --- a/lms/djangoapps/courseware/plugins.py +++ b/lms/djangoapps/courseware/plugins.py @@ -65,6 +65,51 @@ def get_allowed_operations(cls, course_key: CourseKey, user: Optional[User] = No } +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.FEATURES.get("ENABLE_DATES_COURSE_APP", False) + + @classmethod + def is_enabled(cls, course_key: CourseKey) -> bool: + """ + The dates course status is stored in the course block. + """ + return not CourseOverview.get_from_id(course_key).hide_dates_tab + + @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) + course.hide_dates_tab = 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. diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 2a67b6454e42..c993078b92ea 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -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,12 @@ def link_func(course, _reverse_func): 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 + return not getattr(course, 'hide_dates_tab', False) + def get_course_tab_list(user, course): """ diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py index 841b0ffe5362..56db188fe195 100644 --- a/lms/djangoapps/courseware/tests/test_tabs.py +++ b/lms/djangoapps/courseware/tests/test_tabs.py @@ -886,3 +886,16 @@ def test_singular_dates_tab(self): 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): + tab = DatesTab({'type': DatesTab.type, 'name': 'dates'}) + + is_enrolled.return_value = True + user = self.create_mock_user(is_staff=False, is_enrolled=True) + + self.course.hide_dates_tab = False + assert self.is_tab_enabled(tab, self.course, user) + + self.course.hide_dates_tab = True + assert not self.is_tab_enabled(tab, self.course, user) diff --git a/lms/envs/common.py b/lms/envs/common.py index 0419633f583e..219805ee27d6 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2783,6 +2783,15 @@ # .. setting_description: Content to replace spam posts with CONTENT_FOR_SPAM_POSTS = "" +# .. toggle_name: settings.FEATURES['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-01-21 +# .. toggle_tickets: None +FEATURES['ENABLE_DATES_COURSE_APP'] = False + # .. toggle_name: ENABLE_AUTHN_RESET_PASSWORD_HIBP_POLICY # .. toggle_implementation: DjangoSetting # .. toggle_default: False diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index c5b453f82e6c..7e3378af40b1 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -943,6 +943,13 @@ def hide_progress_tab(self): """ return self._original_course.hide_progress_tab + @property + def hide_dates_tab(self): + """ + TODO: move this to the model. + """ + return self._original_course.hide_dates_tab + @property def edxnotes(self): """ diff --git a/setup.py b/setup.py index eeb7b79f534d..8cc1acc666e7 100644 --- a/setup.py +++ b/setup.py @@ -86,6 +86,7 @@ "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", diff --git a/xmodule/course_block.py b/xmodule/course_block.py index 17dc4d877be7..0c22c49e39d7 100644 --- a/xmodule/course_block.py +++ b/xmodule/course_block.py @@ -727,6 +727,13 @@ class CourseFields: # lint-amnesty, pylint: disable=missing-class-docstring deprecated=True ) + hide_dates_tab = Boolean( + display_name=_("Hide Dates Tab"), + help=_("Allows hiding of the dates tab."), + scope=Scope.settings, + deprecated=True + ) + display_organization = String( display_name=_("Course Organization Display String"), help=_( From 49fe3d1af76b165ec448a98202bc31061f5c3836 Mon Sep 17 00:00:00 2001 From: Muhammad Anas Date: Thu, 22 Jan 2026 07:49:51 +0000 Subject: [PATCH 2/2] fix: test --- lms/djangoapps/courseware/tests/test_tabs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py index 56db188fe195..f2af5feed643 100644 --- a/lms/djangoapps/courseware/tests/test_tabs.py +++ b/lms/djangoapps/courseware/tests/test_tabs.py @@ -870,6 +870,7 @@ def test_singular_dates_tab(self): """Test cases for making sure no persisted dates tab is surfaced""" user = self.create_mock_user() self.course.tabs = self.all_valid_tab_list + self.course.hide_dates_tab = False self.course.save() # Verify that there is a dates tab in the modulestore