feat: create course home api DRF (#33173)
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
"""
|
||||
Serializers for v1 contentstore API.
|
||||
"""
|
||||
from .home import CourseHomeSerializer
|
||||
from .course_details import CourseDetailsSerializer
|
||||
from .course_team import CourseTeamSerializer
|
||||
from .course_rerun import CourseRerunSerializer
|
||||
from .grading import CourseGradingModelSerializer, CourseGradingSerializer
|
||||
from .proctoring import (
|
||||
LimitedProctoredExamSettingsSerializer,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Common API Serializers
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from openedx.core.lib.api.serializers import CourseKeyField
|
||||
|
||||
|
||||
class CourseCommonSerializer(serializers.Serializer):
|
||||
"""Serializer for course renders"""
|
||||
course_key = CourseKeyField()
|
||||
display_name = serializers.CharField()
|
||||
lms_link = serializers.CharField()
|
||||
number = serializers.CharField()
|
||||
org = serializers.CharField()
|
||||
rerun_link = serializers.CharField()
|
||||
run = serializers.CharField()
|
||||
url = serializers.CharField()
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
API Serializers for course rerun
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class CourseRerunSerializer(serializers.Serializer):
|
||||
""" Serializer for course rerun """
|
||||
allow_unicode_course_id = serializers.BooleanField()
|
||||
course_creator_status = serializers.CharField()
|
||||
display_name = serializers.CharField()
|
||||
number = serializers.CharField()
|
||||
org = serializers.CharField()
|
||||
run = serializers.CharField()
|
||||
62
cms/djangoapps/contentstore/rest_api/v1/serializers/home.py
Normal file
62
cms/djangoapps/contentstore/rest_api/v1/serializers/home.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
API Serializers for course home
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from openedx.core.lib.api.serializers import CourseKeyField
|
||||
|
||||
from .common import CourseCommonSerializer
|
||||
|
||||
|
||||
class UnsucceededCourseSerializer(serializers.Serializer):
|
||||
"""Serializer for unsucceeded course"""
|
||||
display_name = serializers.CharField()
|
||||
course_key = CourseKeyField()
|
||||
org = serializers.CharField()
|
||||
number = serializers.CharField()
|
||||
run = serializers.CharField()
|
||||
is_failed = serializers.BooleanField()
|
||||
is_in_progress = serializers.BooleanField()
|
||||
dismiss_link = serializers.CharField()
|
||||
|
||||
|
||||
class LibraryViewSerializer(serializers.Serializer):
|
||||
"""Serializer for library view"""
|
||||
display_name = serializers.CharField()
|
||||
library_key = serializers.CharField()
|
||||
url = serializers.CharField()
|
||||
org = serializers.CharField()
|
||||
number = serializers.CharField()
|
||||
can_edit = serializers.BooleanField()
|
||||
|
||||
|
||||
class CourseHomeSerializer(serializers.Serializer):
|
||||
"""Serializer for course home"""
|
||||
allow_course_reruns = serializers.BooleanField()
|
||||
allow_to_create_new_org = serializers.BooleanField()
|
||||
allow_unicode_course_id = serializers.BooleanField()
|
||||
allowed_organizations = serializers.ListSerializer(
|
||||
child=serializers.CharField(),
|
||||
allow_empty=True
|
||||
)
|
||||
archived_courses = CourseCommonSerializer(required=False, many=True)
|
||||
can_create_organizations = serializers.BooleanField()
|
||||
course_creator_status = serializers.CharField()
|
||||
courses = CourseCommonSerializer(required=False, many=True)
|
||||
in_process_course_actions = UnsucceededCourseSerializer(many=True, required=False, allow_null=True)
|
||||
libraries = LibraryViewSerializer(many=True, required=False, allow_null=True)
|
||||
libraries_enabled = serializers.BooleanField()
|
||||
library_authoring_mfe_url = serializers.CharField()
|
||||
optimization_enabled = serializers.BooleanField()
|
||||
redirect_to_library_authoring_mfe = serializers.BooleanField()
|
||||
request_course_creator_url = serializers.CharField()
|
||||
rerun_creator_status = serializers.BooleanField()
|
||||
show_new_library_button = serializers.BooleanField()
|
||||
split_studio_home = serializers.BooleanField()
|
||||
studio_name = serializers.CharField()
|
||||
studio_short_name = serializers.CharField()
|
||||
studio_request_email = serializers.CharField()
|
||||
tech_support_email = serializers.CharField()
|
||||
platform_name = serializers.CharField()
|
||||
user_is_active = serializers.BooleanField()
|
||||
@@ -4,23 +4,11 @@ API Serializers for course settings
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from openedx.core.lib.api.serializers import CourseKeyField
|
||||
|
||||
|
||||
class PossiblePreRequisiteCourseSerializer(serializers.Serializer):
|
||||
""" Serializer for possible pre requisite course """
|
||||
course_key = CourseKeyField()
|
||||
display_name = serializers.CharField()
|
||||
lms_link = serializers.CharField()
|
||||
number = serializers.CharField()
|
||||
org = serializers.CharField()
|
||||
rerun_link = serializers.CharField()
|
||||
run = serializers.CharField()
|
||||
url = serializers.CharField()
|
||||
from .common import CourseCommonSerializer
|
||||
|
||||
|
||||
class CourseSettingsSerializer(serializers.Serializer):
|
||||
""" Serializer for course settings """
|
||||
"""Serializer for course settings"""
|
||||
about_page_editable = serializers.BooleanField()
|
||||
can_show_certificate_available_date_field = serializers.BooleanField()
|
||||
course_display_name = serializers.CharField()
|
||||
@@ -38,7 +26,7 @@ class CourseSettingsSerializer(serializers.Serializer):
|
||||
marketing_enabled = serializers.BooleanField()
|
||||
mfe_proctored_exam_settings_url = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
||||
platform_name = serializers.CharField()
|
||||
possible_pre_requisite_courses = PossiblePreRequisiteCourseSerializer(required=False, many=True)
|
||||
possible_pre_requisite_courses = CourseCommonSerializer(required=False, many=True)
|
||||
short_description_editable = serializers.BooleanField()
|
||||
show_min_grade_warning = serializers.BooleanField()
|
||||
sidebar_html_enabled = serializers.BooleanField()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
""" Contenstore API v1 URLs. """
|
||||
|
||||
from django.urls import path
|
||||
from django.urls import re_path
|
||||
from django.conf import settings
|
||||
from django.urls import re_path, path
|
||||
|
||||
from openedx.core.constants import COURSE_ID_PATTERN
|
||||
|
||||
@@ -10,7 +9,9 @@ from .views import (
|
||||
CourseDetailsView,
|
||||
CourseTeamView,
|
||||
CourseGradingView,
|
||||
CourseRerunView,
|
||||
CourseSettingsView,
|
||||
HomePageView,
|
||||
ProctoredExamSettingsView,
|
||||
ProctoringErrorsView,
|
||||
xblock,
|
||||
@@ -25,6 +26,11 @@ app_name = 'v1'
|
||||
VIDEO_ID_PATTERN = r'(?:(?P<edx_video_id>[-\w]+))'
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
'home',
|
||||
HomePageView.as_view(),
|
||||
name="home"
|
||||
),
|
||||
re_path(
|
||||
fr'^proctored_exam_settings/{COURSE_ID_PATTERN}$',
|
||||
ProctoredExamSettingsView.as_view(),
|
||||
@@ -92,4 +98,9 @@ urlpatterns = [
|
||||
HelpUrlsView.as_view(),
|
||||
name="help_urls"
|
||||
),
|
||||
re_path(
|
||||
fr'^course_rerun/{COURSE_ID_PATTERN}$',
|
||||
CourseRerunView.as_view(),
|
||||
name="course_rerun"
|
||||
),
|
||||
]
|
||||
|
||||
@@ -3,8 +3,10 @@ Views for v1 contentstore API.
|
||||
"""
|
||||
from .course_details import CourseDetailsView
|
||||
from .course_team import CourseTeamView
|
||||
from .course_rerun import CourseRerunView
|
||||
from .grading import CourseGradingView
|
||||
from .proctoring import ProctoredExamSettingsView, ProctoringErrorsView
|
||||
from .home import HomePageView
|
||||
from .settings import CourseSettingsView
|
||||
from .xblock import XblockView
|
||||
from .assets import AssetsView
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
""" API Views for course rerun """
|
||||
|
||||
import edx_api_doc_tools as apidocs
|
||||
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.utils import get_course_rerun_context
|
||||
from cms.djangoapps.contentstore.rest_api.v1.serializers import CourseRerunSerializer
|
||||
from common.djangoapps.student.roles import GlobalStaff
|
||||
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@view_auth_classes(is_authenticated=True)
|
||||
class CourseRerunView(DeveloperErrorViewMixin, APIView):
|
||||
"""
|
||||
View for course rerun.
|
||||
"""
|
||||
|
||||
@apidocs.schema(
|
||||
parameters=[
|
||||
apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"),
|
||||
],
|
||||
responses={
|
||||
200: CourseRerunSerializer,
|
||||
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 rerun.
|
||||
|
||||
**Example Request**
|
||||
|
||||
GET /api/contentstore/v1/course_rerun/{course_id}
|
||||
|
||||
**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 rerun.
|
||||
|
||||
**Example Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"allow_unicode_course_id": False,
|
||||
"course_creator_status": "granted",
|
||||
"number": "101",
|
||||
"display_name": "new edx course",
|
||||
"org": "edx",
|
||||
"run": "2023",
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
if not GlobalStaff().has_user(request.user):
|
||||
self.permission_denied(request)
|
||||
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_block = modulestore().get_course(course_key)
|
||||
course_rerun_context = get_course_rerun_context(course_key, course_block, request.user)
|
||||
course_rerun_context.update({
|
||||
'org': course_key.org,
|
||||
'number': course_key.course,
|
||||
'run': course_key.run,
|
||||
})
|
||||
serializer = CourseRerunSerializer(course_rerun_context)
|
||||
return Response(serializer.data)
|
||||
120
cms/djangoapps/contentstore/rest_api/v1/views/home.py
Normal file
120
cms/djangoapps/contentstore/rest_api/v1/views/home.py
Normal file
@@ -0,0 +1,120 @@
|
||||
""" API Views for course home """
|
||||
|
||||
import edx_api_doc_tools as apidocs
|
||||
from django.conf import settings
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
|
||||
from ....utils import get_home_context
|
||||
from ..serializers import CourseHomeSerializer
|
||||
|
||||
|
||||
@view_auth_classes(is_authenticated=True)
|
||||
class HomePageView(APIView):
|
||||
"""
|
||||
View for getting all courses and libraries available to the logged in user.
|
||||
"""
|
||||
@apidocs.schema(
|
||||
parameters=[
|
||||
apidocs.string_parameter(
|
||||
"org",
|
||||
apidocs.ParameterLocation.QUERY,
|
||||
description="Query param to filter by course org",
|
||||
)],
|
||||
responses={
|
||||
200: CourseHomeSerializer,
|
||||
401: "The requester is not authenticated.",
|
||||
},
|
||||
)
|
||||
def get(self, request: Request):
|
||||
"""
|
||||
Get an object containing all courses and libraries on home page.
|
||||
|
||||
**Example Request**
|
||||
|
||||
GET /api/contentstore/v1/home
|
||||
|
||||
**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 home.
|
||||
|
||||
**Example Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"allow_course_reruns": true,
|
||||
"allow_to_create_new_org": true,
|
||||
"allow_unicode_course_id": false,
|
||||
"allowed_organizations": [],
|
||||
"archived_courses": [
|
||||
{
|
||||
"course_key": "course-v1:edX+P315+2T2023",
|
||||
"display_name": "Quantum Entanglement",
|
||||
"lms_link": "//localhost:18000/courses/course-v1:edX+P315+2T2023",
|
||||
"number": "P315",
|
||||
"org": "edX",
|
||||
"rerun_link": "/course_rerun/course-v1:edX+P315+2T2023",
|
||||
"run": "2T2023"
|
||||
"url": "/course/course-v1:edX+P315+2T2023"
|
||||
},
|
||||
],
|
||||
"can_create_organizations": true,
|
||||
"course_creator_status": "granted",
|
||||
"courses": [
|
||||
{
|
||||
"course_key": "course-v1:edX+E2E-101+course",
|
||||
"display_name": "E2E Test Course",
|
||||
"lms_link": "//localhost:18000/courses/course-v1:edX+E2E-101+course",
|
||||
"number": "E2E-101",
|
||||
"org": "edX",
|
||||
"rerun_link": "/course_rerun/course-v1:edX+E2E-101+course",
|
||||
"run": "course",
|
||||
"url": "/course/course-v1:edX+E2E-101+course"
|
||||
},
|
||||
],
|
||||
"in_process_course_actions": [],
|
||||
"libraries": [
|
||||
{
|
||||
"display_name": "My First Library",
|
||||
"library_key": "library-v1:new+CPSPR",
|
||||
"url": "/library/library-v1:new+CPSPR",
|
||||
"org": "new",
|
||||
"number": "CPSPR",
|
||||
"can_edit": true
|
||||
}
|
||||
],
|
||||
"libraries_enabled": true,
|
||||
"library_authoring_mfe_url": "//localhost:3001/course/course-v1:edX+P315+2T2023",
|
||||
"optimization_enabled": true,
|
||||
"redirect_to_library_authoring_mfe": false,
|
||||
"request_course_creator_url": "/request_course_creator",
|
||||
"rerun_creator_status": true,
|
||||
"show_new_library_button": true,
|
||||
"split_studio_home": false,
|
||||
"studio_name": "Studio",
|
||||
"studio_short_name": "Studio",
|
||||
"studio_request_email": "",
|
||||
"tech_support_email": "technical@example.com",
|
||||
"platform_name": "Your Platform Name Here"
|
||||
"user_is_active": true,
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
home_context = get_home_context(request)
|
||||
home_context.update({
|
||||
'allow_to_create_new_org': settings.FEATURES.get('ENABLE_CREATOR_GROUP', True) and request.user.is_staff,
|
||||
'studio_name': settings.STUDIO_NAME,
|
||||
'studio_short_name': settings.STUDIO_SHORT_NAME,
|
||||
'studio_request_email': settings.FEATURES.get('STUDIO_REQUEST_EMAIL', ''),
|
||||
'tech_support_email': settings.TECH_SUPPORT_EMAIL,
|
||||
'platform_name': settings.PLATFORM_NAME,
|
||||
'user_is_active': request.user.is_active,
|
||||
})
|
||||
serializer = CourseHomeSerializer(home_context)
|
||||
return Response(serializer.data)
|
||||
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Unit tests for course rerun.
|
||||
"""
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.rest_api.v1.mixins import PermissionAccessMixin
|
||||
|
||||
|
||||
class CourseRerunViewTest(CourseTestCase, PermissionAccessMixin):
|
||||
"""
|
||||
Tests for CourseRerunView.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse(
|
||||
"cms.djangoapps.contentstore:v1:course_rerun",
|
||||
kwargs={"course_id": self.course.id},
|
||||
)
|
||||
|
||||
def test_course_rerun_response(self):
|
||||
"""Check successful response content"""
|
||||
response = self.client.get(self.url)
|
||||
expected_response = {
|
||||
"allow_unicode_course_id": False,
|
||||
"course_creator_status": "granted",
|
||||
"display_name": self.course.display_name,
|
||||
"number": self.course.id.course,
|
||||
"org": self.course.id.org,
|
||||
"run": self.course.id.run,
|
||||
}
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertDictEqual(expected_response, response.data)
|
||||
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Unit tests for home page view.
|
||||
"""
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from edx_toggles.toggles.testutils import override_waffle_switch
|
||||
from rest_framework import status
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.views.course import ENABLE_GLOBAL_STAFF_OPTIMIZATION
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class HomePageViewTest(CourseTestCase):
|
||||
"""
|
||||
Tests for HomePageView.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse("cms.djangoapps.contentstore:v1:home")
|
||||
|
||||
def test_home_page_response(self):
|
||||
"""Check successful response content"""
|
||||
response = self.client.get(self.url)
|
||||
expected_response = {
|
||||
"allow_course_reruns": True,
|
||||
"allow_to_create_new_org": False,
|
||||
"allow_unicode_course_id": False,
|
||||
"allowed_organizations": [],
|
||||
"archived_courses": [],
|
||||
"can_create_organizations": True,
|
||||
"course_creator_status": "granted",
|
||||
"courses": [{
|
||||
"course_key": str(self.course.id),
|
||||
"display_name": self.course.display_name,
|
||||
"lms_link": None,
|
||||
"number": self.course.number,
|
||||
"org": self.course.org,
|
||||
"rerun_link": None,
|
||||
"run": self.course.id.run,
|
||||
"url": None,
|
||||
}],
|
||||
"in_process_course_actions": [],
|
||||
"libraries": [],
|
||||
"libraries_enabled": True,
|
||||
"library_authoring_mfe_url": settings.LIBRARY_AUTHORING_MICROFRONTEND_URL,
|
||||
"optimization_enabled": False,
|
||||
"redirect_to_library_authoring_mfe": False,
|
||||
"request_course_creator_url": "/request_course_creator",
|
||||
"rerun_creator_status": True,
|
||||
"show_new_library_button": True,
|
||||
"split_studio_home": False,
|
||||
"studio_name": settings.STUDIO_NAME,
|
||||
"studio_short_name": settings.STUDIO_SHORT_NAME,
|
||||
"studio_request_email": "",
|
||||
"tech_support_email": "technical@example.com",
|
||||
"platform_name": settings.PLATFORM_NAME,
|
||||
"user_is_active": True,
|
||||
}
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertDictEqual(expected_response, response.data)
|
||||
|
||||
@override_waffle_switch(ENABLE_GLOBAL_STAFF_OPTIMIZATION, True)
|
||||
def test_org_query_if_passed(self):
|
||||
"""Test home page when org filter passed as a query param"""
|
||||
foo_course = self.store.make_course_key('foo-org', 'bar-number', 'baz-run')
|
||||
test_course = CourseFactory.create(
|
||||
org=foo_course.org,
|
||||
number=foo_course.course,
|
||||
run=foo_course.run
|
||||
)
|
||||
CourseOverviewFactory.create(id=test_course.id, org='foo-org')
|
||||
response = self.client.get(self.url, {"org": "foo-org"})
|
||||
self.assertEqual(len(response.data['courses']), 1)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@override_waffle_switch(ENABLE_GLOBAL_STAFF_OPTIMIZATION, True)
|
||||
def test_org_query_if_empty(self):
|
||||
"""Test home page with an empty org query param"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(len(response.data['courses']), 0)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
@@ -24,6 +24,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
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.edxmako.services import MakoService
|
||||
from common.djangoapps.student import auth
|
||||
@@ -59,6 +60,7 @@ from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME
|
||||
from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
|
||||
from cms.djangoapps.contentstore.toggles import (
|
||||
split_library_view_on_dashboard,
|
||||
use_new_advanced_settings_page,
|
||||
use_new_course_outline_page,
|
||||
use_new_export_page,
|
||||
@@ -68,12 +70,13 @@ from cms.djangoapps.contentstore.toggles import (
|
||||
use_new_home_page,
|
||||
use_new_import_page,
|
||||
use_new_schedule_details_page,
|
||||
use_new_text_editor,
|
||||
use_new_unit_page,
|
||||
use_new_updates_page,
|
||||
use_new_video_editor,
|
||||
use_new_video_uploads_page,
|
||||
use_new_custom_pages,
|
||||
)
|
||||
from cms.djangoapps.contentstore.toggles import use_new_text_editor, use_new_video_editor
|
||||
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
|
||||
from xmodule.library_tools import LibraryToolsService
|
||||
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
|
||||
@@ -1387,6 +1390,109 @@ def get_help_urls():
|
||||
return help_tokens
|
||||
|
||||
|
||||
def get_home_context(request):
|
||||
"""
|
||||
Utils is used to get context of course grading.
|
||||
It is used for both DRF and django views.
|
||||
"""
|
||||
|
||||
from cms.djangoapps.contentstore.views.course import (
|
||||
get_allowed_organizations,
|
||||
get_allowed_organizations_for_libraries,
|
||||
get_courses_accessible_to_user,
|
||||
user_can_create_organizations,
|
||||
_accessible_libraries_iter,
|
||||
_get_course_creator_status,
|
||||
_format_library_for_view,
|
||||
_process_courses_list,
|
||||
ENABLE_GLOBAL_STAFF_OPTIMIZATION,
|
||||
)
|
||||
from cms.djangoapps.contentstore.views.library import (
|
||||
LIBRARY_AUTHORING_MICROFRONTEND_URL,
|
||||
LIBRARIES_ENABLED,
|
||||
should_redirect_to_library_authoring_mfe,
|
||||
user_can_create_library,
|
||||
)
|
||||
|
||||
optimization_enabled = GlobalStaff().has_user(request.user) and ENABLE_GLOBAL_STAFF_OPTIMIZATION.is_enabled()
|
||||
|
||||
org = request.GET.get('org', '') if optimization_enabled else None
|
||||
courses_iter, in_process_course_actions = get_courses_accessible_to_user(request, org)
|
||||
user = request.user
|
||||
libraries = []
|
||||
if not split_library_view_on_dashboard() and LIBRARIES_ENABLED:
|
||||
libraries = _accessible_libraries_iter(request.user)
|
||||
|
||||
def format_in_process_course_view(uca):
|
||||
"""
|
||||
Return a dict of the data which the view requires for each unsucceeded course
|
||||
"""
|
||||
return {
|
||||
'display_name': uca.display_name,
|
||||
'course_key': str(uca.course_key),
|
||||
'org': uca.course_key.org,
|
||||
'number': uca.course_key.course,
|
||||
'run': uca.course_key.run,
|
||||
'is_failed': uca.state == CourseRerunUIStateManager.State.FAILED,
|
||||
'is_in_progress': uca.state == CourseRerunUIStateManager.State.IN_PROGRESS,
|
||||
'dismiss_link': reverse_course_url(
|
||||
'course_notifications_handler',
|
||||
uca.course_key,
|
||||
kwargs={
|
||||
'action_state_id': uca.id,
|
||||
},
|
||||
) if uca.state == CourseRerunUIStateManager.State.FAILED else ''
|
||||
}
|
||||
|
||||
split_archived = settings.FEATURES.get('ENABLE_SEPARATE_ARCHIVED_COURSES', False)
|
||||
active_courses, archived_courses = _process_courses_list(courses_iter, in_process_course_actions, split_archived)
|
||||
in_process_course_actions = [format_in_process_course_view(uca) for uca in in_process_course_actions]
|
||||
|
||||
home_context = {
|
||||
'courses': active_courses,
|
||||
'split_studio_home': split_library_view_on_dashboard(),
|
||||
'archived_courses': archived_courses,
|
||||
'in_process_course_actions': in_process_course_actions,
|
||||
'libraries_enabled': LIBRARIES_ENABLED,
|
||||
'redirect_to_library_authoring_mfe': should_redirect_to_library_authoring_mfe(),
|
||||
'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL,
|
||||
'libraries': [_format_library_for_view(lib, request) for lib in libraries],
|
||||
'show_new_library_button': user_can_create_library(user) and not should_redirect_to_library_authoring_mfe(),
|
||||
'user': user,
|
||||
'request_course_creator_url': reverse('request_course_creator'),
|
||||
'course_creator_status': _get_course_creator_status(user),
|
||||
'rerun_creator_status': GlobalStaff().has_user(user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
|
||||
'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True),
|
||||
'optimization_enabled': optimization_enabled,
|
||||
'active_tab': 'courses',
|
||||
'allowed_organizations': get_allowed_organizations(user),
|
||||
'allowed_organizations_for_libraries': get_allowed_organizations_for_libraries(user),
|
||||
'can_create_organizations': user_can_create_organizations(user),
|
||||
}
|
||||
|
||||
return home_context
|
||||
|
||||
|
||||
def get_course_rerun_context(course_key, course_block, user):
|
||||
"""
|
||||
Utils is used to get context of course rerun.
|
||||
It is used for both DRF and django views.
|
||||
"""
|
||||
|
||||
from cms.djangoapps.contentstore.views.course import _get_course_creator_status
|
||||
|
||||
course_rerun_context = {
|
||||
'source_course_key': course_key,
|
||||
'display_name': course_block.display_name,
|
||||
'user': user,
|
||||
'course_creator_status': _get_course_creator_status(user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False)
|
||||
}
|
||||
|
||||
return course_rerun_context
|
||||
|
||||
|
||||
class StudioPermissionsService:
|
||||
"""
|
||||
Service that can provide information about a user's permissions.
|
||||
|
||||
@@ -31,6 +31,7 @@ from organizations.api import add_organization_course, ensure_organization
|
||||
from organizations.exceptions import InvalidOrganizationException
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import create_xblock_info
|
||||
from cms.djangoapps.course_creators.views import add_user_with_status_unrequested, get_course_creator_status
|
||||
from cms.djangoapps.course_creators.models import CourseCreator
|
||||
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
|
||||
@@ -100,6 +101,7 @@ from ..utils import (
|
||||
add_instructor,
|
||||
get_course_settings,
|
||||
get_course_grading,
|
||||
get_home_context,
|
||||
get_lms_link_for_item,
|
||||
get_proctored_exam_settings_url,
|
||||
get_course_outline_url,
|
||||
@@ -108,6 +110,7 @@ from ..utils import (
|
||||
get_advanced_settings_url,
|
||||
get_grading_url,
|
||||
get_schedule_details_url,
|
||||
get_course_rerun_context,
|
||||
initialize_permissions,
|
||||
remove_all_instructors,
|
||||
reverse_course_url,
|
||||
@@ -118,15 +121,7 @@ from ..utils import (
|
||||
update_course_discussions_settings,
|
||||
)
|
||||
from .component import ADVANCED_COMPONENT_TYPES
|
||||
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import (
|
||||
create_xblock_info,
|
||||
)
|
||||
from .library import (
|
||||
LIBRARIES_ENABLED,
|
||||
LIBRARY_AUTHORING_MICROFRONTEND_URL,
|
||||
user_can_create_library,
|
||||
should_redirect_to_library_authoring_mfe
|
||||
)
|
||||
from .library import LIBRARIES_ENABLED
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
User = get_user_model()
|
||||
@@ -334,13 +329,8 @@ def course_rerun_handler(request, course_key_string):
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_block = get_course_and_check_access(course_key, request.user, depth=3)
|
||||
if request.method == 'GET':
|
||||
return render_to_response('course-create-rerun.html', {
|
||||
'source_course_key': course_key,
|
||||
'display_name': course_block.display_name,
|
||||
'user': request.user,
|
||||
'course_creator_status': _get_course_creator_status(request.user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False)
|
||||
})
|
||||
course_rerun_context = get_course_rerun_context(course_key, course_block, request.user)
|
||||
return render_to_response('course-create-rerun.html', course_rerun_context)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -551,62 +541,8 @@ def course_listing(request):
|
||||
if use_new_home_page():
|
||||
return redirect(get_studio_home_url())
|
||||
|
||||
optimization_enabled = GlobalStaff().has_user(request.user) and ENABLE_GLOBAL_STAFF_OPTIMIZATION.is_enabled()
|
||||
|
||||
org = request.GET.get('org', '') if optimization_enabled else None
|
||||
courses_iter, in_process_course_actions = get_courses_accessible_to_user(request, org)
|
||||
user = request.user
|
||||
libraries = []
|
||||
if not split_library_view_on_dashboard() and LIBRARIES_ENABLED:
|
||||
libraries = _accessible_libraries_iter(request.user)
|
||||
|
||||
def format_in_process_course_view(uca):
|
||||
"""
|
||||
Return a dict of the data which the view requires for each unsucceeded course
|
||||
"""
|
||||
return {
|
||||
'display_name': uca.display_name,
|
||||
'course_key': str(uca.course_key),
|
||||
'org': uca.course_key.org,
|
||||
'number': uca.course_key.course,
|
||||
'run': uca.course_key.run,
|
||||
'is_failed': uca.state == CourseRerunUIStateManager.State.FAILED,
|
||||
'is_in_progress': uca.state == CourseRerunUIStateManager.State.IN_PROGRESS,
|
||||
'dismiss_link': reverse_course_url(
|
||||
'course_notifications_handler',
|
||||
uca.course_key,
|
||||
kwargs={
|
||||
'action_state_id': uca.id,
|
||||
},
|
||||
) if uca.state == CourseRerunUIStateManager.State.FAILED else ''
|
||||
}
|
||||
|
||||
split_archived = settings.FEATURES.get('ENABLE_SEPARATE_ARCHIVED_COURSES', False)
|
||||
active_courses, archived_courses = _process_courses_list(courses_iter, in_process_course_actions, split_archived)
|
||||
in_process_course_actions = [format_in_process_course_view(uca) for uca in in_process_course_actions]
|
||||
|
||||
return render_to_response('index.html', {
|
||||
'courses': active_courses,
|
||||
'split_studio_home': split_library_view_on_dashboard(),
|
||||
'archived_courses': archived_courses,
|
||||
'in_process_course_actions': in_process_course_actions,
|
||||
'libraries_enabled': LIBRARIES_ENABLED,
|
||||
'redirect_to_library_authoring_mfe': should_redirect_to_library_authoring_mfe(),
|
||||
'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL,
|
||||
'libraries': [_format_library_for_view(lib, request) for lib in libraries],
|
||||
'show_new_library_button': user_can_create_library(user) and not should_redirect_to_library_authoring_mfe(),
|
||||
'user': user,
|
||||
'request_course_creator_url': reverse('request_course_creator'),
|
||||
'course_creator_status': _get_course_creator_status(user),
|
||||
'rerun_creator_status': GlobalStaff().has_user(user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
|
||||
'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True),
|
||||
'optimization_enabled': optimization_enabled,
|
||||
'active_tab': 'courses',
|
||||
'allowed_organizations': get_allowed_organizations(user),
|
||||
'allowed_organizations_for_libraries': get_allowed_organizations_for_libraries(user),
|
||||
'can_create_organizations': user_can_create_organizations(user),
|
||||
})
|
||||
home_context = get_home_context(request)
|
||||
return render_to_response('index.html', home_context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user