diff --git a/cms/djangoapps/contentstore/rest_api/serializers/common.py b/cms/djangoapps/contentstore/rest_api/serializers/common.py index 824054330f..362504ccb0 100644 --- a/cms/djangoapps/contentstore/rest_api/serializers/common.py +++ b/cms/djangoapps/contentstore/rest_api/serializers/common.py @@ -50,23 +50,3 @@ class StrictSerializer(serializers.Serializer): ) return ret - - -class ProctoringErrorModelSerializer(serializers.Serializer): - """ - Serializer for proctoring error model item. - """ - deprecated = serializers.BooleanField() - display_name = serializers.CharField() - help = serializers.CharField() - hide_on_enabled_publisher = serializers.BooleanField() - value = serializers.CharField() - - -class ProctoringErrorListSerializer(serializers.Serializer): - """ - Serializer for proctoring error list. - """ - key = serializers.CharField() - message = serializers.CharField() - model = ProctoringErrorModelSerializer() diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py index 12f797e8e6..f47c9911b2 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py @@ -4,7 +4,6 @@ Serializers for v1 contentstore API. from .course_details import CourseDetailsSerializer from .course_rerun import CourseRerunSerializer from .course_team import CourseTeamSerializer -from .course_index import CourseIndexSerializer from .grading import CourseGradingModelSerializer, CourseGradingSerializer from .home import CourseHomeSerializer from .proctoring import ( diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_index.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_index.py deleted file mode 100644 index d423f7e9da..0000000000 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_index.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -API Serializers for course index -""" - -from rest_framework import serializers - -from cms.djangoapps.contentstore.rest_api.serializers.common import ProctoringErrorListSerializer - - -class InitialIndexStateSerializer(serializers.Serializer): - """Serializer for initial course index state""" - expanded_locators = serializers.ListSerializer(child=serializers.CharField()) - locator_to_show = serializers.CharField() - - -class CourseIndexSerializer(serializers.Serializer): - """Serializer for course index""" - course_release_date = serializers.CharField() - course_structure = serializers.DictField() - deprecated_blocks_info = serializers.DictField() - discussions_incontext_feedback_url = serializers.CharField() - discussions_incontext_learnmore_url = serializers.CharField() - initial_state = InitialIndexStateSerializer() - initial_user_clipboard = serializers.DictField() - language_code = serializers.CharField() - lms_link = serializers.CharField() - mfe_proctored_exam_settings_url = serializers.CharField() - notification_dismiss_url = serializers.CharField() - proctoring_errors = ProctoringErrorListSerializer(many=True) - reindex_link = serializers.CharField() - rerun_notification_id = serializers.IntegerField() diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/proctoring.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/proctoring.py index 8398bd8ad6..df5b77f72f 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers/proctoring.py +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/proctoring.py @@ -4,7 +4,6 @@ API Serializers for proctoring from rest_framework import serializers -from cms.djangoapps.contentstore.rest_api.serializers.common import ProctoringErrorListSerializer from xmodule.course_block import get_available_providers @@ -32,6 +31,26 @@ class ProctoredExamConfigurationSerializer(serializers.Serializer): course_start_date = serializers.DateTimeField() +class ProctoringErrorModelSerializer(serializers.Serializer): + """ + Serializer for proctoring error model item. + """ + deprecated = serializers.BooleanField() + display_name = serializers.CharField() + help = serializers.CharField() + hide_on_enabled_publisher = serializers.BooleanField() + value = serializers.CharField() + + +class ProctoringErrorListSerializer(serializers.Serializer): + """ + Serializer for proctoring error list. + """ + key = serializers.CharField() + message = serializers.CharField() + model = ProctoringErrorModelSerializer() + + class ProctoringErrorsSerializer(serializers.Serializer): """ Serializer for proctoring errors with url to proctored exam settings. diff --git a/cms/djangoapps/contentstore/rest_api/v1/urls.py b/cms/djangoapps/contentstore/rest_api/v1/urls.py index 217957dfc0..168fa9bcab 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/urls.py +++ b/cms/djangoapps/contentstore/rest_api/v1/urls.py @@ -7,7 +7,6 @@ from openedx.core.constants import COURSE_ID_PATTERN from .views import ( CourseDetailsView, CourseTeamView, - CourseIndexView, CourseGradingView, CourseRerunView, CourseSettingsView, @@ -60,11 +59,6 @@ urlpatterns = [ CourseSettingsView.as_view(), name="course_settings" ), - re_path( - fr'^course_index/{COURSE_ID_PATTERN}$', - CourseIndexView.as_view(), - name="course_index" - ), re_path( fr'^course_details/{COURSE_ID_PATTERN}$', CourseDetailsView.as_view(), diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py b/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py index 0800568ba2..81666919a4 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py @@ -2,7 +2,6 @@ Views for v1 contentstore API. """ from .course_details import CourseDetailsView -from .course_index import CourseIndexView from .course_team import CourseTeamView from .course_rerun import CourseRerunView from .grading import CourseGradingView diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/course_index.py b/cms/djangoapps/contentstore/rest_api/v1/views/course_index.py deleted file mode 100644 index 1ffac5ba69..0000000000 --- a/cms/djangoapps/contentstore/rest_api/v1/views/course_index.py +++ /dev/null @@ -1,98 +0,0 @@ -"""API Views for course index""" - -import edx_api_doc_tools as apidocs -from django.conf import settings -from opaque_keys.edx.keys import CourseKey -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.views import APIView - -from cms.djangoapps.contentstore.rest_api.v1.serializers import CourseIndexSerializer -from cms.djangoapps.contentstore.utils import get_course_index_context -from common.djangoapps.student.auth import has_studio_read_access -from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes - - -@view_auth_classes(is_authenticated=True) -class CourseIndexView(DeveloperErrorViewMixin, APIView): - """View for Course Index""" - - @apidocs.schema( - parameters=[ - apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"), - apidocs.string_parameter( - "show", - apidocs.ParameterLocation.QUERY, - description="Query param to set initial state which fully expanded to see the item", - )], - responses={ - 200: CourseIndexSerializer, - 401: "The requester is not authenticated.", - 403: "The requester cannot access the specified course.", - 404: "The requested course does not exist.", - }, - ) - @verify_course_exists() - def get(self, request: Request, course_id: str): - """ - Get an object containing course index for outline. - - **Example Request** - - GET /api/contentstore/v1/course_index/{course_id}?show=block-v1:edx+101+y+type@course+block@course - - **Response Values** - - If the request is successful, an HTTP 200 "OK" response is returned. - - The HTTP 200 response contains a single dict that contains keys that - are the course's outline. - - **Example Response** - - ```json - { - "course_release_date": "Set Date", - "course_structure": {}, - "deprecated_blocks_info": { - "deprecated_enabled_block_types": [], - "blocks": [], - "advance_settings_url": "/settings/advanced/course-v1:edx+101+y76" - }, - "discussions_incontext_feedback_url": "", - "discussions_incontext_learnmore_url": "", - "initial_state": { - "expanded_locators": [ - "block-v1:edx+101+y76+type@chapter+block@03de0adc9d1c4cc097062d80eb04abf6", - "block-v1:edx+101+y76+type@sequential+block@8a85e287e30a47e98d8c1f37f74a6a9d" - ], - "locator_to_show": "block-v1:edx+101+y76+type@chapter+block@03de0adc9d1c4cc097062d80eb04abf6" - }, - "initial_user_clipboard": { - "content": null, - "source_usage_key": "", - "source_context_title": "", - "source_edit_url": "" - }, - "language_code": "en", - "lms_link": "//localhost:18000/courses/course-v1:edx+101+y76/jump_to/block-v1:edx+101+y76", - "mfe_proctored_exam_settings_url": "", - "notification_dismiss_url": "/course_notifications/course-v1:edx+101+y76/2", - "proctoring_errors": [], - "reindex_link": "/course/course-v1:edx+101+y76/search_reindex", - "rerun_notification_id": 2 - } - ``` - """ - - course_key = CourseKey.from_string(course_id) - if not has_studio_read_access(request.user, course_key): - self.permission_denied(request) - course_index_context = get_course_index_context(request, course_key) - course_index_context.update({ - "discussions_incontext_learnmore_url": settings.DISCUSSIONS_INCONTEXT_LEARNMORE_URL, - "discussions_incontext_feedback_url": settings.DISCUSSIONS_INCONTEXT_FEEDBACK_URL, - }) - - serializer = CourseIndexSerializer(course_index_context) - return Response(serializer.data) diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py deleted file mode 100644 index 49b22a5d63..0000000000 --- a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Unit tests for course index outline. -""" -from django.test import RequestFactory -from django.urls import reverse -from rest_framework import status - -from cms.djangoapps.contentstore.rest_api.v1.mixins import PermissionAccessMixin -from cms.djangoapps.contentstore.tests.utils import CourseTestCase -from cms.djangoapps.contentstore.utils import get_lms_link_for_item -from cms.djangoapps.contentstore.views.course import _course_outline_json -from common.djangoapps.student.tests.factories import UserFactory -from xmodule.modulestore.tests.factories import BlockFactory - - -class CourseIndexViewTest(CourseTestCase, PermissionAccessMixin): - """ - Tests for CourseIndexView. - """ - - def setUp(self): - super().setUp() - with self.store.bulk_operations(self.course.id, emit_signals=False): - self.chapter = BlockFactory.create( - parent=self.course, display_name='Overview' - ) - self.section = BlockFactory.create( - parent=self.chapter, display_name='Welcome' - ) - self.unit = BlockFactory.create( - parent=self.section, display_name='New Unit' - ) - self.xblock = BlockFactory.create( - parent=self.unit, - category='problem', - display_name='Some problem' - ) - self.user = UserFactory() - self.factory = RequestFactory() - self.request = self.factory.get(f"/course/{self.course.id}") - self.request.user = self.user - self.reload_course() - self.url = reverse( - "cms.djangoapps.contentstore:v1:course_index", - kwargs={"course_id": self.course.id}, - ) - - def test_course_index_response(self): - """Check successful response content""" - response = self.client.get(self.url) - expected_response = { - "course_release_date": "Set Date", - "course_structure": _course_outline_json(self.request, self.course), - "deprecated_blocks_info": { - "deprecated_enabled_block_types": [], - "blocks": [], - "advance_settings_url": f"/settings/advanced/{self.course.id}" - }, - "discussions_incontext_feedback_url": "", - "discussions_incontext_learnmore_url": "", - "initial_state": None, - "initial_user_clipboard": { - "content": None, - "source_usage_key": "", - "source_context_title": "", - "source_edit_url": "" - }, - "language_code": "en", - "lms_link": get_lms_link_for_item(self.course.location), - "mfe_proctored_exam_settings_url": "", - "notification_dismiss_url": None, - "proctoring_errors": [], - "reindex_link": f"/course/{self.course.id}/search_reindex", - "rerun_notification_id": None - } - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(expected_response, response.data) - - def test_course_index_response_with_show_locators(self): - """Check successful response content with show query param""" - response = self.client.get(self.url, {"show": str(self.unit.location)}) - expected_response = { - "course_release_date": "Set Date", - "course_structure": _course_outline_json(self.request, self.course), - "deprecated_blocks_info": { - "deprecated_enabled_block_types": [], - "blocks": [], - "advance_settings_url": f"/settings/advanced/{self.course.id}" - }, - "discussions_incontext_feedback_url": "", - "discussions_incontext_learnmore_url": "", - "initial_state": { - "expanded_locators": [ - str(self.unit.location), - str(self.xblock.location), - ], - "locator_to_show": str(self.unit.location), - }, - "initial_user_clipboard": { - "content": None, - "source_usage_key": "", - "source_context_title": "", - "source_edit_url": "" - }, - "language_code": "en", - "lms_link": get_lms_link_for_item(self.course.location), - "mfe_proctored_exam_settings_url": "", - "notification_dismiss_url": None, - "proctoring_errors": [], - "reindex_link": f"/course/{self.course.id}/search_reindex", - "rerun_notification_id": None - } - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertDictEqual(expected_response, response.data) - - def test_course_index_response_with_invalid_course(self): - """Check error response for invalid course id""" - response = self.client.get(self.url + "1") - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response.data, { - "developer_message": f"Unknown course {self.course.id}1", - "error_code": "course_does_not_exist" - }) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index d1acd50f71..62951e4cd6 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -25,8 +25,7 @@ from pytz import UTC from xblock.fields import Scope from cms.djangoapps.contentstore.toggles import exam_setting_view_enabled -from common.djangoapps.course_action_state.models import CourseRerunUIStateManager, CourseRerunState -from common.djangoapps.course_action_state.managers import CourseActionStateItemNotFoundError +from common.djangoapps.course_action_state.models import CourseRerunUIStateManager from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.edxmako.services import MakoService from common.djangoapps.student import auth @@ -46,8 +45,6 @@ from common.djangoapps.util.milestones_helpers import ( get_namespace_choices, generate_milestone_namespace ) -from common.djangoapps.util.date_utils import get_default_time_display -from common.djangoapps.xblock_django.api import deprecated_xblocks from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService from openedx.core import toggles as core_toggles from openedx.core.djangoapps.credit.api import get_credit_requirements, is_credit_course @@ -83,9 +80,7 @@ from cms.djangoapps.contentstore.toggles import ( # use_xpert_translations_component, ) from cms.djangoapps.models.settings.course_grading import CourseGradingModel -from cms.djangoapps.models.settings.course_metadata import CourseMetadata from xmodule.library_tools import LibraryToolsService -from xmodule.course_block import DEFAULT_START_DATE # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order @@ -1677,89 +1672,6 @@ def get_course_videos_context(course_block, pagination_conf, course_key=None): return course_video_context -def get_course_index_context(request, course_key, course_block=None): - """ - Utils is used to get context of course index outline. - It is used for both DRF and django views. - """ - - from cms.djangoapps.contentstore.views.course import ( - course_outline_initial_state, - _course_outline_json, - _deprecated_blocks_info, - ) - from openedx.core.djangoapps.content_staging import api as content_staging_api - - if not course_block: - with modulestore().bulk_operations(course_key): - course_block = modulestore().get_course(course_key) - - lms_link = get_lms_link_for_item(course_block.location) - reindex_link = None - if settings.FEATURES.get('ENABLE_COURSEWARE_INDEX', False): - if GlobalStaff().has_user(request.user): - reindex_link = f"/course/{str(course_key)}/search_reindex" - sections = course_block.get_children() - course_structure = _course_outline_json(request, course_block) - locator_to_show = request.GET.get('show', None) - - course_release_date = ( - get_default_time_display(course_block.start) - if course_block.start != DEFAULT_START_DATE - else _("Set Date") - ) - - settings_url = reverse_course_url('settings_handler', course_key) - - try: - current_action = CourseRerunState.objects.find_first(course_key=course_key, should_display=True) - except (ItemNotFoundError, CourseActionStateItemNotFoundError): - current_action = None - - deprecated_block_names = [block.name for block in deprecated_xblocks()] - deprecated_blocks_info = _deprecated_blocks_info(course_block, deprecated_block_names) - - frontend_app_publisher_url = configuration_helpers.get_value_for_org( - course_block.location.org, - 'FRONTEND_APP_PUBLISHER_URL', - settings.FEATURES.get('FRONTEND_APP_PUBLISHER_URL', False) - ) - # gather any errors in the currently stored proctoring settings. - advanced_dict = CourseMetadata.fetch(course_block) - proctoring_errors = CourseMetadata.validate_proctoring_settings(course_block, advanced_dict, request.user) - - user_clipboard = content_staging_api.get_user_clipboard_json(request.user.id, request) - - course_index_context = { - 'language_code': request.LANGUAGE_CODE, - 'context_course': course_block, - 'lms_link': lms_link, - 'sections': sections, - 'course_structure': course_structure, - 'initial_state': course_outline_initial_state(locator_to_show, course_structure) if locator_to_show else None, # lint-amnesty, pylint: disable=line-too-long - 'initial_user_clipboard': user_clipboard, - 'rerun_notification_id': current_action.id if current_action else None, - 'course_release_date': course_release_date, - 'settings_url': settings_url, - 'reindex_link': reindex_link, - 'deprecated_blocks_info': deprecated_blocks_info, - 'notification_dismiss_url': reverse_course_url( - 'course_notifications_handler', - current_action.course_key, - kwargs={ - 'action_state_id': current_action.id, - }, - ) if current_action else None, - 'frontend_app_publisher_url': frontend_app_publisher_url, - 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id), - 'advance_settings_url': reverse_course_url('advanced_settings_handler', course_block.id), - 'proctoring_errors': proctoring_errors, - 'taxonomy_tags_widget_url': get_taxonomy_tags_widget_url(course_block.id), - } - - return course_index_context - - class StudioPermissionsService: """ Service that can provide information about a user's permissions. diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 6d832b68ae..b42c011c4b 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -54,9 +54,12 @@ from common.djangoapps.student.roles import ( UserBasedRole, OrgStaffRole ) +from common.djangoapps.util.date_utils import get_default_time_display from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest, expect_json from common.djangoapps.util.string_utils import _has_non_ascii_characters +from common.djangoapps.xblock_django.api import deprecated_xblocks from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.content_staging import api as content_staging_api from openedx.core.djangoapps.credit.tasks import update_credit_course_requirements from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers @@ -66,11 +69,11 @@ from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME from organizations.models import Organization from xmodule.contentstore.content import StaticContent # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.course_block import CourseBlock, CourseFields # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.course_block import CourseBlock, DEFAULT_START_DATE, CourseFields # lint-amnesty, pylint: disable=wrong-import-order from xmodule.error_block import ErrorBlock # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore import EdxJSONEncoder # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.exceptions import DuplicateCourseError # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.modulestore.exceptions import DuplicateCourseError, ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions import UserPartition # lint-amnesty, pylint: disable=wrong-import-order from xmodule.tabs import CourseTab, CourseTabList, InvalidTabsException # lint-amnesty, pylint: disable=wrong-import-order @@ -99,10 +102,10 @@ from ..utils import ( get_course_grading, get_home_context, get_library_context, - get_course_index_context, get_lms_link_for_item, get_proctored_exam_settings_url, get_course_outline_url, + get_taxonomy_tags_widget_url, get_studio_home_url, get_updates_url, get_advanced_settings_url, @@ -622,13 +625,72 @@ def course_index(request, course_key): # A unit may not have a draft version, but one of its components could, and hence the unit itself has changes. with modulestore().bulk_operations(course_key): course_block = get_course_and_check_access(course_key, request.user, depth=None) - if not course_block: - raise Http404 - if use_new_course_outline_page(course_key): - return redirect(get_course_outline_url(course_key)) + if not course_block: + raise Http404 + if use_new_course_outline_page(course_key): + return redirect(get_course_outline_url(course_key)) + lms_link = get_lms_link_for_item(course_block.location) + reindex_link = None + if settings.FEATURES.get('ENABLE_COURSEWARE_INDEX', False): + if GlobalStaff().has_user(request.user): + reindex_link = f"/course/{str(course_key)}/search_reindex" + sections = course_block.get_children() + course_structure = _course_outline_json(request, course_block) + locator_to_show = request.GET.get('show', None) - course_index_context = get_course_index_context(request, course_key, course_block) - return render_to_response('course_outline.html', course_index_context) + course_release_date = ( + get_default_time_display(course_block.start) + if course_block.start != DEFAULT_START_DATE + else _("Set Date") + ) + + settings_url = reverse_course_url('settings_handler', course_key) + + try: + current_action = CourseRerunState.objects.find_first(course_key=course_key, should_display=True) + except (ItemNotFoundError, CourseActionStateItemNotFoundError): + current_action = None + + deprecated_block_names = [block.name for block in deprecated_xblocks()] + deprecated_blocks_info = _deprecated_blocks_info(course_block, deprecated_block_names) + + frontend_app_publisher_url = configuration_helpers.get_value_for_org( + course_block.location.org, + 'FRONTEND_APP_PUBLISHER_URL', + settings.FEATURES.get('FRONTEND_APP_PUBLISHER_URL', False) + ) + # gather any errors in the currently stored proctoring settings. + advanced_dict = CourseMetadata.fetch(course_block) + proctoring_errors = CourseMetadata.validate_proctoring_settings(course_block, advanced_dict, request.user) + + user_clipboard = content_staging_api.get_user_clipboard_json(request.user.id, request) + + return render_to_response('course_outline.html', { + 'language_code': request.LANGUAGE_CODE, + 'context_course': course_block, + 'lms_link': lms_link, + 'sections': sections, + 'course_structure': course_structure, + 'initial_state': course_outline_initial_state(locator_to_show, course_structure) if locator_to_show else None, # lint-amnesty, pylint: disable=line-too-long + 'initial_user_clipboard': user_clipboard, + 'rerun_notification_id': current_action.id if current_action else None, + 'course_release_date': course_release_date, + 'settings_url': settings_url, + 'reindex_link': reindex_link, + 'deprecated_blocks_info': deprecated_blocks_info, + 'notification_dismiss_url': reverse_course_url( + 'course_notifications_handler', + current_action.course_key, + kwargs={ + 'action_state_id': current_action.id, + }, + ) if current_action else None, + 'frontend_app_publisher_url': frontend_app_publisher_url, + 'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id), + 'advance_settings_url': reverse_course_url('advanced_settings_handler', course_block.id), + 'proctoring_errors': proctoring_errors, + 'taxonomy_tags_widget_url': get_taxonomy_tags_widget_url(course_block.id), + }) @function_trace('get_courses_accessible_to_user')