fix: allow instructors to modify zendesk ticket setting (#27512)

MST-362. Previously instructors were unable to modify the zendesk ticket field, although that field should change based on the proctoring provider. This backend change allows course instructors to modify the field so that it is appropriate for whateverproctoring provider they choose.
This commit is contained in:
alangsto
2021-05-05 12:04:53 -04:00
committed by GitHub
parent 65977a9e02
commit 5bfc790014
4 changed files with 85 additions and 24 deletions

View File

@@ -21,6 +21,7 @@ class LimitedProctoredExamSettingsSerializer(serializers.Serializer):
enable_proctored_exams = serializers.BooleanField()
proctoring_provider = serializers.CharField()
proctoring_escalation_email = serializers.CharField(allow_blank=True)
create_zendesk_tickets = serializers.BooleanField(required=False)
class ProctoredExamConfigurationSerializer(serializers.Serializer):

View File

@@ -2,6 +2,8 @@
Unit tests for Contentstore views.
"""
import ddt
from mock import patch
from django.test.utils import override_settings
from django.urls import reverse
from opaque_keys.edx.keys import CourseKey
@@ -116,6 +118,7 @@ class ProctoringExamSettingsGetTests(ProctoringExamSettingsTestMixin, ModuleStor
assert response.data == self.get_expected_response_data(self.course, self.course_instructor)
@ddt.ddt
class ProctoringExamSettingsPostTests(ProctoringExamSettingsTestMixin, ModuleStoreTestCase, APITestCase):
""" Tests for proctored exam settings POST """
@@ -267,7 +270,7 @@ class ProctoringExamSettingsPostTests(ProctoringExamSettingsTestMixin, ModuleSto
assert updated.enable_proctored_exams is False
assert updated.proctoring_provider == 'null'
def test_403_if_instructor_request_includes_opting_out_or_zendesk(self):
def test_403_if_instructor_request_includes_opting_out(self):
self.client.login(username=self.course_instructor, password=self.password)
data = self.get_request_data()
response = self.make_request(data=data)
@@ -279,7 +282,7 @@ class ProctoringExamSettingsPostTests(ProctoringExamSettingsTestMixin, ModuleSto
'proctortrack': {}
},
)
def test_200_for_instructor_request(self):
def test_200_for_instructor_request_compatibility(self):
self.client.login(username=self.course_instructor, password=self.password)
data = {
'proctored_exam_settings': {
@@ -290,3 +293,45 @@ class ProctoringExamSettingsPostTests(ProctoringExamSettingsTestMixin, ModuleSto
}
response = self.make_request(data=data)
assert response.status_code == status.HTTP_200_OK
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'null',
'proctortrack': {},
'software_secure': {},
},
)
@patch('logging.Logger.info')
@ddt.data(
('proctortrack', False, False),
('software_secure', True, False),
('proctortrack', True, True),
('software_secure', False, True),
)
@ddt.unpack
def test_nonadmin_with_zendesk_ticket(self, proctoring_provider, create_zendesk_tickets, expect_log, logger_mock):
self.client.login(username=self.course_instructor, password=self.password)
data = {
'proctored_exam_settings': {
'enable_proctored_exams': True,
'proctoring_provider': proctoring_provider,
'proctoring_escalation_email': 'foo@bar.com',
'create_zendesk_tickets': create_zendesk_tickets,
}
}
response = self.make_request(data=data)
assert response.status_code == status.HTTP_200_OK
if expect_log:
logger_string = (
'create_zendesk_tickets set to {ticket_value} but proctoring '
'provider is {provider} for course {course_id}. create_zendesk_tickets '
'should be updated for this course.'.format(
ticket_value=create_zendesk_tickets,
provider=proctoring_provider,
course_id=self.course.id
)
)
logger_mock.assert_any_call(logger_string)
updated = modulestore().get_item(self.course.location)
assert updated.create_zendesk_tickets is create_zendesk_tickets

View File

@@ -1549,14 +1549,7 @@ class CourseMetadataEditingTest(CourseTestCase):
)
self.assertIn(field_name, test_model)
def test_create_zendesk_tickets_not_present_for_course_staff(self):
"""
Tests that create zendesk tickets field is filtered out when the user is not an edX staff member.
"""
test_model = CourseMetadata.fetch(self.fullcourse)
self.assertNotIn('create_zendesk_tickets', test_model)
def test_validate_update_does_filter_out_create_zendesk_tickets_for_course_staff(self):
def test_validate_update_does_not_filter_out_create_zendesk_tickets_for_course_staff(self):
"""
Tests that create zendesk tickets field is not returned by validate_and_update_from_json method when
the user is not an edX staff member.
@@ -1570,9 +1563,9 @@ class CourseMetadataEditingTest(CourseTestCase):
},
user=self.user
)
self.assertNotIn(field_name, test_model)
self.assertIn(field_name, test_model)
def test_update_from_json_does_filter_out_create_zendesk_tickets_for_course_staff(self):
def test_update_from_json_does_not_filter_out_create_zendesk_tickets_for_course_staff(self):
"""
Tests that create zendesk tickets field is not returned by update_from_json method when
the user is not an edX staff member.
@@ -1586,7 +1579,7 @@ class CourseMetadataEditingTest(CourseTestCase):
},
user=self.user
)
self.assertNotIn(field_name, test_model)
self.assertIn(field_name, test_model)
def _set_request_user_to_staff(self):
"""

