refactor: rename descriptor -> block within cms
Co-authored-by: Agrendalath <piotr@surowiec.it>
This commit is contained in:
committed by
Agrendalath
parent
a2f1ad1f11
commit
ce13cd540a
@@ -25,7 +25,7 @@ def print_course(course):
|
||||
print('num type name')
|
||||
for index, item in enumerate(course.tabs):
|
||||
print(index + 1, '"' + item.get('type') + '"', '"' + item.get('name', '') + '"')
|
||||
# If a course is bad we will get an error descriptor here, dump it and die instead of
|
||||
# If a course is bad we will get an error here, dump it and die instead of
|
||||
# just sending up the error that .id doesn't exist.
|
||||
except AttributeError:
|
||||
print(course)
|
||||
|
||||
@@ -1713,17 +1713,17 @@ class MetadataSaveTestCase(ContentStoreTestCase):
|
||||
"""
|
||||
video_data = VideoBlock.parse_video_xml(video_sample_xml)
|
||||
video_data.pop('source')
|
||||
self.video_descriptor = BlockFactory.create(
|
||||
self.video_block = BlockFactory.create(
|
||||
parent_location=course.location, category='video',
|
||||
**video_data
|
||||
)
|
||||
|
||||
def test_metadata_not_persistence(self):
|
||||
"""
|
||||
Test that descriptors which set metadata fields in their
|
||||
Test that blocks which set metadata fields in their
|
||||
constructor are correctly deleted.
|
||||
"""
|
||||
self.assertIn('html5_sources', own_metadata(self.video_descriptor))
|
||||
self.assertIn('html5_sources', own_metadata(self.video_block))
|
||||
attrs_to_strip = {
|
||||
'show_captions',
|
||||
'youtube_id_1_0',
|
||||
@@ -1736,13 +1736,13 @@ class MetadataSaveTestCase(ContentStoreTestCase):
|
||||
'track'
|
||||
}
|
||||
|
||||
location = self.video_descriptor.location
|
||||
location = self.video_block.location
|
||||
|
||||
for field_name in attrs_to_strip:
|
||||
delattr(self.video_descriptor, field_name)
|
||||
delattr(self.video_block, field_name)
|
||||
|
||||
self.assertNotIn('html5_sources', own_metadata(self.video_descriptor))
|
||||
self.store.update_item(self.video_descriptor, self.user.id)
|
||||
self.assertNotIn('html5_sources', own_metadata(self.video_block))
|
||||
self.store.update_item(self.video_block, self.user.id)
|
||||
block = self.store.get_item(location)
|
||||
|
||||
self.assertNotIn('html5_sources', own_metadata(block))
|
||||
@@ -2047,12 +2047,12 @@ class ContentLicenseTest(ContentStoreTestCase):
|
||||
def test_video_license_export(self):
|
||||
content_store = contentstore()
|
||||
root_dir = path(mkdtemp_clean())
|
||||
video_descriptor = BlockFactory.create(
|
||||
video_block = BlockFactory.create(
|
||||
parent_location=self.course.location, category='video',
|
||||
license="all-rights-reserved"
|
||||
)
|
||||
export_course_to_xml(self.store, content_store, self.course.id, root_dir, 'test_license')
|
||||
fname = f"{video_descriptor.scope_ids.usage_id.block_id}.xml"
|
||||
fname = f"{video_block.scope_ids.usage_id.block_id}.xml"
|
||||
video_file_path = root_dir / "test_license" / "video" / fname
|
||||
with video_file_path.open() as f:
|
||||
video_xml = etree.parse(f)
|
||||
|
||||
@@ -885,33 +885,33 @@ class CourseGradingTest(CourseTestCase):
|
||||
@mock.patch('cms.djangoapps.contentstore.signals.signals.GRADING_POLICY_CHANGED.send')
|
||||
def test_update_section_grader_type(self, send_signal, tracker, uuid):
|
||||
uuid.return_value = 'mockUUID'
|
||||
# Get the descriptor and the section_grader_type and assert they are the default values
|
||||
descriptor = modulestore().get_item(self.course.location)
|
||||
# Get the block and the section_grader_type and assert they are the default values
|
||||
block = modulestore().get_item(self.course.location)
|
||||
section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)
|
||||
|
||||
self.assertEqual('notgraded', section_grader_type['graderType'])
|
||||
self.assertEqual(None, descriptor.format)
|
||||
self.assertEqual(False, descriptor.graded)
|
||||
self.assertEqual(None, block.format)
|
||||
self.assertEqual(False, block.graded)
|
||||
|
||||
# Change the default grader type to Homework, which should also mark the section as graded
|
||||
CourseGradingModel.update_section_grader_type(self.course, 'Homework', self.user)
|
||||
descriptor = modulestore().get_item(self.course.location)
|
||||
block = modulestore().get_item(self.course.location)
|
||||
section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)
|
||||
grading_policy_1 = self._grading_policy_hash_for_course()
|
||||
|
||||
self.assertEqual('Homework', section_grader_type['graderType'])
|
||||
self.assertEqual('Homework', descriptor.format)
|
||||
self.assertEqual(True, descriptor.graded)
|
||||
self.assertEqual('Homework', block.format)
|
||||
self.assertEqual(True, block.graded)
|
||||
|
||||
# Change the grader type back to notgraded, which should also unmark the section as graded
|
||||
CourseGradingModel.update_section_grader_type(self.course, 'notgraded', self.user)
|
||||
descriptor = modulestore().get_item(self.course.location)
|
||||
block = modulestore().get_item(self.course.location)
|
||||
section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)
|
||||
grading_policy_2 = self._grading_policy_hash_for_course()
|
||||
|
||||
self.assertEqual('notgraded', section_grader_type['graderType'])
|
||||
self.assertEqual(None, descriptor.format)
|
||||
self.assertEqual(False, descriptor.graded)
|
||||
self.assertEqual(None, block.format)
|
||||
self.assertEqual(False, block.graded)
|
||||
|
||||
# one for each call to update_section_grader_type()
|
||||
send_signal.assert_has_calls([
|
||||
|
||||
@@ -69,19 +69,19 @@ class TestXBlockI18nService(ModuleStoreTestCase):
|
||||
self.request = mock.Mock()
|
||||
self.course = CourseFactory.create()
|
||||
self.field_data = mock.Mock()
|
||||
self.descriptor = BlockFactory(category="pure", parent=self.course)
|
||||
self.block = BlockFactory(category="pure", parent=self.course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
self.descriptor,
|
||||
self.block,
|
||||
self.field_data,
|
||||
)
|
||||
self.addCleanup(translation.deactivate)
|
||||
|
||||
def get_block_i18n_service(self, descriptor):
|
||||
def get_block_i18n_service(self, block):
|
||||
"""
|
||||
return the block i18n service.
|
||||
"""
|
||||
i18n_service = self.descriptor.runtime.service(descriptor, 'i18n')
|
||||
i18n_service = self.block.runtime.service(block, 'i18n')
|
||||
self.assertIsNotNone(i18n_service)
|
||||
self.assertIsInstance(i18n_service, XBlockI18nService)
|
||||
return i18n_service
|
||||
@@ -113,7 +113,7 @@ class TestXBlockI18nService(ModuleStoreTestCase):
|
||||
self.module.ugettext = self.old_ugettext
|
||||
self.module.gettext = self.old_ugettext
|
||||
|
||||
i18n_service = self.get_block_i18n_service(self.descriptor)
|
||||
i18n_service = self.get_block_i18n_service(self.block)
|
||||
|
||||
# Activate french, so that if the fr files haven't been loaded, they will be loaded now.
|
||||
with translation.override("fr"):
|
||||
@@ -150,13 +150,13 @@ class TestXBlockI18nService(ModuleStoreTestCase):
|
||||
translation.activate("es")
|
||||
with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir,
|
||||
languages=[get_language()])):
|
||||
i18n_service = self.get_block_i18n_service(self.descriptor)
|
||||
i18n_service = self.get_block_i18n_service(self.block)
|
||||
self.assertEqual(i18n_service.ugettext('Hello'), 'es-hello-world')
|
||||
|
||||
translation.activate("ar")
|
||||
with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir,
|
||||
languages=[get_language()])):
|
||||
i18n_service = self.get_block_i18n_service(self.descriptor)
|
||||
i18n_service = self.get_block_i18n_service(self.block)
|
||||
self.assertEqual(get_gettext(i18n_service)('Hello'), 'Hello')
|
||||
self.assertNotEqual(get_gettext(i18n_service)('Hello'), 'fr-hello-world')
|
||||
self.assertNotEqual(get_gettext(i18n_service)('Hello'), 'es-hello-world')
|
||||
@@ -164,14 +164,14 @@ class TestXBlockI18nService(ModuleStoreTestCase):
|
||||
translation.activate("fr")
|
||||
with mock.patch('gettext.translation', return_value=_translator(domain='text', localedir=localedir,
|
||||
languages=[get_language()])):
|
||||
i18n_service = self.get_block_i18n_service(self.descriptor)
|
||||
i18n_service = self.get_block_i18n_service(self.block)
|
||||
self.assertEqual(i18n_service.ugettext('Hello'), 'fr-hello-world')
|
||||
|
||||
def test_i18n_service_callable(self):
|
||||
"""
|
||||
Test: i18n service should be callable in studio.
|
||||
"""
|
||||
self.assertTrue(callable(self.descriptor.runtime._services.get('i18n'))) # pylint: disable=protected-access
|
||||
self.assertTrue(callable(self.block.runtime._services.get('i18n'))) # pylint: disable=protected-access
|
||||
|
||||
|
||||
class InternationalizationTest(ModuleStoreTestCase):
|
||||
|
||||
@@ -114,7 +114,7 @@ class LibraryTestCase(ModuleStoreTestCase):
|
||||
self.assertEqual(response.status_code, status_code_expected)
|
||||
return modulestore().get_item(lib_content_block.location)
|
||||
|
||||
def _bind_block(self, descriptor, user=None):
|
||||
def _bind_block(self, block, user=None):
|
||||
"""
|
||||
Helper to use the CMS's module system so we can access student-specific fields.
|
||||
"""
|
||||
@@ -123,7 +123,7 @@ class LibraryTestCase(ModuleStoreTestCase):
|
||||
if user not in self.session_data:
|
||||
self.session_data[user] = {}
|
||||
request = Mock(user=user, session=self.session_data[user])
|
||||
_load_preview_block(request, descriptor)
|
||||
_load_preview_block(request, block)
|
||||
|
||||
def _update_block(self, usage_key, metadata):
|
||||
"""
|
||||
@@ -174,13 +174,13 @@ class TestLibraries(LibraryTestCase):
|
||||
lc_block = self._refresh_children(lc_block)
|
||||
|
||||
# Now, we want to make sure that .children has the total # of potential
|
||||
# children, and that get_child_descriptors() returns the actual children
|
||||
# children, and that get_child_blocks() returns the actual children
|
||||
# chosen for a given student.
|
||||
# In order to be able to call get_child_descriptors(), we must first
|
||||
# In order to be able to call get_child_blocks(), we must first
|
||||
# call bind_for_student:
|
||||
self._bind_block(lc_block)
|
||||
self.assertEqual(len(lc_block.children), num_to_create)
|
||||
self.assertEqual(len(lc_block.get_child_descriptors()), num_expected)
|
||||
self.assertEqual(len(lc_block.get_child_blocks()), num_expected)
|
||||
|
||||
def test_consistent_children(self):
|
||||
"""
|
||||
@@ -204,7 +204,7 @@ class TestLibraries(LibraryTestCase):
|
||||
"""
|
||||
Fetch the child shown to the current user.
|
||||
"""
|
||||
children = block.get_child_descriptors()
|
||||
children = block.get_child_blocks()
|
||||
self.assertEqual(len(children), 1)
|
||||
return children[0]
|
||||
|
||||
|
||||
@@ -620,7 +620,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
|
||||
self.assertEqual(partitions[0]["scheme"], "random")
|
||||
|
||||
def _set_partitions(self, partitions):
|
||||
"""Set the user partitions of the course descriptor. """
|
||||
"""Set the user partitions of the course block. """
|
||||
self.course.user_partitions = partitions
|
||||
self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test)
|
||||
|
||||
|
||||
@@ -191,8 +191,8 @@ class CourseTestCase(ProceduralCourseTestMixin, ModuleStoreTestCase):
|
||||
""" Test getting the editing HTML for each vertical. """
|
||||
# assert is here to make sure that the course being tested actually has verticals (units) to check.
|
||||
self.assertGreater(len(items), 0, "Course has no verticals (units) to check")
|
||||
for descriptor in items:
|
||||
resp = self.client.get_html(get_url('container_handler', descriptor.location))
|
||||
for block in items:
|
||||
resp = self.client.get_html(get_url('container_handler', block.location))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def assertAssetsEqual(self, asset_son, course1_id, course2_id):
|
||||
|
||||
@@ -372,7 +372,7 @@ def get_split_group_display_name(xblock, course):
|
||||
|
||||
Arguments:
|
||||
xblock (XBlock): The courseware component.
|
||||
course (XBlock): The course descriptor.
|
||||
course (XBlock): The course block.
|
||||
|
||||
Returns:
|
||||
group name (String): Group name of the matching group xblock.
|
||||
@@ -399,14 +399,14 @@ def get_user_partition_info(xblock, schemes=None, course=None):
|
||||
schemes (iterable of str): If provided, filter partitions to include only
|
||||
schemes with the provided names.
|
||||
|
||||
course (XBlock): The course descriptor. If provided, uses this to look up the user partitions
|
||||
course (XBlock): The course block. If provided, uses this to look up the user partitions
|
||||
instead of loading the course. This is useful if we're calling this function multiple
|
||||
times for the same course want to minimize queries to the modulestore.
|
||||
|
||||
Returns: list
|
||||
|
||||
Example Usage:
|
||||
>>> get_user_partition_info(block, schemes=["cohort", "verification"])
|
||||
>>> get_user_partition_info(xblock, schemes=["cohort", "verification"])
|
||||
[
|
||||
{
|
||||
"id": 12345,
|
||||
@@ -509,7 +509,7 @@ def get_visibility_partition_info(xblock, course=None):
|
||||
Arguments:
|
||||
xblock (XBlock): The component being edited.
|
||||
|
||||
course (XBlock): The course descriptor. If provided, uses this to look up the user partitions
|
||||
course (XBlock): The course block. If provided, uses this to look up the user partitions
|
||||
instead of loading the course. This is useful if we're calling this function multiple
|
||||
times for the same course want to minimize queries to the modulestore.
|
||||
|
||||
@@ -569,8 +569,8 @@ def get_xblock_aside_instance(usage_key):
|
||||
:param usage_key: Usage key of aside xblock
|
||||
"""
|
||||
try:
|
||||
descriptor = modulestore().get_item(usage_key.usage_key)
|
||||
for aside in descriptor.runtime.get_asides(descriptor):
|
||||
xblock = modulestore().get_item(usage_key.usage_key)
|
||||
for aside in xblock.runtime.get_asides(xblock):
|
||||
if aside.scope_ids.block_type == usage_key.aside_type:
|
||||
return aside
|
||||
except ItemNotFoundError:
|
||||
|
||||
@@ -128,7 +128,7 @@ class CertificateValidationError(CertificateException):
|
||||
class CertificateManager:
|
||||
"""
|
||||
The CertificateManager is responsible for storage, retrieval, and manipulation of Certificates
|
||||
Certificates are not stored in the Django ORM, they are a field/setting on the course descriptor
|
||||
Certificates are not stored in the Django ORM, they are a field/setting on the course block
|
||||
"""
|
||||
@staticmethod
|
||||
def parse(json_string):
|
||||
|
||||
@@ -552,18 +552,18 @@ def component_handler(request, usage_key_string, handler, suffix=''):
|
||||
|
||||
try:
|
||||
if is_xblock_aside(usage_key):
|
||||
# Get the descriptor for the block being wrapped by the aside (not the aside itself)
|
||||
descriptor = modulestore().get_item(usage_key.usage_key)
|
||||
handler_descriptor = get_aside_from_xblock(descriptor, usage_key.aside_type)
|
||||
asides = [handler_descriptor]
|
||||
# Get the block being wrapped by the aside (not the aside itself)
|
||||
block = modulestore().get_item(usage_key.usage_key)
|
||||
handler_block = get_aside_from_xblock(block, usage_key.aside_type)
|
||||
asides = [handler_block]
|
||||
else:
|
||||
descriptor = modulestore().get_item(usage_key)
|
||||
handler_descriptor = descriptor
|
||||
block = modulestore().get_item(usage_key)
|
||||
handler_block = block
|
||||
asides = []
|
||||
load_services_for_studio(handler_descriptor.runtime, request.user)
|
||||
resp = handler_descriptor.handle(handler, req, suffix)
|
||||
load_services_for_studio(handler_block.runtime, request.user)
|
||||
resp = handler_block.handle(handler, req, suffix)
|
||||
except NoSuchHandlerError:
|
||||
log.info("XBlock %s attempted to access missing handler %r", handler_descriptor, handler, exc_info=True)
|
||||
log.info("XBlock %s attempted to access missing handler %r", handler_block, handler, exc_info=True)
|
||||
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
||||
|
||||
# unintentional update to handle any side effects of handle call
|
||||
@@ -572,7 +572,7 @@ def component_handler(request, usage_key_string, handler, suffix=''):
|
||||
# TNL 101-62 studio write permission is also checked for editing content.
|
||||
|
||||
if has_course_author_access(request.user, usage_key.course_key):
|
||||
modulestore().update_item(descriptor, request.user.id, asides=asides)
|
||||
modulestore().update_item(block, request.user.id, asides=asides)
|
||||
else:
|
||||
#fail quietly if user is not course author.
|
||||
log.warning(
|
||||
|
||||
@@ -176,9 +176,9 @@ def _get_entrance_exam(request, course_key):
|
||||
except InvalidKeyError:
|
||||
return HttpResponse(status=404)
|
||||
try:
|
||||
exam_descriptor = modulestore().get_item(exam_key)
|
||||
exam_block = modulestore().get_item(exam_key)
|
||||
return HttpResponse( # lint-amnesty, pylint: disable=http-response-with-content-type-json
|
||||
dump_js_escaped_json({'locator': str(exam_descriptor.location)}),
|
||||
dump_js_escaped_json({'locator': str(exam_block.location)}),
|
||||
status=200, content_type='application/json')
|
||||
except ItemNotFoundError:
|
||||
return HttpResponse(status=404)
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from rest_framework.request import Request
|
||||
from web_fragments.fragment import Fragment
|
||||
from xblock.django.request import django_to_webob_request, webob_to_django_response
|
||||
from xblock.exceptions import NoSuchHandlerError
|
||||
@@ -24,7 +25,7 @@ from xmodule.services import SettingsService, TeamsConfigurationService
|
||||
from xmodule.studio_editable import has_author_view
|
||||
from xmodule.util.sandboxing import SandboxService
|
||||
from xmodule.util.xmodule_django import add_webpack_to_fragment
|
||||
from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW
|
||||
from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW, XModuleMixin
|
||||
from cms.djangoapps.xblock_config.models import StudioConfig
|
||||
from cms.djangoapps.contentstore.toggles import individualize_anonymous_user_id, ENABLE_COPY_PASTE_FEATURE
|
||||
from cms.lib.xblock.field_data import CmsFieldData
|
||||
@@ -65,8 +66,8 @@ def preview_handler(request, usage_key_string, handler, suffix=''):
|
||||
"""
|
||||
usage_key = UsageKey.from_string(usage_key_string)
|
||||
|
||||
descriptor = modulestore().get_item(usage_key)
|
||||
instance = _load_preview_block(request, descriptor)
|
||||
block = modulestore().get_item(usage_key)
|
||||
instance = _load_preview_block(request, block)
|
||||
|
||||
# Let the module handle the AJAX
|
||||
req = django_to_webob_request(request)
|
||||
@@ -154,6 +155,7 @@ def _prepare_runtime_for_preview(request, block, field_data):
|
||||
required for rendering block previews.
|
||||
|
||||
request: The active django request
|
||||
block: An XBlock
|
||||
field_data: Wrapped field data for previews
|
||||
"""
|
||||
|
||||
@@ -256,29 +258,29 @@ class StudioPartitionService(PartitionService):
|
||||
return None
|
||||
|
||||
|
||||
def _load_preview_block(request, descriptor):
|
||||
def _load_preview_block(request: Request, block: XModuleMixin):
|
||||
"""
|
||||
Return a preview XBlock instantiated from the supplied descriptor. Will use mutable fields
|
||||
Return a preview XBlock instantiated from the supplied block. Will use mutable fields
|
||||
if XBlock supports an author_view. Otherwise, will use immutable fields and student_view.
|
||||
|
||||
request: The active django request
|
||||
descriptor: An XModuleDescriptor
|
||||
block: An XModuleMixin
|
||||
"""
|
||||
student_data = KvsFieldData(SessionKeyValueStore(request))
|
||||
if has_author_view(descriptor):
|
||||
if has_author_view(block):
|
||||
wrapper = partial(CmsFieldData, student_data=student_data)
|
||||
else:
|
||||
wrapper = partial(LmsFieldData, student_data=student_data)
|
||||
|
||||
# wrap the _field_data upfront to pass to _prepare_runtime_for_preview
|
||||
wrapped_field_data = wrapper(descriptor._field_data) # pylint: disable=protected-access
|
||||
_prepare_runtime_for_preview(request, descriptor, wrapped_field_data)
|
||||
wrapped_field_data = wrapper(block._field_data) # pylint: disable=protected-access
|
||||
_prepare_runtime_for_preview(request, block, wrapped_field_data)
|
||||
|
||||
descriptor.bind_for_student(
|
||||
block.bind_for_student(
|
||||
request.user.id,
|
||||
[wrapper]
|
||||
)
|
||||
return descriptor
|
||||
return block
|
||||
|
||||
|
||||
def _is_xblock_reorderable(xblock, context):
|
||||
@@ -332,12 +334,12 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
return frag
|
||||
|
||||
|
||||
def get_preview_fragment(request, descriptor, context):
|
||||
def get_preview_fragment(request, block, context):
|
||||
"""
|
||||
Returns the HTML returned by the XModule's student_view or author_view (if available),
|
||||
specified by the descriptor and idx.
|
||||
specified by the block and idx.
|
||||
"""
|
||||
block = _load_preview_block(request, descriptor)
|
||||
block = _load_preview_block(request, block)
|
||||
|
||||
preview_view = AUTHOR_VIEW if has_author_view(block) else STUDENT_VIEW
|
||||
|
||||
|
||||
@@ -2159,10 +2159,10 @@ class TestComponentHandler(TestCase):
|
||||
self.modulestore = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
# component_handler calls modulestore.get_item to get the descriptor of the requested xBlock.
|
||||
# component_handler calls modulestore.get_item to get the requested xBlock.
|
||||
# Here, we mock the return value of modulestore.get_item so it can be used to mock the handler
|
||||
# of the xBlock descriptor.
|
||||
self.descriptor = self.modulestore.return_value.get_item.return_value
|
||||
# of the xBlock.
|
||||
self.block = self.modulestore.return_value.get_item.return_value
|
||||
|
||||
self.usage_key = BlockUsageLocator(
|
||||
CourseLocator('dummy_org', 'dummy_course', 'dummy_run'), 'dummy_category', 'dummy_name'
|
||||
@@ -2173,7 +2173,7 @@ class TestComponentHandler(TestCase):
|
||||
self.request.user = self.user
|
||||
|
||||
def test_invalid_handler(self):
|
||||
self.descriptor.handle.side_effect = NoSuchHandlerError
|
||||
self.block.handle.side_effect = NoSuchHandlerError
|
||||
|
||||
with self.assertRaises(Http404):
|
||||
component_handler(self.request, self.usage_key_string, 'invalid_handler')
|
||||
@@ -2185,7 +2185,7 @@ class TestComponentHandler(TestCase):
|
||||
self.assertEqual(request.method, method)
|
||||
return Response()
|
||||
|
||||
self.descriptor.handle = check_handler
|
||||
self.block.handle = check_handler
|
||||
|
||||
# Have to use the right method to create the request to get the HTTP method that we want
|
||||
req_factory_method = getattr(self.request_factory, method.lower())
|
||||
@@ -2198,7 +2198,7 @@ class TestComponentHandler(TestCase):
|
||||
def create_response(handler, request, suffix): # lint-amnesty, pylint: disable=unused-argument
|
||||
return Response(status_code=status_code)
|
||||
|
||||
self.descriptor.handle = create_response
|
||||
self.block.handle = create_response
|
||||
|
||||
self.assertEqual(component_handler(self.request, self.usage_key_string, 'dummy_handler').status_code,
|
||||
status_code)
|
||||
@@ -2219,7 +2219,7 @@ class TestComponentHandler(TestCase):
|
||||
self.request.user = UserFactory()
|
||||
mock_handler = 'dummy_handler'
|
||||
|
||||
self.descriptor.handle = create_response
|
||||
self.block.handle = create_response
|
||||
|
||||
with patch(
|
||||
'cms.djangoapps.contentstore.views.component.is_xblock_aside',
|
||||
@@ -2253,7 +2253,7 @@ class TestComponentHandler(TestCase):
|
||||
else self.usage_key_string
|
||||
)
|
||||
|
||||
self.descriptor.handle = create_response
|
||||
self.block.handle = create_response
|
||||
|
||||
with patch(
|
||||
'cms.djangoapps.contentstore.views.component.is_xblock_aside',
|
||||
|
||||
@@ -213,13 +213,13 @@ class StudioXBlockServiceBindingTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests that the 'user' and 'i18n' services are provided by the Studio runtime.
|
||||
"""
|
||||
descriptor = BlockFactory(category="pure", parent=self.course)
|
||||
block = BlockFactory(category="pure", parent=self.course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
descriptor,
|
||||
block,
|
||||
self.field_data,
|
||||
)
|
||||
service = descriptor.runtime.service(descriptor, expected_service)
|
||||
service = block.runtime.service(block, expected_service)
|
||||
self.assertIsNotNone(service)
|
||||
|
||||
|
||||
@@ -242,32 +242,31 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
self.request = RequestFactory().get('/dummy-url')
|
||||
self.request.user = self.user
|
||||
self.request.session = {}
|
||||
self.descriptor = BlockFactory(category="video", parent=course)
|
||||
self.field_data = mock.Mock()
|
||||
self.contentstore = contentstore()
|
||||
self.descriptor = BlockFactory(category="problem", parent=course)
|
||||
self.block = BlockFactory(category="problem", parent=course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
block=self.descriptor,
|
||||
block=self.block,
|
||||
field_data=mock.Mock(),
|
||||
)
|
||||
self.course = self.store.get_item(course.location)
|
||||
|
||||
def test_get_user_role(self):
|
||||
assert self.descriptor.runtime.get_user_role() == 'staff'
|
||||
assert self.block.runtime.get_user_role() == 'staff'
|
||||
|
||||
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
|
||||
def test_render_template(self):
|
||||
descriptor = BlockFactory(category="pure", parent=self.course)
|
||||
html = get_preview_fragment(self.request, descriptor, {'element_id': 142}).content
|
||||
block = BlockFactory(category="pure", parent=self.course)
|
||||
html = get_preview_fragment(self.request, block, {'element_id': 142}).content
|
||||
assert '<div id="142" ns="main">Testing the MakoService</div>' in html
|
||||
|
||||
@override_settings(COURSES_WITH_UNSAFE_CODE=[r'course-v1:edX\+LmsModuleShimTest\+2021_Fall'])
|
||||
def test_can_execute_unsafe_code(self):
|
||||
assert self.descriptor.runtime.can_execute_unsafe_code()
|
||||
assert self.block.runtime.can_execute_unsafe_code()
|
||||
|
||||
def test_cannot_execute_unsafe_code(self):
|
||||
assert not self.descriptor.runtime.can_execute_unsafe_code()
|
||||
assert not self.block.runtime.can_execute_unsafe_code()
|
||||
|
||||
@override_settings(PYTHON_LIB_FILENAME=PYTHON_LIB_FILENAME)
|
||||
def test_get_python_lib_zip(self):
|
||||
@@ -277,7 +276,7 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
source_file=self.PYTHON_LIB_SOURCE_FILE,
|
||||
target_filename=self.PYTHON_LIB_FILENAME,
|
||||
)
|
||||
assert self.descriptor.runtime.get_python_lib_zip() == zipfile
|
||||
assert self.block.runtime.get_python_lib_zip() == zipfile
|
||||
|
||||
def test_no_get_python_lib_zip(self):
|
||||
zipfile = upload_file_to_course(
|
||||
@@ -286,40 +285,40 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
source_file=self.PYTHON_LIB_SOURCE_FILE,
|
||||
target_filename=self.PYTHON_LIB_FILENAME,
|
||||
)
|
||||
assert self.descriptor.runtime.get_python_lib_zip() is None
|
||||
assert self.block.runtime.get_python_lib_zip() is None
|
||||
|
||||
def test_cache(self):
|
||||
assert hasattr(self.descriptor.runtime.cache, 'get')
|
||||
assert hasattr(self.descriptor.runtime.cache, 'set')
|
||||
assert hasattr(self.block.runtime.cache, 'get')
|
||||
assert hasattr(self.block.runtime.cache, 'set')
|
||||
|
||||
def test_replace_urls(self):
|
||||
html = '<a href="/static/id">'
|
||||
assert self.descriptor.runtime.replace_urls(html) == \
|
||||
assert self.block.runtime.replace_urls(html) == \
|
||||
static_replace.replace_static_urls(html, course_id=self.course.id)
|
||||
|
||||
def test_anonymous_user_id_preview(self):
|
||||
assert self.descriptor.runtime.anonymous_student_id == 'student'
|
||||
assert self.block.runtime.anonymous_student_id == 'student'
|
||||
|
||||
@override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True)
|
||||
def test_anonymous_user_id_individual_per_student(self):
|
||||
"""Test anonymous_user_id on a block which uses per-student anonymous IDs"""
|
||||
# Create the runtime with the flag turned on.
|
||||
descriptor = BlockFactory(category="problem", parent=self.course)
|
||||
block = BlockFactory(category="problem", parent=self.course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
block=descriptor,
|
||||
block=block,
|
||||
field_data=mock.Mock(),
|
||||
)
|
||||
assert descriptor.runtime.anonymous_student_id == '26262401c528d7c4a6bbeabe0455ec46'
|
||||
assert block.runtime.anonymous_student_id == '26262401c528d7c4a6bbeabe0455ec46'
|
||||
|
||||
@override_waffle_flag(INDIVIDUALIZE_ANONYMOUS_USER_ID, active=True)
|
||||
def test_anonymous_user_id_individual_per_course(self):
|
||||
"""Test anonymous_user_id on a block which uses per-course anonymous IDs"""
|
||||
# Create the runtime with the flag turned on.
|
||||
descriptor = BlockFactory(category="lti", parent=self.course)
|
||||
block = BlockFactory(category="lti", parent=self.course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
block=descriptor,
|
||||
block=block,
|
||||
field_data=mock.Mock(),
|
||||
)
|
||||
assert descriptor.runtime.anonymous_student_id == 'ad503f629b55c531fed2e45aa17a3368'
|
||||
assert block.runtime.anonymous_student_id == 'ad503f629b55c531fed2e45aa17a3368'
|
||||
|
||||
@@ -364,7 +364,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
def test_transcript_upload_with_non_existant_edx_video_id(self):
|
||||
"""
|
||||
Test that transcript upload works as expected if `edx_video_id` set on
|
||||
video descriptor is different from `edx_video_id` received in POST request.
|
||||
video block is different from `edx_video_id` received in POST request.
|
||||
"""
|
||||
non_existant_edx_video_id = '1111-2222-3333-4444'
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ def link_video_to_component(video_component, user):
|
||||
Links a VAL video to the video component.
|
||||
|
||||
Arguments:
|
||||
video_component: video descriptor item.
|
||||
video_component: video block.
|
||||
user: A requesting user.
|
||||
|
||||
Returns:
|
||||
@@ -134,7 +134,7 @@ def validate_video_block(request, locator):
|
||||
locator: video locator.
|
||||
|
||||
Returns:
|
||||
A tuple containing error(or None) and video descriptor(i.e. if validation succeeds).
|
||||
A tuple containing error(or None) and video block(i.e. if validation succeeds).
|
||||
|
||||
Raises:
|
||||
PermissionDenied: if requesting user does not have access to author the video component.
|
||||
|
||||
@@ -25,21 +25,21 @@ class CourseGradingModel:
|
||||
"""
|
||||
# Within this class, allow access to protected members of client classes.
|
||||
# This comes up when accessing kvs data and caches during kvs saves and modulestore writes.
|
||||
def __init__(self, course_descriptor):
|
||||
def __init__(self, course):
|
||||
self.graders = [
|
||||
CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)
|
||||
CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course.raw_grader)
|
||||
] # weights transformed to ints [0..100]
|
||||
self.grade_cutoffs = course_descriptor.grade_cutoffs
|
||||
self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor)
|
||||
self.minimum_grade_credit = course_descriptor.minimum_grade_credit
|
||||
self.grade_cutoffs = course.grade_cutoffs
|
||||
self.grace_period = CourseGradingModel.convert_set_grace_period(course)
|
||||
self.minimum_grade_credit = course.minimum_grade_credit
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, course_key):
|
||||
"""
|
||||
Fetch the course grading policy for the given course from persistence and return a CourseGradingModel.
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
model = cls(descriptor)
|
||||
course = modulestore().get_course(course_key)
|
||||
model = cls(course)
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
@@ -48,10 +48,10 @@ class CourseGradingModel:
|
||||
Fetch the course's nth grader
|
||||
Returns an empty dict if there's no such grader.
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
course = modulestore().get_course(course_key)
|
||||
index = int(index)
|
||||
if len(descriptor.raw_grader) > index:
|
||||
return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])
|
||||
if len(course.raw_grader) > index:
|
||||
return CourseGradingModel.jsonize_grader(index, course.raw_grader[index])
|
||||
|
||||
# return empty model
|
||||
else:
|
||||
@@ -69,27 +69,27 @@ class CourseGradingModel:
|
||||
Decode the json into CourseGradingModel and save any changes. Returns the modified model.
|
||||
Probably not the usual path for updates as it's too coarse grained.
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
previous_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy))
|
||||
course = modulestore().get_course(course_key)
|
||||
previous_grading_policy_hash = str(hash_grading_policy(course.grading_policy))
|
||||
|
||||
graders_parsed = [CourseGradingModel.parse_grader(jsonele) for jsonele in jsondict['graders']]
|
||||
fire_signal = CourseGradingModel.must_fire_grading_event_and_signal(
|
||||
course_key,
|
||||
graders_parsed,
|
||||
descriptor,
|
||||
course,
|
||||
jsondict
|
||||
)
|
||||
descriptor.raw_grader = graders_parsed
|
||||
descriptor.grade_cutoffs = jsondict['grade_cutoffs']
|
||||
course.raw_grader = graders_parsed
|
||||
course.grade_cutoffs = jsondict['grade_cutoffs']
|
||||
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
modulestore().update_item(course, user.id)
|
||||
|
||||
CourseGradingModel.update_grace_period_from_json(course_key, jsondict['grace_period'], user)
|
||||
|
||||
CourseGradingModel.update_minimum_grade_credit_from_json(course_key, jsondict['minimum_grade_credit'], user)
|
||||
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
new_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy))
|
||||
course = modulestore().get_course(course_key)
|
||||
new_grading_policy_hash = str(hash_grading_policy(course.grading_policy))
|
||||
log.info(
|
||||
"Updated course grading policy for course %s from %s to %s. fire_signal = %s",
|
||||
str(course_key),
|
||||
@@ -153,28 +153,28 @@ class CourseGradingModel:
|
||||
Create or update the grader of the given type (string key) for the given course. Returns the modified
|
||||
grader which is a full model on the client but not on the server (just a dict)
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
previous_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy))
|
||||
course = modulestore().get_course(course_key)
|
||||
previous_grading_policy_hash = str(hash_grading_policy(course.grading_policy))
|
||||
|
||||
# parse removes the id; so, grab it before parse
|
||||
index = int(grader.get('id', len(descriptor.raw_grader)))
|
||||
index = int(grader.get('id', len(course.raw_grader)))
|
||||
grader = CourseGradingModel.parse_grader(grader)
|
||||
|
||||
fire_signal = True
|
||||
if index < len(descriptor.raw_grader):
|
||||
if index < len(course.raw_grader):
|
||||
fire_signal = CourseGradingModel.must_fire_grading_event_and_signal_single_grader(
|
||||
course_key,
|
||||
grader,
|
||||
descriptor.raw_grader[index]
|
||||
course.raw_grader[index]
|
||||
)
|
||||
descriptor.raw_grader[index] = grader
|
||||
course.raw_grader[index] = grader
|
||||
else:
|
||||
descriptor.raw_grader.append(grader)
|
||||
course.raw_grader.append(grader)
|
||||
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
modulestore().update_item(course, user.id)
|
||||
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
new_grading_policy_hash = str(hash_grading_policy(descriptor.grading_policy))
|
||||
course = modulestore().get_course(course_key)
|
||||
new_grading_policy_hash = str(hash_grading_policy(course.grading_policy))
|
||||
log.info(
|
||||
"Updated grader for course %s. Grading policy has changed from %s to %s. fire_signal = %s",
|
||||
str(course_key),
|
||||
@@ -185,7 +185,7 @@ class CourseGradingModel:
|
||||
if fire_signal:
|
||||
_grading_event_and_signal(course_key, user.id)
|
||||
|
||||
return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])
|
||||
return CourseGradingModel.jsonize_grader(index, course.raw_grader[index])
|
||||
|
||||
@staticmethod
|
||||
def update_cutoffs_from_json(course_key, cutoffs, user):
|
||||
@@ -193,10 +193,10 @@ class CourseGradingModel:
|
||||
Create or update the grade cutoffs for the given course. Returns sent in cutoffs (ie., no extra
|
||||
db fetch).
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
descriptor.grade_cutoffs = cutoffs
|
||||
course = modulestore().get_course(course_key)
|
||||
course.grade_cutoffs = cutoffs
|
||||
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
modulestore().update_item(course, user.id)
|
||||
_grading_event_and_signal(course_key, user.id)
|
||||
return cutoffs
|
||||
|
||||
@@ -207,7 +207,7 @@ class CourseGradingModel:
|
||||
grace_period entry in an enclosing dict. It is also safe to call this method with a value of
|
||||
None for graceperiodjson.
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
course = modulestore().get_course(course_key)
|
||||
|
||||
# Before a graceperiod has ever been created, it will be None (once it has been
|
||||
# created, it cannot be set back to None).
|
||||
@@ -216,9 +216,9 @@ class CourseGradingModel:
|
||||
graceperiodjson = graceperiodjson['grace_period']
|
||||
|
||||
grace_timedelta = timedelta(**graceperiodjson)
|
||||
descriptor.graceperiod = grace_timedelta
|
||||
course.graceperiod = grace_timedelta
|
||||
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
modulestore().update_item(course, user.id)
|
||||
|
||||
@staticmethod
|
||||
def update_minimum_grade_credit_from_json(course_key, minimum_grade_credit, user):
|
||||
@@ -230,29 +230,29 @@ class CourseGradingModel:
|
||||
user(User): The user object
|
||||
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
course = modulestore().get_course(course_key)
|
||||
|
||||
# 'minimum_grade_credit' cannot be set to None
|
||||
if minimum_grade_credit is not None:
|
||||
minimum_grade_credit = minimum_grade_credit # lint-amnesty, pylint: disable=self-assigning-variable
|
||||
|
||||
descriptor.minimum_grade_credit = minimum_grade_credit
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
course.minimum_grade_credit = minimum_grade_credit
|
||||
modulestore().update_item(course, user.id)
|
||||
|
||||
@staticmethod
|
||||
def delete_grader(course_key, index, user):
|
||||
"""
|
||||
Delete the grader of the given type from the given course.
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
course = modulestore().get_course(course_key)
|
||||
|
||||
index = int(index)
|
||||
if index < len(descriptor.raw_grader):
|
||||
del descriptor.raw_grader[index]
|
||||
if index < len(course.raw_grader):
|
||||
del course.raw_grader[index]
|
||||
# force propagation to definition
|
||||
descriptor.raw_grader = descriptor.raw_grader
|
||||
course.raw_grader = course.raw_grader
|
||||
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
modulestore().update_item(course, user.id)
|
||||
_grading_event_and_signal(course_key, user.id)
|
||||
|
||||
@staticmethod
|
||||
@@ -260,37 +260,37 @@ class CourseGradingModel:
|
||||
"""
|
||||
Delete the course's grace period.
|
||||
"""
|
||||
descriptor = modulestore().get_course(course_key)
|
||||
course = modulestore().get_course(course_key)
|
||||
|
||||
del descriptor.graceperiod
|
||||
del course.graceperiod
|
||||
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
modulestore().update_item(course, user.id)
|
||||
|
||||
@staticmethod
|
||||
def get_section_grader_type(location):
|
||||
descriptor = modulestore().get_item(location)
|
||||
block = modulestore().get_item(location)
|
||||
return {
|
||||
"graderType": descriptor.format if descriptor.format is not None else 'notgraded',
|
||||
"graderType": block.format if block.format is not None else 'notgraded',
|
||||
"location": str(location),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def update_section_grader_type(descriptor, grader_type, user): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
def update_section_grader_type(block, grader_type, user): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
if grader_type is not None and grader_type != 'notgraded':
|
||||
descriptor.format = grader_type
|
||||
descriptor.graded = True
|
||||
block.format = grader_type
|
||||
block.graded = True
|
||||
else:
|
||||
del descriptor.format
|
||||
del descriptor.graded
|
||||
del block.format
|
||||
del block.graded
|
||||
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
_grading_event_and_signal(descriptor.location.course_key, user.id)
|
||||
modulestore().update_item(block, user.id)
|
||||
_grading_event_and_signal(block.location.course_key, user.id)
|
||||
return {'graderType': grader_type}
|
||||
|
||||
@staticmethod
|
||||
def convert_set_grace_period(descriptor): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
def convert_set_grace_period(course): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
# 5 hours 59 minutes 59 seconds => converted to iso format
|
||||
rawgrace = descriptor.graceperiod
|
||||
rawgrace = course.graceperiod
|
||||
if rawgrace:
|
||||
hours_from_days = rawgrace.days * 24
|
||||
seconds = rawgrace.seconds
|
||||
|
||||
@@ -155,14 +155,14 @@ class CourseMetadata:
|
||||
return exclude_list
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, descriptor, filter_fields=None):
|
||||
def fetch(cls, block, filter_fields=None):
|
||||
"""
|
||||
Fetch the key:value editable course details for the given course from
|
||||
persistence and return a CourseMetadata model.
|
||||
"""
|
||||
result = {}
|
||||
metadata = cls.fetch_all(descriptor, filter_fields=filter_fields)
|
||||
exclude_list_of_fields = cls.get_exclude_list_of_fields(descriptor.id)
|
||||
metadata = cls.fetch_all(block, filter_fields=filter_fields)
|
||||
exclude_list_of_fields = cls.get_exclude_list_of_fields(block.id)
|
||||
|
||||
for key, value in metadata.items():
|
||||
if key in exclude_list_of_fields:
|
||||
@@ -171,12 +171,12 @@ class CourseMetadata:
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def fetch_all(cls, descriptor, filter_fields=None):
|
||||
def fetch_all(cls, block, filter_fields=None):
|
||||
"""
|
||||
Fetches all key:value pairs from persistence and returns a CourseMetadata model.
|
||||
"""
|
||||
result = {}
|
||||
for field in descriptor.fields.values():
|
||||
for field in block.fields.values():
|
||||
if field.scope != Scope.settings:
|
||||
continue
|
||||
|
||||
@@ -189,7 +189,7 @@ class CourseMetadata:
|
||||
field_help = field_help.format(**help_args)
|
||||
|
||||
result[field.name] = {
|
||||
'value': field.read_json(descriptor),
|
||||
'value': field.read_json(block),
|
||||
'display_name': _(field.display_name), # lint-amnesty, pylint: disable=translation-of-non-string
|
||||
'help': field_help,
|
||||
'deprecated': field.runtime_options.get('deprecated', False),
|
||||
@@ -198,13 +198,13 @@ class CourseMetadata:
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def update_from_json(cls, descriptor, jsondict, user, filter_tabs=True):
|
||||
def update_from_json(cls, block, jsondict, user, filter_tabs=True):
|
||||
"""
|
||||
Decode the json into CourseMetadata and save any changed attrs to the db.
|
||||
|
||||
Ensures none of the fields are in the exclude list.
|
||||
"""
|
||||
exclude_list_of_fields = cls.get_exclude_list_of_fields(descriptor.id)
|
||||
exclude_list_of_fields = cls.get_exclude_list_of_fields(block.id)
|
||||
# Don't filter on the tab attribute if filter_tabs is False.
|
||||
if not filter_tabs:
|
||||
exclude_list_of_fields.remove("tabs")
|
||||
@@ -218,16 +218,16 @@ class CourseMetadata:
|
||||
continue
|
||||
try:
|
||||
val = model['value']
|
||||
if hasattr(descriptor, key) and getattr(descriptor, key) != val:
|
||||
key_values[key] = descriptor.fields[key].from_json(val)
|
||||
if hasattr(block, key) and getattr(block, key) != val:
|
||||
key_values[key] = block.fields[key].from_json(val)
|
||||
except (TypeError, ValueError) as err:
|
||||
raise ValueError(_("Incorrect format for field '{name}'. {detailed_message}").format( # lint-amnesty, pylint: disable=raise-missing-from
|
||||
name=model['display_name'], detailed_message=str(err)))
|
||||
|
||||
return cls.update_from_dict(key_values, descriptor, user)
|
||||
return cls.update_from_dict(key_values, block, user)
|
||||
|
||||
@classmethod
|
||||
def validate_and_update_from_json(cls, descriptor, jsondict, user, filter_tabs=True):
|
||||
def validate_and_update_from_json(cls, block, jsondict, user, filter_tabs=True):
|
||||
"""
|
||||
Validate the values in the json dict (validated by xblock fields from_json method)
|
||||
|
||||
@@ -240,7 +240,7 @@ class CourseMetadata:
|
||||
errors: list of error objects
|
||||
result: the updated course metadata or None if error
|
||||
"""
|
||||
exclude_list_of_fields = cls.get_exclude_list_of_fields(descriptor.id)
|
||||
exclude_list_of_fields = cls.get_exclude_list_of_fields(block.id)
|
||||
|
||||
if not filter_tabs:
|
||||
exclude_list_of_fields.remove("tabs")
|
||||
@@ -254,8 +254,8 @@ class CourseMetadata:
|
||||
for key, model in filtered_dict.items():
|
||||
try:
|
||||
val = model['value']
|
||||
if hasattr(descriptor, key) and getattr(descriptor, key) != val:
|
||||
key_values[key] = descriptor.fields[key].from_json(val)
|
||||
if hasattr(block, key) and getattr(block, key) != val:
|
||||
key_values[key] = block.fields[key].from_json(val)
|
||||
except (TypeError, ValueError, ValidationError) as err:
|
||||
did_validate = False
|
||||
errors.append({'key': key, 'message': str(err), 'model': model})
|
||||
@@ -264,7 +264,7 @@ class CourseMetadata:
|
||||
# Because we cannot pass course context to the exception, we need to check if the LTI provider
|
||||
# should actually be available to the course
|
||||
err_message = str(err)
|
||||
if not exams_ida_enabled(descriptor.id):
|
||||
if not exams_ida_enabled(block.id):
|
||||
available_providers = get_available_providers()
|
||||
available_providers.remove('lti_external')
|
||||
err_message = str(InvalidProctoringProvider(val, available_providers))
|
||||
@@ -277,29 +277,29 @@ class CourseMetadata:
|
||||
errors = errors + team_setting_errors
|
||||
did_validate = False
|
||||
|
||||
proctoring_errors = cls.validate_proctoring_settings(descriptor, filtered_dict, user)
|
||||
proctoring_errors = cls.validate_proctoring_settings(block, filtered_dict, user)
|
||||
if proctoring_errors:
|
||||
errors = errors + proctoring_errors
|
||||
did_validate = False
|
||||
|
||||
# If did validate, go ahead and update the metadata
|
||||
if did_validate:
|
||||
updated_data = cls.update_from_dict(key_values, descriptor, user, save=False)
|
||||
updated_data = cls.update_from_dict(key_values, block, user, save=False)
|
||||
|
||||
return did_validate, errors, updated_data
|
||||
|
||||
@classmethod
|
||||
def update_from_dict(cls, key_values, descriptor, user, save=True):
|
||||
def update_from_dict(cls, key_values, block, user, save=True):
|
||||
"""
|
||||
Update metadata descriptor from key_values. Saves to modulestore if save is true.
|
||||
Update metadata from key_values. Saves to modulestore if save is true.
|
||||
"""
|
||||
for key, value in key_values.items():
|
||||
setattr(descriptor, key, value)
|
||||
setattr(block, key, value)
|
||||
|
||||
if save and key_values:
|
||||
modulestore().update_item(descriptor, user.id)
|
||||
modulestore().update_item(block, user.id)
|
||||
|
||||
return cls.fetch(descriptor)
|
||||
return cls.fetch(block)
|
||||
|
||||
@classmethod
|
||||
def validate_team_settings(cls, settings_dict):
|
||||
@@ -397,7 +397,7 @@ class CourseMetadata:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def validate_proctoring_settings(cls, descriptor, settings_dict, user):
|
||||
def validate_proctoring_settings(cls, block, settings_dict, user):
|
||||
"""
|
||||
Verify proctoring settings
|
||||
|
||||
@@ -412,9 +412,9 @@ class CourseMetadata:
|
||||
if (
|
||||
not user.is_staff and
|
||||
cls._has_requested_proctoring_provider_changed(
|
||||
descriptor.proctoring_provider, proctoring_provider_model.get('value')
|
||||
block.proctoring_provider, proctoring_provider_model.get('value')
|
||||
) and
|
||||
datetime.now(pytz.UTC) > descriptor.start
|
||||
datetime.now(pytz.UTC) > block.start
|
||||
):
|
||||
message = (
|
||||
'The proctoring provider cannot be modified after a course has started.'
|
||||
@@ -426,7 +426,7 @@ class CourseMetadata:
|
||||
# should only be allowed if the exams IDA is enabled for a course
|
||||
available_providers = get_available_providers()
|
||||
updated_provider = settings_dict.get('proctoring_provider', {}).get('value')
|
||||
if updated_provider == 'lti_external' and not exams_ida_enabled(descriptor.id):
|
||||
if updated_provider == 'lti_external' and not exams_ida_enabled(block.id):
|
||||
available_providers.remove('lti_external')
|
||||
error = InvalidProctoringProvider('lti_external', available_providers)
|
||||
errors.append({'key': 'proctoring_provider', 'message': str(error), 'model': proctoring_provider_model})
|
||||
@@ -435,7 +435,7 @@ class CourseMetadata:
|
||||
if enable_proctoring_model:
|
||||
enable_proctoring = enable_proctoring_model.get('value')
|
||||
else:
|
||||
enable_proctoring = descriptor.enable_proctored_exams
|
||||
enable_proctoring = block.enable_proctored_exams
|
||||
|
||||
if enable_proctoring:
|
||||
# Require a valid escalation email if Proctortrack is chosen as the proctoring provider
|
||||
@@ -443,12 +443,12 @@ class CourseMetadata:
|
||||
if escalation_email_model:
|
||||
escalation_email = escalation_email_model.get('value')
|
||||
else:
|
||||
escalation_email = descriptor.proctoring_escalation_email
|
||||
escalation_email = block.proctoring_escalation_email
|
||||
|
||||
if proctoring_provider_model:
|
||||
proctoring_provider = proctoring_provider_model.get('value')
|
||||
else:
|
||||
proctoring_provider = descriptor.proctoring_provider
|
||||
proctoring_provider = block.proctoring_provider
|
||||
|
||||
missing_escalation_email_msg = 'Provider \'{provider}\' requires an exam escalation contact.'
|
||||
if proctoring_provider_model and proctoring_provider == 'proctortrack':
|
||||
@@ -477,7 +477,7 @@ class CourseMetadata:
|
||||
if zendesk_ticket_model:
|
||||
create_zendesk_tickets = zendesk_ticket_model.get('value')
|
||||
else:
|
||||
create_zendesk_tickets = descriptor.create_zendesk_tickets
|
||||
create_zendesk_tickets = block.create_zendesk_tickets
|
||||
|
||||
if (
|
||||
(proctoring_provider == 'proctortrack' and create_zendesk_tickets)
|
||||
@@ -489,7 +489,7 @@ class CourseMetadata:
|
||||
'should be updated for this course.'.format(
|
||||
ticket_value=create_zendesk_tickets,
|
||||
provider=proctoring_provider,
|
||||
course_id=descriptor.id
|
||||
course_id=block.id
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user