Merge pull request #19366 from edx/dahlia/proctoring-master
Upgrade edx-proctoring to version 1.5
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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', {})
|
||||
|
||||
@@ -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 #####################
|
||||
|
||||
|
||||
@@ -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', {})
|
||||
|
||||
@@ -23,6 +23,7 @@ DATABASES = {
|
||||
|
||||
}
|
||||
|
||||
|
||||
######################### PIPELINE ####################################
|
||||
|
||||
# Use RequireJS optimized storage
|
||||
|
||||
Reference in New Issue
Block a user