176 lines
6.7 KiB
Python
176 lines
6.7 KiB
Python
"Contentstore Views"
|
|
import copy
|
|
|
|
from opaque_keys.edx.keys import CourseKey
|
|
from rest_framework import status
|
|
from rest_framework.exceptions import NotFound
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from cms.djangoapps.contentstore.views.course import get_course_and_check_access
|
|
from cms.djangoapps.models.settings.course_metadata import CourseMetadata
|
|
from common.lib.xmodule.xmodule.course_module import get_available_providers
|
|
from openedx.core.lib.api.view_utils import view_auth_classes
|
|
from xmodule.modulestore.django import modulestore
|
|
|
|
from .serializers import (
|
|
LimitedProctoredExamSettingsSerializer,
|
|
ProctoredExamConfigurationSerializer,
|
|
ProctoredExamSettingsSerializer
|
|
)
|
|
|
|
|
|
@view_auth_classes()
|
|
class ProctoredExamSettingsView(APIView):
|
|
"""
|
|
A view for retrieving information about proctored exam settings for a course.
|
|
|
|
Path: ``/api/contentstore/v1/proctored_exam_settings/{course_id}``
|
|
|
|
Accepts: [GET, POST]
|
|
|
|
------------------------------------------------------------------------------------
|
|
GET
|
|
------------------------------------------------------------------------------------
|
|
|
|
**Returns**
|
|
|
|
* 200: OK - Contains a set of course proctored exam settings.
|
|
* 401: The requesting user is not authenticated.
|
|
* 403: The requesting user lacks access to the course.
|
|
* 404: The requested course does not exist.
|
|
|
|
**Response**
|
|
|
|
In the case of a 200 response code, the response will proctored exam settings data
|
|
as well as other metadata about the course or the requesting user that are necessary
|
|
for rendering the settings page.
|
|
|
|
**Example**
|
|
|
|
{
|
|
"proctored_exam_settings": {
|
|
"enable_proctored_exams": true,
|
|
"allow_proctoring_opt_out": true,
|
|
"proctoring_provider": "mockprock",
|
|
"proctoring_escalation_email": null,
|
|
"create_zendesk_tickets": true
|
|
},
|
|
"available_proctoring_providers": [
|
|
"mockprock",
|
|
"proctortrack"
|
|
],
|
|
"course_start_date": "2013-02-05T05:00:00Z",
|
|
}
|
|
|
|
------------------------------------------------------------------------------------
|
|
POST
|
|
------------------------------------------------------------------------------------
|
|
|
|
**Returns**
|
|
|
|
* 200: OK - Proctored exam settings saved.
|
|
* 400: Bad Request - Unable to save requested settings.
|
|
* 401: The requesting user is not authenticated.
|
|
* 403: The requesting user lacks access to the course.
|
|
* 404: The requested course does not exist.
|
|
|
|
**Response**
|
|
|
|
In the case of a 200 response code, the response will echo the updated proctored
|
|
exam settings data.
|
|
"""
|
|
PROCTORED_EXAM_SETTINGS_KEYS = [
|
|
'enable_proctored_exams',
|
|
'allow_proctoring_opt_out',
|
|
'proctoring_provider',
|
|
'proctoring_escalation_email',
|
|
'create_zendesk_tickets',
|
|
]
|
|
|
|
def get(self, request, course_id):
|
|
""" GET handler """
|
|
with modulestore().bulk_operations(CourseKey.from_string(course_id)):
|
|
course_module = self._get_and_validate_course_access(request.user, course_id)
|
|
course_metadata = CourseMetadata().fetch_all(course_module)
|
|
proctored_exam_settings = self._get_proctored_exam_setting_values(course_metadata)
|
|
|
|
data = {}
|
|
|
|
data['proctored_exam_settings'] = proctored_exam_settings
|
|
data['available_proctoring_providers'] = get_available_providers()
|
|
data['course_start_date'] = course_metadata['start'].get('value')
|
|
|
|
serializer = ProctoredExamConfigurationSerializer(data)
|
|
|
|
return Response(serializer.data)
|
|
|
|
def post(self, request, course_id):
|
|
""" POST handler """
|
|
serializer = ProctoredExamSettingsSerializer if request.user.is_staff else LimitedProctoredExamSettingsSerializer
|
|
exam_config = serializer(data=request.data.get('proctored_exam_settings', {}))
|
|
valid_request = exam_config.is_valid()
|
|
if not request.user.is_staff and valid_request and ProctoredExamSettingsSerializer(data=request.data.get('proctored_exam_settings', {})).is_valid():
|
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
|
|
|
with modulestore().bulk_operations(CourseKey.from_string(course_id)):
|
|
course_module = self._get_and_validate_course_access(request.user, course_id)
|
|
course_metadata = CourseMetadata().fetch_all(course_module)
|
|
|
|
models_to_update = {}
|
|
for setting_key, value in exam_config.data.items():
|
|
model = course_metadata.get(setting_key)
|
|
if model:
|
|
models_to_update[setting_key] = copy.deepcopy(model)
|
|
models_to_update[setting_key]['value'] = value
|
|
|
|
# validate data formats and update the course module object
|
|
is_valid, errors, updated_data = CourseMetadata.validate_and_update_from_json(
|
|
course_module,
|
|
models_to_update,
|
|
user=request.user,
|
|
)
|
|
|
|
if not is_valid:
|
|
error_messages = [{error.get('key'): error.get('message')} for error in errors]
|
|
return Response(
|
|
{'detail': error_messages},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
# save to mongo
|
|
modulestore().update_item(course_module, request.user.id)
|
|
|
|
# merge updated settings with all existing settings.
|
|
# do this because fields that could not be modified are excluded from the result
|
|
course_metadata = {**course_metadata, **updated_data}
|
|
updated_setttings = self._get_proctored_exam_setting_values(course_metadata)
|
|
serializer = ProctoredExamSettingsSerializer(updated_setttings)
|
|
return Response({
|
|
'proctored_exam_settings': serializer.data
|
|
})
|
|
|
|
@classmethod
|
|
def _get_proctored_exam_setting_values(cls, course_metadata):
|
|
return {
|
|
setting_key: course_metadata[setting_key].get('value')
|
|
for setting_key in cls.PROCTORED_EXAM_SETTINGS_KEYS
|
|
}
|
|
|
|
@staticmethod
|
|
def _get_and_validate_course_access(user, course_id):
|
|
"""
|
|
Check if course_id exists and is accessible by the user.
|
|
|
|
Returns a course_module object
|
|
"""
|
|
course_key = CourseKey.from_string(course_id)
|
|
course_module = get_course_and_check_access(course_key, user)
|
|
|
|
if not course_module:
|
|
raise NotFound(
|
|
f'Course with course_id {course_id} does not exist.'
|
|
)
|
|
|
|
return course_module
|