refactor: rename descriptor -> block within cms

Co-authored-by: Agrendalath <piotr@surowiec.it>
This commit is contained in:
Pooja Kulkarni
2023-01-03 16:43:19 -05:00
committed by Agrendalath
parent a2f1ad1f11
commit ce13cd540a
18 changed files with 195 additions and 194 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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([

View File

@@ -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):

View File

@@ -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]

View File

@@ -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)

View File

@@ -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):

View File

@@ -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:

View File

@@ -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):

View File

@@ -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(

View File

@@ -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)

View File

@@ -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

View File

@@ -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',

View File

@@ -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'

View File

@@ -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'

View File

@@ -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.

View File

@@ -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

View File

@@ -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
)
)