feat: create DRF endpoint to get course index context (#33667)
* feat: create DRF endpoint to get course index context * refactor: update serializers location and added some tests * refactor: move modulestore usage out of views --------- Co-authored-by: ruzniaievdm <ruzniaievdm@gmail.com>
This commit is contained in:
@@ -50,3 +50,23 @@ 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()
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 (
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
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()
|
||||
@@ -4,6 +4,7 @@ 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
|
||||
|
||||
|
||||
@@ -31,26 +32,6 @@ 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.
|
||||
|
||||
@@ -7,6 +7,7 @@ from openedx.core.constants import COURSE_ID_PATTERN
|
||||
from .views import (
|
||||
CourseDetailsView,
|
||||
CourseTeamView,
|
||||
CourseIndexView,
|
||||
CourseGradingView,
|
||||
CourseRerunView,
|
||||
CourseSettingsView,
|
||||
@@ -59,6 +60,11 @@ 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(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
"""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)
|
||||
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
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"
|
||||
})
|
||||
@@ -25,7 +25,8 @@ 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
|
||||
from common.djangoapps.course_action_state.models import CourseRerunUIStateManager, CourseRerunState
|
||||
from common.djangoapps.course_action_state.managers import CourseActionStateItemNotFoundError
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.edxmako.services import MakoService
|
||||
from common.djangoapps.student import auth
|
||||
@@ -45,6 +46,8 @@ 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
|
||||
@@ -80,7 +83,9 @@ 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
|
||||
@@ -1672,6 +1677,89 @@ 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.
|
||||
|
||||
@@ -54,12 +54,9 @@ 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
|
||||
@@ -69,11 +66,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, DEFAULT_START_DATE, CourseFields # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.course_block import CourseBlock, 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, ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.exceptions import DuplicateCourseError # 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
|
||||
|
||||
@@ -102,10 +99,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,
|
||||
@@ -625,72 +622,13 @@ 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))
|
||||
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)
|
||||
if not course_block:
|
||||
raise Http404
|
||||
if use_new_course_outline_page(course_key):
|
||||
return redirect(get_course_outline_url(course_key))
|
||||
|
||||
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),
|
||||
})
|
||||
course_index_context = get_course_index_context(request, course_key, course_block)
|
||||
return render_to_response('course_outline.html', course_index_context)
|
||||
|
||||
|
||||
@function_trace('get_courses_accessible_to_user')
|
||||
|
||||
Reference in New Issue
Block a user