add rest_api for contentstore app, including a proctored_exam_settings view
This commit is contained in:
0
cms/djangoapps/contentstore/rest_api/__init__.py
Normal file
0
cms/djangoapps/contentstore/rest_api/__init__.py
Normal file
12
cms/djangoapps/contentstore/rest_api/urls.py
Normal file
12
cms/djangoapps/contentstore/rest_api/urls.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Contentstore API URLs.
|
||||
"""
|
||||
|
||||
from django.urls import include, re_path
|
||||
from .v1 import urls as v1_urls
|
||||
|
||||
app_name = 'cms.djangoapps.contentstore'
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^v1/', include(v1_urls))
|
||||
]
|
||||
24
cms/djangoapps/contentstore/rest_api/v1/serializers.py
Normal file
24
cms/djangoapps/contentstore/rest_api/v1/serializers.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
API Serializers for Contentstore
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.lib.xmodule.xmodule.course_module import get_available_providers
|
||||
|
||||
|
||||
class ProctoredExamSettingsSerializer(serializers.Serializer):
|
||||
""" Serializer for proctored exam settings. """
|
||||
enable_proctored_exams = serializers.BooleanField()
|
||||
allow_proctoring_opt_out = serializers.BooleanField()
|
||||
proctoring_provider = serializers.CharField()
|
||||
proctoring_escalation_email = serializers.CharField()
|
||||
create_zendesk_tickets = serializers.BooleanField()
|
||||
|
||||
|
||||
class ProctoredExamConfigurationSerializer(serializers.Serializer):
|
||||
""" Serializer for various metadata associated with proctored exam settings. """
|
||||
proctored_exam_settings = ProctoredExamSettingsSerializer()
|
||||
available_proctoring_providers = serializers.ChoiceField(get_available_providers())
|
||||
course_start_date = serializers.DateTimeField()
|
||||
is_staff = serializers.BooleanField()
|
||||
107
cms/djangoapps/contentstore/rest_api/v1/tests/test_views.py
Normal file
107
cms/djangoapps/contentstore/rest_api/v1/tests/test_views.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Unit tests for Contentstore views.
|
||||
"""
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory, InstructorFactory
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
class ProctoringExamSettingsGetTests(SharedModuleStoreTestCase, APITestCase):
|
||||
""" Tests for proctored exam settings GETs """
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.course_key = CourseKey.from_string('course-v1:edX+ToyX+Toy_Course')
|
||||
cls.other_course_key = CourseKey.from_string('course-v1:edX+ToyX_Other_Course+Toy_Course')
|
||||
cls.course = cls.create_course_from_course_key(cls.course_key)
|
||||
cls.other_course = cls.create_course_from_course_key(cls.other_course_key)
|
||||
cls.password = 'password'
|
||||
cls.student = UserFactory.create(username='student', password=cls.password)
|
||||
cls.global_staff = GlobalStaffFactory(username='global-staff', password=cls.password)
|
||||
cls.course_instructor = InstructorFactory(
|
||||
username='instructor',
|
||||
password=cls.password,
|
||||
course_key=cls.course.id,
|
||||
)
|
||||
cls.other_course_instructor = InstructorFactory(
|
||||
username='other-course-instructor',
|
||||
password=cls.password,
|
||||
course_key=cls.other_course.id,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
self.client.logout()
|
||||
|
||||
@classmethod
|
||||
def create_course_from_course_key(cls, course_key):
|
||||
return CourseFactory.create(
|
||||
org=course_key.org,
|
||||
course=course_key.course,
|
||||
run=course_key.run
|
||||
)
|
||||
|
||||
def get_url(self, course_key):
|
||||
return reverse(
|
||||
'cms.djangoapps.contentstore:v1:proctored_exam_settings',
|
||||
kwargs={'course_id': course_key}
|
||||
)
|
||||
|
||||
def get_expected_response_data(self, course, user):
|
||||
return {
|
||||
'proctored_exam_settings': {
|
||||
'enable_proctored_exams': course.enable_proctored_exams,
|
||||
'allow_proctoring_opt_out': course.allow_proctoring_opt_out,
|
||||
'proctoring_provider': course.proctoring_provider,
|
||||
'proctoring_escalation_email': course.proctoring_escalation_email,
|
||||
'create_zendesk_tickets': course.create_zendesk_tickets,
|
||||
},
|
||||
'course_start_date': '2030-01-01T00:00:00Z',
|
||||
'available_proctoring_providers': ['null'],
|
||||
'is_staff': user.is_staff,
|
||||
}
|
||||
|
||||
def test_403_if_student(self):
|
||||
self.client.login(username=self.student.username, password=self.password)
|
||||
url = self.get_url(self.course.id)
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_403_if_instructor_in_another_course(self):
|
||||
self.client.login(
|
||||
username=self.other_course_instructor.username,
|
||||
password=self.password
|
||||
)
|
||||
url = self.get_url(self.course.id)
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_200_global_staff(self):
|
||||
self.client.login(username=self.global_staff.username, password=self.password)
|
||||
url = self.get_url(self.course.id)
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.data == self.get_expected_response_data(self.course, self.global_staff)
|
||||
|
||||
def test_200_course_instructor(self):
|
||||
self.client.login(username=self.course_instructor.username, password=self.password)
|
||||
url = self.get_url(self.course.id)
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.data == self.get_expected_response_data(self.course, self.course_instructor)
|
||||
|
||||
def test_404_no_course_module(self):
|
||||
course_id = 'course-v1:edX+ToyX_Nonexistent_Course+Toy_Course'
|
||||
self.client.login(username=self.global_staff, password=self.password)
|
||||
url = self.get_url(course_id)
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
assert response.data == 'Course with course_id {} does not exist.'.format(course_id)
|
||||
16
cms/djangoapps/contentstore/rest_api/v1/urls.py
Normal file
16
cms/djangoapps/contentstore/rest_api/v1/urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
""" Contenstore API v1 URLs. """
|
||||
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
from openedx.core.constants import COURSE_ID_PATTERN
|
||||
|
||||
app_name = 'v1'
|
||||
|
||||
urlpatterns = [
|
||||
re_path(
|
||||
r'^proctored_exam_settings/{}$'.format(COURSE_ID_PATTERN),
|
||||
views.proctored_exam_settings,
|
||||
name="proctored_exam_settings"
|
||||
),
|
||||
]
|
||||
102
cms/djangoapps/contentstore/rest_api/v1/views.py
Normal file
102
cms/djangoapps/contentstore/rest_api/v1/views.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"Contentstore Views"
|
||||
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.lib.xmodule.xmodule.course_module import get_available_providers
|
||||
from contentstore.views.course import get_course_and_check_access
|
||||
from models.settings.course_metadata import CourseMetadata
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from contentstore.rest_api.v1.serializers import ProctoredExamConfigurationSerializer
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@view_auth_classes()
|
||||
def proctored_exam_settings(request, course_id):
|
||||
"""
|
||||
A view for retrieving information about proctored exam settings for a course.
|
||||
|
||||
Path: ``/api/contentstore/v1/proctored_exam_settings/{course_id}``
|
||||
|
||||
Accepts: [GET]
|
||||
|
||||
------------------------------------------------------------------------------------
|
||||
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",
|
||||
"is_staff": true
|
||||
}
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
with modulestore().bulk_operations(course_key):
|
||||
if request.method == 'GET':
|
||||
course_module = get_course_and_check_access(course_key, request.user)
|
||||
|
||||
if not course_module:
|
||||
return Response(
|
||||
'Course with course_id {} does not exist.'.format(course_id),
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
course_metadata = CourseMetadata().fetch_all(course_module)
|
||||
data = {}
|
||||
|
||||
# specify only the advanced settings we want to return
|
||||
proctored_exam_settings_advanced_settings_keys = [
|
||||
'enable_proctored_exams',
|
||||
'allow_proctoring_opt_out',
|
||||
'proctoring_provider',
|
||||
'proctoring_escalation_email',
|
||||
'create_zendesk_tickets',
|
||||
'start'
|
||||
]
|
||||
proctored_exam_settings_data = {
|
||||
setting_key: setting_value.get('value')
|
||||
for (setting_key, setting_value) in course_metadata.items()
|
||||
if setting_key in proctored_exam_settings_advanced_settings_keys
|
||||
}
|
||||
|
||||
data['proctored_exam_settings'] = proctored_exam_settings_data
|
||||
data['available_proctoring_providers'] = get_available_providers()
|
||||
|
||||
# move start key:value out of proctored_exam_settings dictionary and change key
|
||||
data['course_start_date'] = proctored_exam_settings_data['start']
|
||||
del data['proctored_exam_settings']['start']
|
||||
|
||||
data['is_staff'] = request.user.is_staff
|
||||
|
||||
serializer = ProctoredExamConfigurationSerializer(data)
|
||||
|
||||
return Response(serializer.data)
|
||||
@@ -300,3 +300,8 @@ if 'openedx.testing.coverage_context_listener' in settings.INSTALLED_APPS:
|
||||
|
||||
from openedx.core.djangoapps.plugins import constants as plugin_constants, plugin_urls
|
||||
urlpatterns.extend(plugin_urls.get_patterns(plugin_constants.ProjectType.CMS))
|
||||
|
||||
# Contentstore
|
||||
urlpatterns += [
|
||||
url(r'^api/contentstore/', include('cms.djangoapps.contentstore.rest_api.urls'))
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user