feat!: Remove proctortrack references; add requires_escalation_email and show_review_rules options (#37576)

BREAKING CHANGE: All references to the hardcoded 'proctortrack' string have
been removed from the codebase,  as well as the `studio.show_review_rules`
waffle flag. These were used to determine whether an escalation email is
required and whether review rules should be shown. These decisions are now made
based on the value of 'requires_escalation_email' (default False) and 'show_review_rules'
(default True) config items in the PROCTORING_BACKENDS entry.

Additionally:
* The proctoring info api will now return the list of providers which require an escalation
   email so that frontend-app-learning does not need to use a hardcoded check agaist
   the provider name 'proctortrack'.
* Removed translation commands, mock variables and user facing strings that contained
  'proctortrack'.
* Updated all test cases that were using proctortrack to use fake providers names.

Part of: https://github.com/openedx/edx-platform/issues/36329
This commit is contained in:
Muhammad Labeeb
2025-11-25 19:37:32 +05:00
committed by GitHub
parent 7671c08573
commit 8ad4d42e3b
23 changed files with 235 additions and 112 deletions

View File

@@ -21,7 +21,12 @@ from common.djangoapps.student.tests.factories import UserFactory
@ddt.ddt
@override_settings(PROCTORING_BACKENDS={'DEFAULT': 'proctortrack', 'proctortrack': {}})
@override_settings(
PROCTORING_BACKENDS={
"DEFAULT": "test_proctoring_provider",
"test_proctoring_provider": {"requires_escalation_email": True},
}
)
class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase):
"""
Test course validation view via a RESTful API
@@ -33,7 +38,7 @@ class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase):
cls.course = CourseFactory.create(
display_name='test course',
run="Testing_course",
proctoring_provider='proctortrack',
proctoring_provider='test_proctoring_provider',
proctoring_escalation_email='test@example.com',
)
cls.course_key = cls.course.id

View File

@@ -8,6 +8,7 @@ from rest_framework.response import Response
from cms.djangoapps.contentstore.course_info_model import get_course_updates
from cms.djangoapps.contentstore.views.certificates import CertificateManager
from common.djangoapps.util.proctoring import requires_escalation_email
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
from xmodule.course_metadata_utils import DEFAULT_GRADING_POLICY # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
@@ -340,8 +341,8 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView):
return False
def _proctoring_validation(self, course):
# A proctoring escalation email is currently only required for courses using Proctortrack
# A proctoring escalation email is required if 'required_escalation_email' is set on the proctoring backend
return dict(
needs_proctoring_escalation_email=course.proctoring_provider == 'proctortrack',
needs_proctoring_escalation_email=requires_escalation_email(course.proctoring_provider),
has_proctoring_escalation_email=bool(course.proctoring_escalation_email)
)

View File

@@ -22,10 +22,6 @@ ENABLE_CHECKLISTS_QUALITY = CourseWaffleFlag( # lint-amnesty, pylint: disable=t
f'{WAFFLE_NAMESPACE}.enable_checklists_quality', __name__, LOG_PREFIX
)
SHOW_REVIEW_RULES_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation
f'{WAFFLE_NAMESPACE}.show_review_rules', __name__, LOG_PREFIX
)
# .. toggle_name: studio.custom_relative_dates
# .. toggle_implementation: CourseWaffleFlag

View File

@@ -5,7 +5,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
from xmodule.course_block import get_available_providers, get_requires_escalation_email_providers
class ProctoredExamSettingsSerializer(serializers.Serializer):
@@ -29,6 +29,9 @@ 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())
requires_escalation_email_providers = serializers.ChoiceField(
get_requires_escalation_email_providers()
)
course_start_date = serializers.DateTimeField()

View File

@@ -14,7 +14,10 @@ from cms.djangoapps.contentstore.views.course import get_course_and_check_access
from cms.djangoapps.contentstore.utils import get_proctored_exam_settings_url
from cms.djangoapps.models.settings.course_metadata import CourseMetadata
from common.djangoapps.student.auth import has_studio_advanced_settings_access
from xmodule.course_block import get_available_providers # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.course_block import (
get_available_providers,
get_requires_escalation_email_providers,
) # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.course_apps.toggles import exams_ida_enabled
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, verify_course_exists, view_auth_classes
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
@@ -49,7 +52,8 @@ class ProctoredExamSettingsView(APIView):
**Response**
In the case of a 200 response code, the response will proctored exam settings data
In the case of a 200 response code, the response will proctored exam settings data,
list of proctoring backends that have 'requires_escalation_email' set to 'True'
as well as other metadata about the course or the requesting user that are necessary
for rendering the settings page.
@@ -65,7 +69,10 @@ class ProctoredExamSettingsView(APIView):
},
"available_proctoring_providers": [
"mockprock",
"proctortrack"
"software_secure",
],
"requires_escalation_email_providers": [
"software_secure"
],
"course_start_date": "2013-02-05T05:00:00Z",
}
@@ -108,10 +115,15 @@ class ProctoredExamSettingsView(APIView):
data['course_start_date'] = course_metadata['start'].get('value')
available_providers = get_available_providers()
requires_escalation_email_providers = (
get_requires_escalation_email_providers()
)
if not exams_ida_enabled(CourseKey.from_string(course_id)):
available_providers.remove('lti_external')
requires_escalation_email_providers.remove('lti_external')
data['available_proctoring_providers'] = available_providers
data['requires_escalation_email_providers'] = requires_escalation_email_providers
serializer = ProctoredExamConfigurationSerializer(data)

View File

@@ -62,6 +62,7 @@ class ProctoringExamSettingsGetTests(
},
"course_start_date": "2030-01-01T00:00:00Z",
"available_proctoring_providers": ["null"],
"requires_escalation_email_providers": [],
}
def make_request(self, course_id=None, data=None):
@@ -100,6 +101,7 @@ class ProctoringExamSettingsGetTests(
},
"course_start_date": "2030-01-01T00:00:00Z",
"available_proctoring_providers": ["null"],
"requires_escalation_email_providers": [],
}
assert response.data == expected_data
@@ -122,6 +124,7 @@ class ProctoringExamSettingsGetTests(
},
"course_start_date": "2030-01-01T00:00:00Z",
"available_proctoring_providers": ["lti_external", "null"],
"requires_escalation_email_providers": ["lti_external"],
}
assert response.data == expected_data
@@ -162,14 +165,17 @@ class ProctoringExamSettingsPostTests(
return super().test_course_instructor(expect_status=expect_status)
@override_settings(
PROCTORING_BACKENDS={"DEFAULT": "proctortrack", "proctortrack": {}},
PROCTORING_BACKENDS={
"DEFAULT": "test_proctoring_provider",
"test_proctoring_provider": {"requires_escalation_email": True},
},
)
def test_update_exam_settings_200_escalation_email(self):
"""update exam settings for provider that requires an escalation email (proctortrack)"""
"""update exam settings for provider that requires an escalation email"""
self.client.login(username=self.global_staff.username, password=self.password)
data = self.get_request_data(
enable_proctored_exams=True,
proctoring_provider="proctortrack",
proctoring_provider="test_proctoring_provider",
proctoring_escalation_email="foo@bar.com",
)
response = self.make_request(data=data)
@@ -182,7 +188,7 @@ class ProctoringExamSettingsPostTests(
"proctored_exam_settings": {
"enable_proctored_exams": True,
"allow_proctoring_opt_out": True,
"proctoring_provider": "proctortrack",
"proctoring_provider": "test_proctoring_provider",
"proctoring_escalation_email": "foo@bar.com",
"create_zendesk_tickets": True,
}
@@ -192,7 +198,7 @@ class ProctoringExamSettingsPostTests(
# course settings have been updated
updated = modulestore().get_item(self.course.location)
assert updated.enable_proctored_exams is True
assert updated.proctoring_provider == "proctortrack"
assert updated.proctoring_provider == "test_proctoring_provider"
assert updated.proctoring_escalation_email == "foo@bar.com"
@override_settings(
@@ -299,14 +305,17 @@ class ProctoringExamSettingsPostTests(
assert response.status_code == status.HTTP_403_FORBIDDEN
@override_settings(
PROCTORING_BACKENDS={"DEFAULT": "proctortrack", "proctortrack": {}},
PROCTORING_BACKENDS={
"DEFAULT": "test_proctoring_provider",
"test_proctoring_provider": {"requires_escalation_email": True},
},
)
def test_200_for_instructor_request_compatibility(self):
self.client.login(username=self.course_instructor, password=self.password)
data = {
"proctored_exam_settings": {
"enable_proctored_exams": True,
"proctoring_provider": "proctortrack",
"proctoring_provider": "test_proctoring_provider",
"proctoring_escalation_email": "foo@bar.com",
}
}
@@ -315,16 +324,13 @@ class ProctoringExamSettingsPostTests(
@override_settings(
PROCTORING_BACKENDS={
"DEFAULT": "proctortrack",
"proctortrack": {},
"DEFAULT": "software_secure",
"software_secure": {},
},
)
@patch("logging.Logger.info")
@ddt.data(
("proctortrack", False, False),
("software_secure", True, False),
("proctortrack", True, True),
("software_secure", False, True),
)
@ddt.unpack

View File

@@ -1147,12 +1147,13 @@ class CourseMetadataEditingTest(CourseTestCase):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'proctortrack': {}
'test_proctoring_provider': {"requires_escalation_email": True},
},
)
def test_fetch_proctoring_escalation_email_present(self):
"""
If 'proctortrack' is an available provider, show the escalation email setting
If proctoring provider has 'requires_escalation_email' set to 'True',
show the escalation email setting
"""
test_model = CourseMetadata.fetch(self.fullcourse)
self.assertIn('proctoring_escalation_email', test_model)
@@ -1160,12 +1161,13 @@ class CourseMetadataEditingTest(CourseTestCase):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'alternate_provider': {}
'test_proctoring_provider': {}
},
)
def test_fetch_proctoring_escalation_email_not_present(self):
"""
If 'proctortrack' is not an available provider, don't show the escalation email setting
If proctoring provider does not have 'requires_escalation_email' set to 'True',
don't show the escalation email setting
"""
test_model = CourseMetadata.fetch(self.fullcourse)
self.assertNotIn('proctoring_escalation_email', test_model)
@@ -1526,13 +1528,12 @@ class CourseMetadataEditingTest(CourseTestCase):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {},
'proctortrack': {}
'test_proctoring_provider': {"requires_escalation_email": True},
},
)
def test_validate_update_requires_escalation_email_for_proctortrack(self, include_blank_email):
def test_validate_update_requires_escalation_email_if_relevant_flag_is_set(self, include_blank_email):
json_data = {
"proctoring_provider": {"value": 'proctortrack'},
"proctoring_provider": {"value": 'test_proctoring_provider'},
}
if include_blank_email:
json_data["proctoring_escalation_email"] = {"value": ""}
@@ -1549,14 +1550,13 @@ class CourseMetadataEditingTest(CourseTestCase):
self.assertIsNone(test_model)
self.assertEqual(
errors[0].get('message'),
'Provider \'proctortrack\' requires an exam escalation contact.'
'Provider \'test_proctoring_provider\' requires an exam escalation contact.'
)
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {},
'proctortrack': {}
}
)
def test_validate_update_does_not_require_escalation_email_by_default(self):
@@ -1573,14 +1573,14 @@ class CourseMetadataEditingTest(CourseTestCase):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'proctortrack',
'proctortrack': {}
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {"requires_escalation_email": True},
},
)
def test_validate_update_cannot_unset_escalation_email_when_proctortrack_is_provider(self):
def test_validate_update_cannot_unset_escalation_email_when_requires_escalation_email_set_on_provider(self):
course = CourseFactory.create()
CourseMetadata.update_from_dict(
{"proctoring_provider": 'proctortrack', "enable_proctored_exams": True},
{"proctoring_provider": 'test_proctoring_provider', "enable_proctored_exams": True},
course,
self.user
)
@@ -1596,20 +1596,20 @@ class CourseMetadataEditingTest(CourseTestCase):
self.assertIsNone(test_model)
self.assertEqual(
errors[0].get('message'),
'Provider \'proctortrack\' requires an exam escalation contact.'
'Provider \'test_proctoring_provider\' requires an exam escalation contact.'
)
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'proctortrack',
'proctortrack': {}
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {"requires_escalation_email": True},
}
)
def test_validate_update_set_proctortrack_provider_with_valid_escalation_email(self):
def test_validate_update_set_proctoring_provider_with_valid_escalation_email(self):
did_validate, errors, test_model = CourseMetadata.validate_and_update_from_json(
self.course,
{
"proctoring_provider": {"value": "proctortrack"},
"proctoring_provider": {"value": "test_proctoring_provider"},
"proctoring_escalation_email": {"value": "foo@bar.com"},
},
user=self.user
@@ -1621,16 +1621,20 @@ class CourseMetadataEditingTest(CourseTestCase):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'proctortrack',
'proctortrack': {}
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {"requires_escalation_email": True},
}
)
def test_validate_update_disable_proctoring_with_no_escalation_email(self):
course = CourseFactory.create()
CourseMetadata.update_from_dict(
{"proctoring_provider": 'proctortrack', "proctoring_escalation_email": '', "enable_proctored_exams": True},
{
"proctoring_provider": "test_proctoring_provider",
"proctoring_escalation_email": "",
"enable_proctored_exams": True,
},
course,
self.user
self.user,
)
did_validate, errors, test_model = CourseMetadata.validate_and_update_from_json(
course,
@@ -1645,15 +1649,15 @@ class CourseMetadataEditingTest(CourseTestCase):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'proctortrack',
'proctortrack': {}
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {"requires_escalation_email": True},
}
)
def test_validate_update_disable_proctoring_and_change_escalation_email(self):
did_validate, errors, test_model = CourseMetadata.validate_and_update_from_json(
self.course,
{
"proctoring_provider": {"value": "proctortrack"},
"proctoring_provider": {"value": "test_proctoring_provider"},
"proctoring_escalation_email": {"value": ""},
"enable_proctored_exams": {"value": False},
},
@@ -1667,14 +1671,14 @@ class CourseMetadataEditingTest(CourseTestCase):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'proctortrack',
'proctortrack': {}
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {"requires_escalation_email": True},
}
)
def test_validate_update_disabled_proctoring_and_unset_escalation_email(self):
course = CourseFactory.create()
CourseMetadata.update_from_dict(
{"proctoring_provider": 'proctortrack', "enable_proctored_exams": False},
{"proctoring_provider": 'test_proctoring_provider', "enable_proctored_exams": False},
course,
self.user
)

View File

@@ -12,6 +12,7 @@ from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
from django.test.utils import override_settings
from edx_toggles.toggles.testutils import override_waffle_flag
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
from openedx_events.content_authoring.data import DuplicatedXBlockData
@@ -3701,10 +3702,49 @@ class TestSpecialExamXBlockInfo(ItemTest):
assert xblock_info["proctoring_exam_configuration_link"] == "test_url"
assert xblock_info["supports_onboarding"] is True
assert xblock_info["is_onboarding_exam"] is False
assert xblock_info["show_review_rules"] is True
mock_get_exam_configuration_dashboard_url.assert_called_with(
self.course.id, xblock_info["id"]
)
@patch_get_exam_configuration_dashboard_url
@patch_does_backend_support_onboarding
@patch_get_exam_by_content_id_success
@override_settings(
PROCTORING_BACKENDS={
"DEFAULT": "null",
# By default "show_review_rules" is True unless you explicitly set it to False.
"test_proctoring_provider": {"show_review_rules": False},
}
)
def test_show_review_rules_xblock_info(
self,
mock_get_exam_by_content_id,
_mock_does_backend_support_onboarding,
mock_get_exam_configuration_dashboard_url,
):
# Set course.proctoring_provider to test_proctoring_provider
self.course.proctoring_provider = 'test_proctoring_provider'
sequential = BlockFactory.create(
parent_location=self.chapter.location,
category="sequential",
display_name="Test Lesson 1",
user_id=self.user.id,
is_proctored_enabled=True,
is_time_limited=True,
default_time_limit_minutes=100,
is_onboarding_exam=False,
)
sequential = modulestore().get_item(sequential.location)
xblock_info = create_xblock_info(
sequential,
include_child_info=True,
include_children_predicate=ALWAYS,
course=self.course,
)
assert xblock_info["show_review_rules"] is False
@patch_get_exam_configuration_dashboard_url
@patch_does_backend_support_onboarding
@patch_get_exam_by_content_id_success
@@ -3757,7 +3797,7 @@ class TestSpecialExamXBlockInfo(ItemTest):
(None, False),
)
@ddt.unpack
def test_xblock_was_ever_proctortrack_proctored_exam(
def test_xblock_was_ever_linked_to_external_exam(
self,
external_id,
expected_value,
@@ -3787,7 +3827,7 @@ class TestSpecialExamXBlockInfo(ItemTest):
@patch_get_exam_configuration_dashboard_url
@patch_does_backend_support_onboarding
@patch_get_exam_by_content_id_not_found
def test_xblock_was_never_proctortrack_proctored_exam(
def test_xblock_was_never_linked_to_external_exam(
self,
mock_get_exam_by_content_id,
_mock_does_backend_support_onboarding_patch,
@@ -3846,6 +3886,7 @@ class TestSpecialExamXBlockInfo(ItemTest):
assert xblock_info["proctoring_exam_configuration_link"] is None
assert xblock_info["supports_onboarding"] is True
assert xblock_info["is_onboarding_exam"] is False
assert xblock_info["show_review_rules"] is True
class TestLibraryXBlockInfo(ModuleStoreTestCase):

View File

@@ -87,7 +87,7 @@ class TestExamSettingsView(CourseTestCase, UrlResetMixin):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'proctortrack': {}
'test_proctoring_provider': {"requires_escalation_email": True}
},
)
@ddt.data(
@@ -98,9 +98,9 @@ class TestExamSettingsView(CourseTestCase, UrlResetMixin):
An alert should appear if current exam settings are invalid.
The exam settings alert should direct users to the exam settings page.
"""
# create an error by setting proctortrack as the provider and not setting
# create an error by setting 'requires_escalation_email' as 'True' and not setting
# the (required) escalation contact
self.course.proctoring_provider = 'proctortrack'
self.course.proctoring_provider = 'test_proctoring_provider'
self.course.enable_proctored_exams = True
self.save_course()
@@ -123,7 +123,7 @@ class TestExamSettingsView(CourseTestCase, UrlResetMixin):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'proctortrack': {}
'test_proctoring_provider': {"requires_escalation_email": True}
},
)
@ddt.data(
@@ -136,9 +136,9 @@ class TestExamSettingsView(CourseTestCase, UrlResetMixin):
The exam settings alert should direct users to the advanced settings page
if the exam settings feature is not available.
"""
# create an error by setting proctortrack as the provider and not setting
# create an error by setting 'requires_escalation_email' as 'True' and not setting
# the (required) escalation contact
self.course.proctoring_provider = 'proctortrack'
self.course.proctoring_provider = 'test_proctoring_provider'
self.course.enable_proctored_exams = True
self.save_course()
@@ -164,7 +164,6 @@ class TestExamSettingsView(CourseTestCase, UrlResetMixin):
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'proctortrack': {},
'test_proctoring_provider': {},
},
)

View File

@@ -35,7 +35,6 @@ from xblock.core import XBlock
from xblock.fields import Scope
from .xblock_helpers import get_block_key_string
from cms.djangoapps.contentstore.config.waffle import SHOW_REVIEW_RULES_FLAG
from cms.djangoapps.contentstore.helpers import StaticFileNotices
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
from cms.lib.ai_aside_summary_config import AiAsideSummaryConfig
@@ -49,6 +48,7 @@ from common.djangoapps.student.auth import (
)
from common.djangoapps.util.date_utils import get_default_time_display
from common.djangoapps.util.json_request import JsonResponse, expect_json
from common.djangoapps.util.proctoring import show_review_rules
from openedx.core.djangoapps.bookmarks import api as bookmarks_api
from openedx.core.djangoapps.content_tagging.toggles import is_tagging_feature_disabled
from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration
@@ -1302,13 +1302,6 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
f"Error while getting proctoring exam configuration link: {e}"
)
if course.proctoring_provider == "proctortrack":
show_review_rules = SHOW_REVIEW_RULES_FLAG.is_enabled(
xblock.location.course_key
)
else:
show_review_rules = True
xblock_info.update(
{
"is_proctored_exam": xblock.is_proctored_exam,
@@ -1323,7 +1316,7 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
"default_time_limit_minutes": xblock.default_time_limit_minutes,
"proctoring_exam_configuration_link": proctoring_exam_configuration_link,
"supports_onboarding": supports_onboarding,
"show_review_rules": show_review_rules,
"show_review_rules": show_review_rules(course.proctoring_provider),
}
)

View File

@@ -14,6 +14,7 @@ from xblock.fields import Scope
from cms.djangoapps.contentstore import toggles
from common.djangoapps.util.db import MYSQL_MAX_INT, generate_int_id
from common.djangoapps.util.proctoring import requires_escalation_email
from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag
from openedx.core.djangoapps.course_apps.toggles import exams_ida_enabled
from openedx.core.djangoapps.discussions.config.waffle_utils import legacy_discussion_experience_enabled
@@ -144,10 +145,15 @@ class CourseMetadata:
if not COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key=course_key):
exclude_list.append('course_visibility')
# 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:
exclude_list.append('proctoring_escalation_email')
# Do not show "Proctoring Exam Escalation Contact" if 'requires_escalation_email'
# is not set on any of the the proctoring backends.
escalation_email_required = False
for provider in get_available_providers():
if requires_escalation_email(provider):
escalation_email_required = True
break
if not escalation_email_required:
exclude_list.append("proctoring_escalation_email")
if not legacy_discussion_experience_enabled(course_key):
exclude_list.append('discussion_blackouts')
@@ -512,7 +518,7 @@ class CourseMetadata:
'model': proctoring_provider_model
})
# Require a valid escalation email if Proctortrack is chosen as the proctoring provider
# Require a valid escalation email if 'requires_escalation_email' is set in the proctoring backend
escalation_email_model = settings_dict.get('proctoring_escalation_email')
if escalation_email_model:
escalation_email = escalation_email_model.get('value')
@@ -520,7 +526,7 @@ class CourseMetadata:
escalation_email = block.proctoring_escalation_email
missing_escalation_email_msg = 'Provider \'{provider}\' requires an exam escalation contact.'
if proctoring_provider_model and proctoring_provider == 'proctortrack':
if proctoring_provider_model and requires_escalation_email(proctoring_provider):
if not escalation_email:
message = missing_escalation_email_msg.format(provider=proctoring_provider)
errors.append({
@@ -531,7 +537,7 @@ class CourseMetadata:
if (
escalation_email_model and not proctoring_provider_model and
proctoring_provider == 'proctortrack'
requires_escalation_email(proctoring_provider)
):
if not escalation_email:
message = missing_escalation_email_msg.format(provider=proctoring_provider)
@@ -548,10 +554,7 @@ class CourseMetadata:
else:
create_zendesk_tickets = block.create_zendesk_tickets
if (
(proctoring_provider == 'proctortrack' and create_zendesk_tickets)
or (proctoring_provider == 'software_secure' and not create_zendesk_tickets)
):
if 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 '

View File

@@ -638,12 +638,12 @@ PLATFORM_TWITTER_ACCOUNT: '@name'
POLICY_CHANGE_GRADES_ROUTING_KEY: edx.lms.core.grades_policy_change
PRESS_EMAIL: press@example.com
PROCTORING_BACKENDS:
DEFAULT: proctortrack
proctortrack:
base_url: https://testing.verificient.com
DEFAULT: mockprock
mockprock:
base_url: https://testing.mockprock.com
client_id: test_client_id
client_secret: test_client_secret
integration_specific_email: proctortrack-support@example.com
integration_specific_email: mockprock-support@example.com
PROCTORING_SETTINGS:
ALLOW_CALLBACK_SIMULATION: true
LINK_URLS: