Merge pull request #19366 from edx/dahlia/proctoring-master

Upgrade edx-proctoring to version 1.5
This commit is contained in:
Dave St.Germain
2019-01-02 11:37:55 -05:00
committed by GitHub
42 changed files with 1139 additions and 677 deletions

View File

@@ -2,7 +2,7 @@
This module contains various configuration settings via
waffle switches for the contentstore app.
"""
from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, WaffleSwitchNamespace
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace
# Namespace
WAFFLE_NAMESPACE = u'studio'
@@ -23,3 +23,10 @@ def waffle_flags():
Returns the namespaced, cached, audited Waffle Flag class for Studio pages.
"""
return WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Studio: ')
# Flags
ENABLE_PROCTORING_PROVIDER_OVERRIDES = CourseWaffleFlag(
waffle_namespace=waffle_flags(),
flag_name=u'enable_proctoring_provider_overrides',
flag_undefined_default=False
)

View File

@@ -30,7 +30,6 @@ def register_special_exams(course_key):
subsystem. Likewise, if formerly registered exams are unmarked, then those
registered exams are marked as inactive
"""
if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
# if feature is not enabled then do a quick exit
return
@@ -72,52 +71,47 @@ def register_special_exams(course_key):
)
log.info(msg)
exam_metadata = {
'exam_name': timed_exam.display_name,
'time_limit_mins': timed_exam.default_time_limit_minutes,
'due_date': timed_exam.due,
'is_proctored': timed_exam.is_proctored_exam,
'is_practice_exam': timed_exam.is_practice_exam,
'is_active': True,
'hide_after_due': timed_exam.hide_after_due,
'backend': course.proctoring_provider,
}
try:
exam = get_exam_by_content_id(unicode(course_key), unicode(timed_exam.location))
# update case, make sure everything is synced
exam_id = update_exam(
exam_id=exam['id'],
exam_name=timed_exam.display_name,
time_limit_mins=timed_exam.default_time_limit_minutes,
due_date=timed_exam.due,
is_proctored=timed_exam.is_proctored_exam,
is_practice_exam=timed_exam.is_practice_exam,
is_active=True,
hide_after_due=timed_exam.hide_after_due,
)
exam_metadata['exam_id'] = exam['id']
exam_id = update_exam(**exam_metadata)
msg = 'Updated timed exam {exam_id}'.format(exam_id=exam['id'])
log.info(msg)
except ProctoredExamNotFoundException:
exam_id = create_exam(
course_id=unicode(course_key),
content_id=unicode(timed_exam.location),
exam_name=timed_exam.display_name,
time_limit_mins=timed_exam.default_time_limit_minutes,
due_date=timed_exam.due,
is_proctored=timed_exam.is_proctored_exam,
is_practice_exam=timed_exam.is_practice_exam,
is_active=True,
hide_after_due=timed_exam.hide_after_due,
)
exam_metadata['course_id'] = unicode(course_key)
exam_metadata['content_id'] = unicode(timed_exam.location)
exam_id = create_exam(**exam_metadata)
msg = 'Created new timed exam {exam_id}'.format(exam_id=exam_id)
log.info(msg)
exam_review_policy_metadata = {
'exam_id': exam_id,
'set_by_user_id': timed_exam.edited_by,
'review_policy': timed_exam.exam_review_rules,
}
# only create/update exam policy for the proctored exams
if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam:
try:
update_review_policy(
exam_id=exam_id,
set_by_user_id=timed_exam.edited_by,
review_policy=timed_exam.exam_review_rules
)
update_review_policy(**exam_review_policy_metadata)
except ProctoredExamReviewPolicyNotFoundException:
if timed_exam.exam_review_rules: # won't save an empty rule.
create_exam_review_policy(
exam_id=exam_id,
set_by_user_id=timed_exam.edited_by,
review_policy=timed_exam.exam_review_rules
)
create_exam_review_policy(**exam_review_policy_metadata)
msg = 'Created new exam review policy with exam_id {exam_id}'.format(exam_id=exam_id)
log.info(msg)
else:

View File

@@ -7,19 +7,24 @@ import json
import unittest
import ddt
import mock
from django.conf import settings
from django.test import RequestFactory
from django.test.utils import override_settings
from pytz import UTC
from milestones.tests.utils import MilestonesTestCaseMixin
import mock
from mock import Mock, patch
from crum import set_current_request
from milestones.tests.utils import MilestonesTestCaseMixin
from contentstore.utils import reverse_course_url, reverse_usage_url
from contentstore.config.waffle import ENABLE_PROCTORING_PROVIDER_OVERRIDES
from milestones.models import MilestoneRelationshipType
from models.settings.course_grading import CourseGradingModel, GRADING_POLICY_CHANGED_EVENT_TYPE, hash_grading_policy
from models.settings.course_metadata import CourseMetadata
from models.settings.encoder import CourseSettingsEncoder
from openedx.core.djangoapps.models.course_details import CourseDetails
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from student.roles import CourseInstructorRole, CourseStaffRole
from student.tests.factories import UserFactory
from util import milestones_helpers
@@ -790,12 +795,18 @@ class CourseMetadataEditingTest(CourseTestCase):
shard = 1
def setUp(self):
CourseTestCase.setUp(self)
super(CourseMetadataEditingTest, self).setUp()
self.fullcourse = CourseFactory.create()
self.course_setting_url = get_url(self.course.id, 'advanced_settings_handler')
self.fullcourse_setting_url = get_url(self.fullcourse.id, 'advanced_settings_handler')
self.notes_tab = {"type": "notes", "name": "My Notes"}
self.request = RequestFactory().request()
self.user = UserFactory()
self.request.user = self.user
set_current_request(self.request)
self.addCleanup(set_current_request, None)
def test_fetch_initial_fields(self):
test_model = CourseMetadata.fetch(self.course)
self.assertIn('display_name', test_model, 'Missing editable metadata field')
@@ -1189,6 +1200,193 @@ class CourseMetadataEditingTest(CourseTestCase):
})
self.assertEqual(response.status_code, 200)
@override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, True)
def test_proctoring_provider_present_when_waffle_flag_enabled(self):
"""
Tests that proctoring provider field is not filtered out when the waffle flag is enabled.
"""
test_model = CourseMetadata.fetch(self.fullcourse)
self.assertIn('proctoring_provider', test_model)
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {}
}
)
@override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, True)
def test_validate_update_does_not_filter_out_proctoring_provider_when_waffle_flag_enabled(self):
"""
Tests that proctoring provider field is returned by validate_and_update_from_json method when
waffle flag is enabled.
"""
field_name = "proctoring_provider"
_, _, test_model = CourseMetadata.validate_and_update_from_json(
self.course,
{
field_name: {"value": 'test_proctoring_provider'},
},
user=self.user
)
self.assertIn(field_name, test_model)
@override_settings(
PROCTORING_BACKENDS={
'DEFAULT': 'test_proctoring_provider',
'test_proctoring_provider': {}
}
)
@override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, True)
def test_update_from_json_does_not_filter_out_proctoring_provider_when_waffle_flag_enabled(self):
"""
Tests that proctoring provider field is returned by update_from_json method when
waffle flag is enabled.
"""
field_name = "proctoring_provider"
test_model = CourseMetadata.update_from_json(
self.course,
{
field_name: {"value": 'test_proctoring_provider'},
},
user=self.user
)
self.assertIn(field_name, test_model)
@override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, False)
def test_proctoring_provider_not_present_when_waffle_flag_not_enabled(self):
"""
Tests that proctoring provider field is filtered out when the waffle flag is not enabled.
"""
test_model = CourseMetadata.fetch(self.fullcourse)
self.assertNotIn('proctoring_provider', test_model)
@override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, False)
def test_validate_update_does_filter_out_proctoring_provider_when_waffle_flag_not_enabled(self):
"""
Tests that proctoring provider field is not returned by validate_and_update_from_json method when
waffle flag is not enabled.
"""
field_name = "proctoring_provider"
_, _, test_model = CourseMetadata.validate_and_update_from_json(
self.course,
{
field_name: {"value": 'test_proctoring_provider'},
},
user=self.user
)
self.assertNotIn(field_name, test_model)
@override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, False)
def test_update_from_json_does_filter_out_proctoring_provider_when_waffle_flag_not_enabled(self):
"""
Tests that proctoring provider field is not returned by update_from_json method when
waffle flag is not enabled.
"""
field_name = "proctoring_provider"
test_model = CourseMetadata.update_from_json(
self.course,
{
field_name: {"value": 'test_proctoring_provider'},
},
user=self.user
)
self.assertNotIn(field_name, test_model)
def test_create_zendesk_tickets_present_for_edx_staff(self):
"""
Tests that create zendesk tickets field is not filtered out when the user is an edX staff member.
"""
self._set_request_user_to_staff()
test_model = CourseMetadata.fetch(self.fullcourse)
self.assertIn('create_zendesk_tickets', test_model)
def test_validate_update_does_not_filter_out_create_zendesk_tickets_for_edx_staff(self):
"""
Tests that create zendesk tickets field is returned by validate_and_update_from_json method when
the user is an edX staff member.
"""
self._set_request_user_to_staff()
field_name = "create_zendesk_tickets"
_, _, test_model = CourseMetadata.validate_and_update_from_json(
self.course,
{
field_name: {"value": True},
},
user=self.user
)
self.assertIn(field_name, test_model)
def test_update_from_json_does_not_filter_out_create_zendesk_tickets_for_edx_staff(self):
"""
Tests that create zendesk tickets field is returned by update_from_json method when
the user is an edX staff member.
"""
self._set_request_user_to_staff()
field_name = "create_zendesk_tickets"
test_model = CourseMetadata.update_from_json(
self.course,
{
field_name: {"value": True},
},
user=self.user
)
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):
"""
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.
"""
field_name = "create_zendesk_tickets"
_, _, test_model = CourseMetadata.validate_and_update_from_json(
self.course,
{
field_name: {"value": True},
},
user=self.user
)
self.assertNotIn(field_name, test_model)
def test_update_from_json_does_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.
"""
field_name = "create_zendesk_tickets"
test_model = CourseMetadata.update_from_json(
self.course,
{
field_name: {"value": True},
},
user=self.user
)
self.assertNotIn(field_name, test_model)
def _set_request_user_to_staff(self):
"""
Update the current request's user to be an edX staff member.
"""
self.user.is_staff = True
self.request.user = self.user
set_current_request(self.request)
class CourseGraderUpdatesTest(CourseTestCase):
"""

View File

@@ -10,6 +10,7 @@ from mock import patch
from pytz import UTC
from contentstore.signals.handlers import listen_for_course_publish
from django.conf import settings
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -31,7 +32,8 @@ class TestProctoredExams(ModuleStoreTestCase):
org='edX',
course='900',
run='test_run',
enable_proctored_exams=True
enable_proctored_exams=True,
proctoring_provider=settings.PROCTORING_BACKENDS['DEFAULT'],
)
def _verify_exam_data(self, sequence, expected_active):
@@ -61,20 +63,22 @@ class TestProctoredExams(ModuleStoreTestCase):
self.assertEqual(exam['is_proctored'], sequence.is_proctored_exam)
self.assertEqual(exam['is_practice_exam'], sequence.is_practice_exam)
self.assertEqual(exam['is_active'], expected_active)
self.assertEqual(exam['backend'], self.course.proctoring_provider)
@ddt.data(
(True, 10, True, False, True, False, False),
(True, 10, False, False, True, False, False),
(True, 10, False, False, True, False, True),
(True, 10, True, True, True, True, False),
(True, False, True, False, False),
(False, False, True, False, False),
(False, False, True, False, True),
(True, True, True, True, False),
)
@ddt.unpack
def test_publishing_exam(self, is_time_limited, default_time_limit_minutes, is_proctored_exam,
def test_publishing_exam(self, is_proctored_exam,
is_practice_exam, expected_active, republish, hide_after_due):
"""
Happy path testing to see that when a course is published which contains
a proctored exam, it will also put an entry into the exam tables
"""
default_time_limit_minutes = 10
chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
sequence = ItemFactory.create(
@@ -82,7 +86,7 @@ class TestProctoredExams(ModuleStoreTestCase):
category='sequential',
display_name='Test Proctored Exam',
graded=True,
is_time_limited=is_time_limited,
is_time_limited=True,
default_time_limit_minutes=default_time_limit_minutes,
is_proctored_exam=is_proctored_exam,
is_practice_exam=is_practice_exam,
@@ -104,14 +108,13 @@ class TestProctoredExams(ModuleStoreTestCase):
listen_for_course_publish(self, self.course.id)
# reverify
self._verify_exam_data(sequence, expected_active)
self._verify_exam_data(sequence, expected_active,)
def test_unpublishing_proctored_exam(self):
"""
Make sure that if we publish and then unpublish a proctored exam,
the exam record stays, but is marked as is_active=False
"""
chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
sequence = ItemFactory.create(
parent=chapter,
@@ -178,7 +181,6 @@ class TestProctoredExams(ModuleStoreTestCase):
"""
Make sure the feature flag is honored
"""
chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
ItemFactory.create(
parent=chapter,

View File

@@ -2,7 +2,6 @@
Test module for Entrance Exams AJAX callback handler workflows
"""
import json
from django.conf import settings
from django.contrib.auth.models import User
from django.test.client import RequestFactory
@@ -229,13 +228,14 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
{'entrance_exam_minimum_score_pct': '50'},
http_accept='application/json'
)
self.assertEqual(resp.status_code, 201)
resp = self.client.get(self.exam_url)
self.assertEqual(resp.status_code, 200)
self.course = modulestore().get_course(self.course.id)
# Should raise an ItemNotFoundError and return a 404
updated_metadata = {'entrance_exam_id': 'i4x://org.4/course_4/chapter/ed7c4c6a4d68409998e2c8554c4629d1'}
CourseMetadata.update_from_dict(
updated_metadata,
self.course,
@@ -247,6 +247,7 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
# Should raise an InvalidKeyError and return a 404
updated_metadata = {'entrance_exam_id': '123afsdfsad90f87'}
CourseMetadata.update_from_dict(
updated_metadata,
self.course,

View File

@@ -5,11 +5,14 @@ from django.conf import settings
from django.utils.translation import ugettext as _
from six import text_type
from xblock.fields import Scope
from crum import get_current_user
from xblock_django.models import XBlockStudioConfigurationFlag
from xmodule.modulestore.django import modulestore
from student.roles import GlobalStaff
from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG
from cms.djangoapps.contentstore.config.waffle import ENABLE_PROCTORING_PROVIDER_OVERRIDES
class CourseMetadata(object):
@@ -20,9 +23,9 @@ class CourseMetadata(object):
editable metadata.
'''
# The list of fields that wouldn't be shown in Advanced Settings.
# Should not be used directly. Instead the filtered_list method should
# Should not be used directly. Instead the get_blacklist_of_fields method should
# be used if the field needs to be filtered depending on the feature flag.
FILTERED_LIST = [
FIELDS_BLACK_LIST = [
'cohort_config',
'xml_attributes',
'start',
@@ -65,65 +68,77 @@ class CourseMetadata(object):
]
@classmethod
def filtered_list(cls, course_key=None):
def get_blacklist_of_fields(cls, course_key):
"""
Filter fields based on feature flag, i.e. enabled, disabled.
Returns a list of fields to not include in Studio Advanced settings based on a
feature flag (i.e. enabled or disabled).
"""
# Copy the filtered list to avoid permanently changing the class attribute.
filtered_list = list(cls.FILTERED_LIST)
black_list = list(cls.FIELDS_BLACK_LIST)
# Do not show giturl if feature is not enabled.
if not settings.FEATURES.get('ENABLE_EXPORT_GIT'):
filtered_list.append('giturl')
black_list.append('giturl')
# Do not show edxnotes if the feature is disabled.
if not settings.FEATURES.get('ENABLE_EDXNOTES'):
filtered_list.append('edxnotes')
black_list.append('edxnotes')
# Do not show video auto advance if the feature is disabled
if not settings.FEATURES.get('ENABLE_OTHER_COURSE_SETTINGS'):
filtered_list.append('other_course_settings')
black_list.append('other_course_settings')
# Do not show video_upload_pipeline if the feature is disabled.
if not settings.FEATURES.get('ENABLE_VIDEO_UPLOAD_PIPELINE'):
filtered_list.append('video_upload_pipeline')
black_list.append('video_upload_pipeline')
# Do not show video auto advance if the feature is disabled
if not settings.FEATURES.get('ENABLE_AUTOADVANCE_VIDEOS'):
filtered_list.append('video_auto_advance')
black_list.append('video_auto_advance')
# Do not show social sharing url field if the feature is disabled.
if (not hasattr(settings, 'SOCIAL_SHARING_SETTINGS') or
not getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}).get("CUSTOM_COURSE_URLS")):
filtered_list.append('social_sharing_url')
black_list.append('social_sharing_url')
# Do not show teams configuration if feature is disabled.
if not settings.FEATURES.get('ENABLE_TEAMS'):
filtered_list.append('teams_configuration')
black_list.append('teams_configuration')
if not settings.FEATURES.get('ENABLE_VIDEO_BUMPER'):
filtered_list.append('video_bumper')
black_list.append('video_bumper')
# Do not show enable_ccx if feature is not enabled.
if not settings.FEATURES.get('CUSTOM_COURSES_EDX'):
filtered_list.append('enable_ccx')
filtered_list.append('ccx_connector')
black_list.append('enable_ccx')
black_list.append('ccx_connector')
# Do not show "Issue Open Badges" in Studio Advanced Settings
# if the feature is disabled.
if not settings.FEATURES.get('ENABLE_OPENBADGES'):
filtered_list.append('issue_badges')
black_list.append('issue_badges')
# If the XBlockStudioConfiguration table is not being used, there is no need to
# display the "Allow Unsupported XBlocks" setting.
if not XBlockStudioConfigurationFlag.is_enabled():
filtered_list.append('allow_unsupported_xblocks')
black_list.append('allow_unsupported_xblocks')
# If the ENABLE_PROCTORING_PROVIDER_OVERRIDES waffle flag is not enabled,
# do not show "Proctoring Configuration" in Studio Advanced Settings.
if not ENABLE_PROCTORING_PROVIDER_OVERRIDES.is_enabled(course_key):
black_list.append('proctoring_provider')
# Do not show "Course Visibility For Unenrolled Learners" in Studio Advanced Settings
# if the enable_anonymous_access flag is not enabled
if not COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key=course_key):
filtered_list.append('course_visibility')
return filtered_list
black_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()):
black_list.append('create_zendesk_tickets')
return black_list
@classmethod
def fetch(cls, descriptor):
@@ -133,8 +148,10 @@ class CourseMetadata(object):
"""
result = {}
metadata = cls.fetch_all(descriptor)
black_list_of_fields = cls.get_blacklist_of_fields(descriptor.id)
for key, value in metadata.iteritems():
if key in cls.filtered_list(descriptor.id):
if key in black_list_of_fields:
continue
result[key] = value
return result
@@ -169,17 +186,17 @@ class CourseMetadata(object):
Ensures none of the fields are in the blacklist.
"""
filtered_list = cls.filtered_list(descriptor.id)
blacklist_of_fields = cls.get_blacklist_of_fields(descriptor.id)
# Don't filter on the tab attribute if filter_tabs is False.
if not filter_tabs:
filtered_list.remove("tabs")
blacklist_of_fields.remove("tabs")
# Validate the values before actually setting them.
key_values = {}
for key, model in jsondict.iteritems():
# should it be an error if one of the filtered list items is in the payload?
if key in filtered_list:
if key in blacklist_of_fields:
continue
try:
val = model['value']
@@ -205,11 +222,12 @@ class CourseMetadata(object):
errors: list of error objects
result: the updated course metadata or None if error
"""
filtered_list = cls.filtered_list(descriptor.id)
if not filter_tabs:
filtered_list.remove("tabs")
blacklist_of_fields = cls.get_blacklist_of_fields(descriptor.id)
filtered_dict = dict((k, v) for k, v in jsondict.iteritems() if k not in filtered_list)
if not filter_tabs:
blacklist_of_fields.remove("tabs")
filtered_dict = dict((k, v) for k, v in jsondict.iteritems() if k not in blacklist_of_fields)
did_validate = True
errors = []
key_values = {}
@@ -238,7 +256,7 @@ class CourseMetadata(object):
for key, value in key_values.iteritems():
setattr(descriptor, key, value)
if save and len(key_values):
if save and key_values:
modulestore().update_item(descriptor, user.id)
return cls.fetch(descriptor)

View File

@@ -490,11 +490,6 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
################# PROCTORING CONFIGURATION ##################
PROCTORING_BACKEND_PROVIDER = AUTH_TOKENS.get("PROCTORING_BACKEND_PROVIDER", PROCTORING_BACKEND_PROVIDER)
PROCTORING_SETTINGS = ENV_TOKENS.get("PROCTORING_SETTINGS", PROCTORING_SETTINGS)
################# MICROSITE ####################
# microsite specific configurations.
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})

View File

@@ -881,6 +881,10 @@ WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-stats.json')
},
'WORKERS': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-worker-stats.json')
}
}
WEBPACK_CONFIG_PATH = 'webpack.prod.config.js'
@@ -1093,9 +1097,6 @@ INSTALLED_APPS = [
'xblock_django',
# edX Proctoring
'edx_proctoring',
# Catalog integration
'openedx.core.djangoapps.catalog',
@@ -1445,12 +1446,6 @@ MICROSITE_TEMPLATE_BACKEND = 'microsite_configuration.backends.filebased.Filebas
# TTL for microsite database template cache
MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = 5 * 60
############################### PROCTORING CONFIGURATION DEFAULTS ##############
PROCTORING_BACKEND_PROVIDER = {
'class': 'edx_proctoring.backends.null.NullBackendProvider',
'options': {},
}
PROCTORING_SETTINGS = {}
############################ Global Database Configuration #####################

View File

@@ -128,6 +128,7 @@ STATIC_ROOT_BASE = ENV_TOKENS.get('STATIC_ROOT_BASE', None)
if STATIC_ROOT_BASE:
STATIC_ROOT = path(STATIC_ROOT_BASE) / 'studio'
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
WEBPACK_LOADER['WORKERS']['STATS_FILE'] = STATIC_ROOT / "webpack-worker-stats.json"
EMAIL_BACKEND = ENV_TOKENS.get('EMAIL_BACKEND', EMAIL_BACKEND)
EMAIL_FILE_PATH = ENV_TOKENS.get('EMAIL_FILE_PATH', None)
@@ -493,11 +494,6 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
################# PROCTORING CONFIGURATION ##################
PROCTORING_BACKEND_PROVIDER = AUTH_TOKENS.get("PROCTORING_BACKEND_PROVIDER", PROCTORING_BACKEND_PROVIDER)
PROCTORING_SETTINGS = ENV_TOKENS.get("PROCTORING_SETTINGS", PROCTORING_SETTINGS)
################# MICROSITE ####################
# microsite specific configurations.
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})

View File

@@ -23,6 +23,7 @@ DATABASES = {
}
######################### PIPELINE ####################################
# Use RequireJS optimized storage