Skip to content
Open
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
45 changes: 45 additions & 0 deletions lms/djangoapps/courseware/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions lms/djangoapps/courseware/tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
"""
Expand Down
14 changes: 14 additions & 0 deletions lms/djangoapps/courseware/tests/test_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -886,3 +887,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)
9 changes: 9 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions openedx/core/djangoapps/content/course_overviews/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions xmodule/course_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=_(
Expand Down
Loading