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
9 changes: 9 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@
# .. 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
Expand Down
9 changes: 8 additions & 1 deletion lms/djangoapps/course_home_api/dates/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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

Expand Down Expand Up @@ -110,13 +111,19 @@ def get(self, request, *args, **kwargs):
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,
}
Expand Down
8 changes: 7 additions & 1 deletion lms/djangoapps/course_home_api/outline/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
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
Expand Down Expand Up @@ -249,7 +250,12 @@ def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements
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)
Expand Down
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.ENABLE_DATES_COURSE_APP

@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)
1 change: 1 addition & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2783,6 +2783,7 @@
# .. setting_description: Content to replace spam posts with
CONTENT_FOR_SPAM_POSTS = ""


# .. 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