View File

@@ -4,22 +4,23 @@ Django module for Course Metadata class -- manages advanced settings and related
from datetime import datetime
import logging
import pytz
from crum import get_current_user
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from xblock.fields import Scope
from cms.djangoapps.contentstore import toggles
from common.djangoapps.student.roles import GlobalStaff
from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag
from openedx.core.lib.teams_config import TeamsetType
from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import InvalidProctoringProvider
LOGGER = logging.getLogger(__name__)
class CourseMetadata:
'''
@@ -135,11 +136,6 @@ class CourseMetadata:
if not COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key=course_key):
exclude_list.append('course_visibility')
# Do not show "Create Zendesk Tickets For Suspicious Proctored Exam Attempts" in
# Studio Advanced Settings if the user is not edX staff.
if not GlobalStaff().has_user(get_current_user()):
exclude_list.append('create_zendesk_tickets')
# Do not show "Proctortrack Exam Escalation Contact" if Proctortrack is not
# an available proctoring backend.
if not settings.PROCTORING_BACKENDS or settings.PROCTORING_BACKENDS.get('proctortrack') is None:
@@ -414,10 +410,15 @@ class CourseMetadata:
else:
escalation_email = descriptor.proctoring_escalation_email
if proctoring_provider_model:
proctoring_provider = proctoring_provider_model.get('value')
else:
proctoring_provider = descriptor.proctoring_provider
missing_escalation_email_msg = 'Provider \'{provider}\' requires an exam escalation contact.'
if proctoring_provider_model and proctoring_provider_model.get('value') == 'proctortrack':
if proctoring_provider_model and proctoring_provider == 'proctortrack':
if not escalation_email:
message = missing_escalation_email_msg.format(provider=proctoring_provider_model.get('value'))
message = missing_escalation_email_msg.format(provider=proctoring_provider)
errors.append({
'key': 'proctoring_provider',
'message': message,
@@ -426,16 +427,37 @@ class CourseMetadata:
if (
escalation_email_model and not proctoring_provider_model and
descriptor.proctoring_provider == 'proctortrack'
proctoring_provider == 'proctortrack'
):
if not escalation_email:
message = missing_escalation_email_msg.format(provider=descriptor.proctoring_provider)
message = missing_escalation_email_msg.format(provider=proctoring_provider)
errors.append({
'key': 'proctoring_escalation_email',
'message': message,
'model': escalation_email_model
})
# Check that Zendesk field is appropriate for the provider
zendesk_ticket_model = settings_dict.get('create_zendesk_tickets')
if zendesk_ticket_model:
create_zendesk_tickets = zendesk_ticket_model.get('value')
else:
create_zendesk_tickets = descriptor.create_zendesk_tickets
if (
(proctoring_provider == 'proctortrack' and create_zendesk_tickets)
or (proctoring_provider == 'software_secure' and not create_zendesk_tickets)
):
LOGGER.info(
'create_zendesk_tickets set to {ticket_value} but proctoring '
'provider is {provider} for course {course_id}. create_zendesk_tickets '
'should be updated for this course.'.format(
ticket_value=create_zendesk_tickets,
provider=proctoring_provider,
course_id=descriptor.id
)
)
return errors
@staticmethod