Merge pull request #31472 from open-craft/kaustav/unify_modulesystem_descriptorsystem
feat: unify ModuleSystem and DescriptorSystem [BD-13]
This commit is contained in:
@@ -15,7 +15,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory
|
||||
from xmodule.tests.test_export import PureXBlock
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient
|
||||
from cms.djangoapps.contentstore.views.preview import _preview_module_system
|
||||
from cms.djangoapps.contentstore.views.preview import _prepare_runtime_for_preview
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from openedx.core.lib.edx_six import get_gettext
|
||||
|
||||
@@ -70,7 +70,7 @@ class TestXBlockI18nService(ModuleStoreTestCase):
|
||||
self.course = CourseFactory.create()
|
||||
self.field_data = mock.Mock()
|
||||
self.descriptor = BlockFactory(category="pure", parent=self.course)
|
||||
self.runtime = _preview_module_system(
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
self.descriptor,
|
||||
self.field_data,
|
||||
@@ -81,7 +81,7 @@ class TestXBlockI18nService(ModuleStoreTestCase):
|
||||
"""
|
||||
return the block i18n service.
|
||||
"""
|
||||
i18n_service = self.runtime.service(descriptor, 'i18n')
|
||||
i18n_service = self.descriptor.runtime.service(descriptor, 'i18n')
|
||||
self.assertIsNotNone(i18n_service)
|
||||
self.assertIsInstance(i18n_service, XBlockI18nService)
|
||||
return i18n_service
|
||||
@@ -171,7 +171,7 @@ class TestXBlockI18nService(ModuleStoreTestCase):
|
||||
"""
|
||||
Test: i18n service should be callable in studio.
|
||||
"""
|
||||
self.assertTrue(callable(self.runtime._services.get('i18n'))) # pylint: disable=protected-access
|
||||
self.assertTrue(callable(self.descriptor.runtime._services.get('i18n'))) # pylint: disable=protected-access
|
||||
|
||||
|
||||
class InternationalizationTest(ModuleStoreTestCase):
|
||||
|
||||
@@ -293,38 +293,23 @@ class StudioPermissionsService:
|
||||
return has_studio_write_access(self._user, course_key)
|
||||
|
||||
|
||||
class StudioEditModuleRuntime:
|
||||
def load_services_for_studio(runtime, user):
|
||||
"""
|
||||
An extremely minimal ModuleSystem shim used for XBlock edits and studio_view.
|
||||
(i.e. whenever we're not using PreviewModuleSystem.) This is required to make information
|
||||
Function to set some required services used for XBlock edits and studio_view.
|
||||
(i.e. whenever we're not loading _prepare_runtime_for_preview.) This is required to make information
|
||||
about the current user (especially permissions) available via services as needed.
|
||||
"""
|
||||
services = {
|
||||
"user": DjangoXBlockUserService(user),
|
||||
"studio_user_permissions": StudioPermissionsService(user),
|
||||
"mako": MakoService(),
|
||||
"settings": SettingsService(),
|
||||
"lti-configuration": ConfigurationService(CourseAllowPIISharingInLTIFlag),
|
||||
"teams_configuration": TeamsConfigurationService(),
|
||||
"library_tools": LibraryToolsService(modulestore(), user.id)
|
||||
}
|
||||
|
||||
def __init__(self, user):
|
||||
self._user = user
|
||||
|
||||
def service(self, block, service_name):
|
||||
"""
|
||||
This block is not bound to a user but some blocks (LibraryContentBlock) may need
|
||||
user-specific services to check for permissions, etc.
|
||||
If we return None here, CombinedSystem will load services from the descriptor runtime.
|
||||
"""
|
||||
if block.service_declaration(service_name) is not None:
|
||||
if service_name == "user":
|
||||
return DjangoXBlockUserService(self._user)
|
||||
if service_name == "studio_user_permissions":
|
||||
return StudioPermissionsService(self._user)
|
||||
if service_name == "mako":
|
||||
return MakoService()
|
||||
if service_name == "settings":
|
||||
return SettingsService()
|
||||
if service_name == "lti-configuration":
|
||||
return ConfigurationService(CourseAllowPIISharingInLTIFlag)
|
||||
if service_name == "teams_configuration":
|
||||
return TeamsConfigurationService()
|
||||
if service_name == "library_tools":
|
||||
return LibraryToolsService(modulestore(), self._user.id)
|
||||
return None
|
||||
runtime._services.update(services) # lint-amnesty, pylint: disable=protected-access
|
||||
|
||||
|
||||
@require_http_methods("GET")
|
||||
@@ -368,8 +353,8 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
))
|
||||
|
||||
if view_name in (STUDIO_VIEW, VISIBILITY_VIEW):
|
||||
if view_name == STUDIO_VIEW and xblock.xmodule_runtime is None:
|
||||
xblock.xmodule_runtime = StudioEditModuleRuntime(request.user)
|
||||
if view_name == STUDIO_VIEW:
|
||||
load_services_for_studio(xblock.runtime, request.user)
|
||||
|
||||
try:
|
||||
fragment = xblock.render(view_name)
|
||||
@@ -524,7 +509,7 @@ def _update_with_callback(xblock, user, old_metadata=None, old_content=None):
|
||||
old_metadata = own_metadata(xblock)
|
||||
if old_content is None:
|
||||
old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content)
|
||||
xblock.xmodule_runtime = StudioEditModuleRuntime(user)
|
||||
load_services_for_studio(xblock.runtime, user)
|
||||
xblock.editor_saved(user, old_metadata, old_content)
|
||||
|
||||
# Update after the callback so any changes made in the callback will get persisted.
|
||||
@@ -937,7 +922,7 @@ def _duplicate_block(parent_usage_key, duplicate_source_usage_key, user, display
|
||||
# Allow an XBlock to do anything fancy it may need to when duplicated from another block.
|
||||
# These blocks may handle their own children or parenting if needed. Let them return booleans to
|
||||
# let us know if we need to handle these or not.
|
||||
dest_block.xmodule_runtime = StudioEditModuleRuntime(user)
|
||||
load_services_for_studio(dest_block.runtime, user)
|
||||
children_handled = dest_block.studio_post_duplicate(store, source_item)
|
||||
|
||||
# Children are not automatically copied over (and not all xblocks have a 'children' attribute).
|
||||
|
||||
@@ -32,7 +32,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, py
|
||||
|
||||
from ..utils import get_lms_link_for_item, get_sibling_urls, reverse_course_url
|
||||
from .helpers import get_parent_xblock, is_unit, xblock_type_display_name
|
||||
from .block import StudioEditModuleRuntime, add_container_page_publishing_info, create_xblock_info
|
||||
from .block import add_container_page_publishing_info, create_xblock_info, load_services_for_studio
|
||||
|
||||
__all__ = [
|
||||
'container_handler',
|
||||
@@ -560,7 +560,7 @@ def component_handler(request, usage_key_string, handler, suffix=''):
|
||||
descriptor = modulestore().get_item(usage_key)
|
||||
handler_descriptor = descriptor
|
||||
asides = []
|
||||
handler_descriptor.xmodule_runtime = StudioEditModuleRuntime(request.user)
|
||||
load_services_for_studio(handler_descriptor.runtime, request.user)
|
||||
resp = handler_descriptor.handle(handler, req, suffix)
|
||||
except NoSuchHandlerError:
|
||||
log.info("XBlock %s attempted to access missing handler %r", handler_descriptor, handler, exc_info=True)
|
||||
|
||||
@@ -24,7 +24,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, ModuleSystem
|
||||
from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW
|
||||
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
|
||||
@@ -41,8 +41,7 @@ from openedx.core.lib.xblock_utils import (
|
||||
request_token,
|
||||
wrap_fragment,
|
||||
wrap_xblock,
|
||||
wrap_xblock_aside,
|
||||
xblock_local_resource_url
|
||||
wrap_xblock_aside
|
||||
)
|
||||
|
||||
from ..utils import get_visibility_partition_info
|
||||
@@ -94,74 +93,72 @@ def preview_handler(request, usage_key_string, handler, suffix=''):
|
||||
return webob_to_django_response(resp)
|
||||
|
||||
|
||||
class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
def handler_url(block, handler_name, suffix='', query='', thirdparty=False): # lint-amnesty, pylint: disable=unused-argument
|
||||
"""
|
||||
An XModule ModuleSystem for use in Studio previews
|
||||
Handler URL function for Preview
|
||||
"""
|
||||
# xblocks can check for this attribute during rendering to determine if
|
||||
# they are being rendered for preview (i.e. in Studio)
|
||||
is_author_mode = True
|
||||
|
||||
def handler_url(self, block, handler_name, suffix='', query='', thirdparty=False):
|
||||
return reverse('preview_handler', kwargs={
|
||||
'usage_key_string': str(block.scope_ids.usage_id),
|
||||
'handler': handler_name,
|
||||
'suffix': suffix,
|
||||
}) + '?' + query
|
||||
|
||||
def local_resource_url(self, block, uri):
|
||||
return xblock_local_resource_url(block, uri)
|
||||
|
||||
def applicable_aside_types(self, block):
|
||||
"""
|
||||
Remove acid_aside and honor the config record
|
||||
"""
|
||||
if not StudioConfig.asides_enabled(block.scope_ids.block_type):
|
||||
return []
|
||||
|
||||
# TODO: aside_type != 'acid_aside' check should be removed once AcidBlock is only installed during tests
|
||||
# (see https://openedx.atlassian.net/browse/TE-811)
|
||||
return [
|
||||
aside_type
|
||||
for aside_type in super().applicable_aside_types(block)
|
||||
if aside_type != 'acid_aside'
|
||||
]
|
||||
|
||||
def render_child_placeholder(self, block, view_name, context):
|
||||
"""
|
||||
Renders a placeholder XBlock.
|
||||
"""
|
||||
return self.wrap_xblock(block, view_name, Fragment(), context)
|
||||
|
||||
def layout_asides(self, block, context, frag, view_name, aside_frag_fns):
|
||||
position_for_asides = '<!-- footer for xblock_aside -->'
|
||||
result = Fragment()
|
||||
result.add_fragment_resources(frag)
|
||||
|
||||
for aside, aside_fn in aside_frag_fns:
|
||||
aside_frag = aside_fn(block, context)
|
||||
if aside_frag.content != '':
|
||||
aside_frag_wrapped = self.wrap_aside(block, aside, view_name, aside_frag, context)
|
||||
aside.save()
|
||||
result.add_fragment_resources(aside_frag_wrapped)
|
||||
replacement = position_for_asides + aside_frag_wrapped.content
|
||||
frag.content = frag.content.replace(position_for_asides, replacement)
|
||||
|
||||
result.add_content(frag.content)
|
||||
return result
|
||||
return reverse('preview_handler', kwargs={
|
||||
'usage_key_string': str(block.scope_ids.usage_id),
|
||||
'handler': handler_name,
|
||||
'suffix': suffix,
|
||||
}) + '?' + query
|
||||
|
||||
|
||||
def _preview_module_system(request, descriptor, field_data):
|
||||
def preview_applicable_aside_types(block, applicable_aside_types=None):
|
||||
"""
|
||||
Returns a ModuleSystem for the specified descriptor that is specialized for
|
||||
rendering block previews.
|
||||
Remove acid_aside and honor the config record
|
||||
"""
|
||||
if not StudioConfig.asides_enabled(block.scope_ids.block_type):
|
||||
return []
|
||||
|
||||
# TODO: aside_type != 'acid_aside' check should be removed once AcidBlock is only installed during tests
|
||||
# (see https://openedx.atlassian.net/browse/TE-811)
|
||||
return [
|
||||
aside_type
|
||||
for aside_type in applicable_aside_types(block)
|
||||
if aside_type != 'acid_aside'
|
||||
]
|
||||
|
||||
|
||||
def render_child_placeholder(block, view_name, context, wrap_block=None):
|
||||
"""
|
||||
Renders a placeholder XBlock.
|
||||
"""
|
||||
return wrap_block(block, view_name, Fragment(), context)
|
||||
|
||||
|
||||
def preview_layout_asides(block, context, frag, view_name, aside_frag_fns, wrap_aside=None):
|
||||
"""
|
||||
Custom layout of asides for preview
|
||||
"""
|
||||
position_for_asides = '<!-- footer for xblock_aside -->'
|
||||
result = Fragment()
|
||||
result.add_fragment_resources(frag)
|
||||
|
||||
for aside, aside_fn in aside_frag_fns:
|
||||
aside_frag = aside_fn(block, context)
|
||||
if aside_frag.content != '':
|
||||
aside_frag_wrapped = wrap_aside(block, aside, view_name, aside_frag, context)
|
||||
aside.save()
|
||||
result.add_fragment_resources(aside_frag_wrapped)
|
||||
replacement = position_for_asides + aside_frag_wrapped.content
|
||||
frag.content = frag.content.replace(position_for_asides, replacement)
|
||||
|
||||
result.add_content(frag.content)
|
||||
return result
|
||||
|
||||
|
||||
def _prepare_runtime_for_preview(request, block, field_data):
|
||||
"""
|
||||
Sets properties in the runtime of the specified block that is
|
||||
required for rendering block previews.
|
||||
|
||||
request: The active django request
|
||||
descriptor: An XModuleDescriptor
|
||||
field_data: Wrapped field data for previews
|
||||
"""
|
||||
|
||||
course_id = descriptor.location.course_key
|
||||
display_name_only = (descriptor.category == 'static_tab')
|
||||
course_id = block.location.course_key
|
||||
display_name_only = (block.category == 'static_tab')
|
||||
|
||||
replace_url_service = ReplaceURLService(course_id=course_id)
|
||||
|
||||
@@ -201,36 +198,48 @@ def _preview_module_system(request, descriptor, field_data):
|
||||
# the anonymous_user_id to specific courses. These are captured in the
|
||||
# block attribute 'requires_per_student_anonymous_id'. Please note,
|
||||
# the course_id field in AnynomousUserID model is blank if value is None.
|
||||
if getattr(descriptor, 'requires_per_student_anonymous_id', False):
|
||||
if getattr(block, 'requires_per_student_anonymous_id', False):
|
||||
preview_anonymous_user_id = anonymous_id_for_user(request.user, None)
|
||||
else:
|
||||
preview_anonymous_user_id = anonymous_id_for_user(request.user, course_id)
|
||||
|
||||
return PreviewModuleSystem(
|
||||
get_block=partial(_load_preview_block, request),
|
||||
mixins=settings.XBLOCK_MIXINS,
|
||||
services = {
|
||||
"field-data": field_data,
|
||||
"i18n": XBlockI18nService,
|
||||
'mako': mako_service,
|
||||
"settings": SettingsService(),
|
||||
"user": DjangoXBlockUserService(
|
||||
request.user,
|
||||
user_role=get_user_role(request.user, course_id),
|
||||
anonymous_user_id=preview_anonymous_user_id,
|
||||
),
|
||||
"partitions": StudioPartitionService(course_id=course_id),
|
||||
"teams_configuration": TeamsConfigurationService(),
|
||||
"sandbox": SandboxService(contentstore=contentstore, course_id=course_id),
|
||||
"cache": CacheService(cache),
|
||||
'replace_urls': replace_url_service
|
||||
}
|
||||
|
||||
# Set up functions to modify the fragment produced by student_view
|
||||
wrappers=wrappers,
|
||||
wrappers_asides=wrappers_asides,
|
||||
# Get the raw DescriptorSystem, not the CombinedSystem
|
||||
descriptor_runtime=descriptor._runtime, # pylint: disable=protected-access
|
||||
services={
|
||||
"field-data": field_data,
|
||||
"i18n": XBlockI18nService,
|
||||
'mako': mako_service,
|
||||
"settings": SettingsService(),
|
||||
"user": DjangoXBlockUserService(
|
||||
request.user,
|
||||
user_role=get_user_role(request.user, course_id),
|
||||
anonymous_user_id=preview_anonymous_user_id,
|
||||
),
|
||||
"partitions": StudioPartitionService(course_id=course_id),
|
||||
"teams_configuration": TeamsConfigurationService(),
|
||||
"sandbox": SandboxService(contentstore=contentstore, course_id=course_id),
|
||||
"cache": CacheService(cache),
|
||||
'replace_urls': replace_url_service
|
||||
},
|
||||
block.runtime.get_block_for_descriptor = partial(_load_preview_block, request)
|
||||
block.runtime.mixins = settings.XBLOCK_MIXINS
|
||||
|
||||
# Set up functions to modify the fragment produced by student_view
|
||||
block.runtime.wrappers = wrappers
|
||||
block.runtime.wrappers_asides = wrappers_asides
|
||||
block.runtime._runtime_services.update(services) # lint-amnesty, pylint: disable=protected-access
|
||||
|
||||
# xmodules can check for this attribute during rendering to determine if
|
||||
# they are being rendered for preview (i.e. in Studio)
|
||||
block.runtime.is_author_mode = True
|
||||
block.runtime.handler_url_override = handler_url
|
||||
block.runtime.applicable_aside_types_override = preview_applicable_aside_types
|
||||
block.runtime.render_child_placeholder = partial(
|
||||
render_child_placeholder,
|
||||
wrap_block=block.runtime.wrap_xblock
|
||||
)
|
||||
block.runtime.layout_asides_override = partial(
|
||||
preview_layout_asides,
|
||||
wrap_aside=block.runtime.wrap_aside
|
||||
)
|
||||
|
||||
|
||||
@@ -261,12 +270,11 @@ def _load_preview_block(request, descriptor):
|
||||
else:
|
||||
wrapper = partial(LmsFieldData, student_data=student_data)
|
||||
|
||||
# wrap the _field_data upfront to pass to _preview_module_system
|
||||
# wrap the _field_data upfront to pass to _prepare_runtime_for_preview
|
||||
wrapped_field_data = wrapper(descriptor._field_data) # pylint: disable=protected-access
|
||||
preview_runtime = _preview_module_system(request, descriptor, wrapped_field_data)
|
||||
_prepare_runtime_for_preview(request, descriptor, wrapped_field_data)
|
||||
|
||||
descriptor.bind_for_student(
|
||||
preview_runtime,
|
||||
request.user.id,
|
||||
[wrapper]
|
||||
)
|
||||
|
||||
@@ -2128,8 +2128,7 @@ class TestEditSplitModule(ItemTest):
|
||||
group_id_to_child = split_test.group_id_to_child.copy()
|
||||
self.assertEqual(2, len(group_id_to_child))
|
||||
|
||||
# Test environment and Studio use different module systems
|
||||
# (CachingDescriptorSystem is used in tests, PreviewModuleSystem in Studio).
|
||||
# CachingDescriptorSystem is used in tests.
|
||||
# CachingDescriptorSystem doesn't have user service, that's needed for
|
||||
# SplitTestBlock. So, in this line of code we add this service manually.
|
||||
split_test.runtime._services['user'] = DjangoXBlockUserService(self.user) # pylint: disable=protected-access
|
||||
|
||||
@@ -26,7 +26,7 @@ from cms.djangoapps.xblock_config.models import StudioConfig
|
||||
from common.djangoapps import static_replace
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
|
||||
from ..preview import _preview_module_system, get_preview_fragment
|
||||
from ..preview import _prepare_runtime_for_preview, get_preview_fragment
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -214,12 +214,12 @@ class StudioXBlockServiceBindingTest(ModuleStoreTestCase):
|
||||
Tests that the 'user' and 'i18n' services are provided by the Studio runtime.
|
||||
"""
|
||||
descriptor = BlockFactory(category="pure", parent=self.course)
|
||||
runtime = _preview_module_system(
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
descriptor,
|
||||
self.field_data,
|
||||
)
|
||||
service = runtime.service(descriptor, expected_service)
|
||||
service = descriptor.runtime.service(descriptor, expected_service)
|
||||
self.assertIsNotNone(service)
|
||||
|
||||
|
||||
@@ -245,15 +245,16 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
self.descriptor = BlockFactory(category="video", parent=course)
|
||||
self.field_data = mock.Mock()
|
||||
self.contentstore = contentstore()
|
||||
self.runtime = _preview_module_system(
|
||||
self.descriptor = BlockFactory(category="problem", parent=course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
descriptor=BlockFactory(category="problem", parent=course),
|
||||
block=self.descriptor,
|
||||
field_data=mock.Mock(),
|
||||
)
|
||||
self.course = self.store.get_item(course.location)
|
||||
|
||||
def test_get_user_role(self):
|
||||
assert self.runtime.get_user_role() == 'staff'
|
||||
assert self.descriptor.runtime.get_user_role() == 'staff'
|
||||
|
||||
@XBlock.register_temp_plugin(PureXBlock, identifier='pure')
|
||||
def test_render_template(self):
|
||||
@@ -263,10 +264,10 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
|
||||
@override_settings(COURSES_WITH_UNSAFE_CODE=[r'course-v1:edX\+LmsModuleShimTest\+2021_Fall'])
|
||||
def test_can_execute_unsafe_code(self):
|
||||
assert self.runtime.can_execute_unsafe_code()
|
||||
assert self.descriptor.runtime.can_execute_unsafe_code()
|
||||
|
||||
def test_cannot_execute_unsafe_code(self):
|
||||
assert not self.runtime.can_execute_unsafe_code()
|
||||
assert not self.descriptor.runtime.can_execute_unsafe_code()
|
||||
|
||||
@override_settings(PYTHON_LIB_FILENAME=PYTHON_LIB_FILENAME)
|
||||
def test_get_python_lib_zip(self):
|
||||
@@ -276,7 +277,7 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
source_file=self.PYTHON_LIB_SOURCE_FILE,
|
||||
target_filename=self.PYTHON_LIB_FILENAME,
|
||||
)
|
||||
assert self.runtime.get_python_lib_zip() == zipfile
|
||||
assert self.descriptor.runtime.get_python_lib_zip() == zipfile
|
||||
|
||||
def test_no_get_python_lib_zip(self):
|
||||
zipfile = upload_file_to_course(
|
||||
@@ -285,38 +286,40 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
source_file=self.PYTHON_LIB_SOURCE_FILE,
|
||||
target_filename=self.PYTHON_LIB_FILENAME,
|
||||
)
|
||||
assert self.runtime.get_python_lib_zip() is None
|
||||
assert self.descriptor.runtime.get_python_lib_zip() is None
|
||||
|
||||
def test_cache(self):
|
||||
assert hasattr(self.runtime.cache, 'get')
|
||||
assert hasattr(self.runtime.cache, 'set')
|
||||
assert hasattr(self.descriptor.runtime.cache, 'get')
|
||||
assert hasattr(self.descriptor.runtime.cache, 'set')
|
||||
|
||||
def test_replace_urls(self):
|
||||
html = '<a href="/static/id">'
|
||||
assert self.runtime.replace_urls(html) == \
|
||||
assert self.descriptor.runtime.replace_urls(html) == \
|
||||
static_replace.replace_static_urls(html, course_id=self.course.id)
|
||||
|
||||
def test_anonymous_user_id_preview(self):
|
||||
assert self.runtime.anonymous_student_id == 'student'
|
||||
assert self.descriptor.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.
|
||||
runtime = _preview_module_system(
|
||||
descriptor = BlockFactory(category="problem", parent=self.course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
descriptor=BlockFactory(category="problem", parent=self.course),
|
||||
block=descriptor,
|
||||
field_data=mock.Mock(),
|
||||
)
|
||||
assert runtime.anonymous_student_id == '26262401c528d7c4a6bbeabe0455ec46'
|
||||
assert descriptor.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.
|
||||
runtime = _preview_module_system(
|
||||
descriptor = BlockFactory(category="lti", parent=self.course)
|
||||
_prepare_runtime_for_preview(
|
||||
self.request,
|
||||
descriptor=BlockFactory(category="lti", parent=self.course),
|
||||
block=descriptor,
|
||||
field_data=mock.Mock(),
|
||||
)
|
||||
assert runtime.anonymous_student_id == 'ad503f629b55c531fed2e45aa17a3368'
|
||||
assert descriptor.runtime.anonymous_student_id == 'ad503f629b55c531fed2e45aa17a3368'
|
||||
|
||||
@@ -67,7 +67,7 @@ from lms.djangoapps.courseware.field_overrides import OverrideFieldData
|
||||
from lms.djangoapps.courseware.services import UserStateService
|
||||
from lms.djangoapps.grades.api import GradesUtilService
|
||||
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
|
||||
from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem, UserTagsService
|
||||
from lms.djangoapps.lms_xblock.runtime import UserTagsService, lms_wrappers_aside, lms_applicable_aside_types
|
||||
from lms.djangoapps.verify_student.services import XBlockVerificationService
|
||||
from openedx.core.djangoapps.bookmarks.api import BookmarksService
|
||||
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
|
||||
@@ -115,7 +115,7 @@ class LmsModuleRenderError(Exception):
|
||||
def make_track_function(request):
|
||||
'''
|
||||
Make a tracking function that logs what happened.
|
||||
For use in ModuleSystem.
|
||||
For use in DescriptorSystem.
|
||||
'''
|
||||
from common.djangoapps.track import views as track_views
|
||||
|
||||
@@ -403,11 +403,11 @@ def get_block_for_descriptor(user, request, descriptor, field_data_cache, course
|
||||
)
|
||||
|
||||
|
||||
def get_module_system_for_user(
|
||||
def prepare_runtime_for_user(
|
||||
user,
|
||||
student_data, # TODO
|
||||
# Arguments preceding this comment have user binding, those following don't
|
||||
descriptor,
|
||||
block,
|
||||
course_id,
|
||||
track_function,
|
||||
request_token,
|
||||
@@ -421,14 +421,13 @@ def get_module_system_for_user(
|
||||
will_recheck_access=False,
|
||||
):
|
||||
"""
|
||||
Helper function that returns a module system and student_data bound to a user and a descriptor.
|
||||
Helper function that binds the given xblock to a user and student_data to a user and the block.
|
||||
|
||||
The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module,
|
||||
to allow an existing block to be re-bound to a user. Most of the user bindings happen when creating the
|
||||
closures that feed the instantiation of ModuleSystem.
|
||||
to allow an existing block to be re-bound to a user.
|
||||
|
||||
The arguments fall into two categories: those that have explicit or implicit user binding, which are user
|
||||
and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which
|
||||
and student_data, and those don't and are used to instantiate the service required in LMS, which
|
||||
are all the other arguments. Ultimately, this isn't too different than how get_block_for_descriptor_internal
|
||||
was before refactoring.
|
||||
|
||||
@@ -437,7 +436,7 @@ def get_module_system_for_user(
|
||||
request_token (str): A token unique to the request use by xblock initialization
|
||||
|
||||
Returns:
|
||||
(LmsModuleSystem, KvsFieldData): (module system, student_data) bound to, primarily, the user and descriptor
|
||||
KvsFieldData: student_data bound to, primarily, the user and block
|
||||
"""
|
||||
|
||||
def make_xqueue_callback(dispatch='score_update'):
|
||||
@@ -449,7 +448,7 @@ def get_module_system_for_user(
|
||||
kwargs=dict(
|
||||
course_id=str(course_id),
|
||||
userid=str(user.id),
|
||||
mod_id=str(descriptor.location),
|
||||
mod_id=str(block.location),
|
||||
dispatch=dispatch
|
||||
),
|
||||
)
|
||||
@@ -459,7 +458,7 @@ def get_module_system_for_user(
|
||||
# Default queuename is course-specific and is derived from the course that
|
||||
# contains the current block.
|
||||
# TODO: Queuename should be derived from 'course_settings.json' of each course
|
||||
xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course
|
||||
xqueue_default_queuename = block.location.org + '-' + block.location.course
|
||||
|
||||
xqueue_service = XQueueService(
|
||||
construct_callback=make_xqueue_callback,
|
||||
@@ -500,12 +499,12 @@ def get_module_system_for_user(
|
||||
# while giving selected modules a per-course anonymized id.
|
||||
# As we have the time to manually test more modules, we can add to the list
|
||||
# of modules that get the per-course anonymized id.
|
||||
if getattr(descriptor, 'requires_per_student_anonymous_id', False):
|
||||
if getattr(block, 'requires_per_student_anonymous_id', False):
|
||||
anonymous_student_id = anonymous_id_for_user(user, None)
|
||||
else:
|
||||
anonymous_student_id = anonymous_id_for_user(user, course_id)
|
||||
|
||||
user_is_staff = bool(has_access(user, 'staff', descriptor.location, course_id))
|
||||
user_is_staff = bool(has_access(user, 'staff', block.location, course_id))
|
||||
user_service = DjangoXBlockUserService(
|
||||
user,
|
||||
user_is_staff=user_is_staff,
|
||||
@@ -518,7 +517,7 @@ def get_module_system_for_user(
|
||||
rebind_user_service = RebindUserService(
|
||||
user,
|
||||
course_id,
|
||||
get_module_system_for_user,
|
||||
prepare_runtime_for_user,
|
||||
track_function=track_function,
|
||||
position=position,
|
||||
wrap_xblock_display=wrap_xblock_display,
|
||||
@@ -552,9 +551,9 @@ def get_module_system_for_user(
|
||||
))
|
||||
|
||||
replace_url_service = ReplaceURLService(
|
||||
data_directory=getattr(descriptor, 'data_dir', None),
|
||||
data_directory=getattr(block, 'data_dir', None),
|
||||
course_id=course_id,
|
||||
static_asset_path=static_asset_path or descriptor.static_asset_path,
|
||||
static_asset_path=static_asset_path or block.static_asset_path,
|
||||
jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': str(course_id), 'module_id': ''})
|
||||
)
|
||||
|
||||
@@ -578,58 +577,58 @@ def get_module_system_for_user(
|
||||
del user.real_user.masquerade_settings
|
||||
user.real_user.masquerade_settings = masquerade_settings
|
||||
else:
|
||||
staff_access = has_access(user, 'staff', descriptor, course_id)
|
||||
staff_access = has_access(user, 'staff', block, course_id)
|
||||
if staff_access:
|
||||
block_wrappers.append(partial(add_staff_markup, user, disable_staff_debug_info))
|
||||
|
||||
field_data = DateLookupFieldData(descriptor._field_data, course_id, user) # pylint: disable=protected-access
|
||||
field_data = DateLookupFieldData(block._field_data, course_id, user) # pylint: disable=protected-access
|
||||
field_data = LmsFieldData(field_data, student_data)
|
||||
|
||||
store = modulestore()
|
||||
|
||||
system = LmsModuleSystem(
|
||||
get_block=inner_get_block,
|
||||
# TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
|
||||
mixins=descriptor.runtime.mixologist._mixins, # pylint: disable=protected-access
|
||||
wrappers=block_wrappers,
|
||||
services={
|
||||
'fs': FSService(),
|
||||
'field-data': field_data,
|
||||
'mako': mako_service,
|
||||
'user': user_service,
|
||||
'verification': XBlockVerificationService(),
|
||||
'proctoring': ProctoringService(),
|
||||
'milestones': milestones_helpers.get_service(),
|
||||
'credit': CreditService(),
|
||||
'bookmarks': BookmarksService(user=user),
|
||||
'gating': GatingService(),
|
||||
'grade_utils': GradesUtilService(course_id=course_id),
|
||||
'user_state': UserStateService(),
|
||||
'content_type_gating': ContentTypeGatingService(),
|
||||
'cache': CacheService(cache),
|
||||
'sandbox': SandboxService(contentstore=contentstore, course_id=course_id),
|
||||
'xqueue': xqueue_service,
|
||||
'replace_urls': replace_url_service,
|
||||
'rebind_user': rebind_user_service,
|
||||
'completion': CompletionService(user=user, context_key=course_id)
|
||||
if user and user.is_authenticated
|
||||
else None,
|
||||
'i18n': XBlockI18nService,
|
||||
'library_tools': LibraryToolsService(store, user_id=user.id if user else None),
|
||||
'partitions': PartitionService(course_id=course_id, cache=DEFAULT_REQUEST_CACHE.data),
|
||||
'settings': SettingsService(),
|
||||
'user_tags': UserTagsService(user=user, course_id=course_id),
|
||||
'badging': BadgingService(course_id=course_id, modulestore=store) if badges_enabled() else None,
|
||||
'teams': TeamsService(),
|
||||
'teams_configuration': TeamsConfigurationService(),
|
||||
'call_to_action': CallToActionService(),
|
||||
'publish': EventPublishingService(user, course_id, track_function),
|
||||
},
|
||||
descriptor_runtime=descriptor._runtime, # pylint: disable=protected-access
|
||||
request_token=request_token,
|
||||
)
|
||||
services = {
|
||||
'fs': FSService(),
|
||||
'field-data': field_data,
|
||||
'mako': mako_service,
|
||||
'user': user_service,
|
||||
'verification': XBlockVerificationService(),
|
||||
'proctoring': ProctoringService(),
|
||||
'milestones': milestones_helpers.get_service(),
|
||||
'credit': CreditService(),
|
||||
'bookmarks': BookmarksService(user=user),
|
||||
'gating': GatingService(),
|
||||
'grade_utils': GradesUtilService(course_id=course_id),
|
||||
'user_state': UserStateService(),
|
||||
'content_type_gating': ContentTypeGatingService(),
|
||||
'cache': CacheService(cache),
|
||||
'sandbox': SandboxService(contentstore=contentstore, course_id=course_id),
|
||||
'xqueue': xqueue_service,
|
||||
'replace_urls': replace_url_service,
|
||||
'rebind_user': rebind_user_service,
|
||||
'completion': CompletionService(user=user, context_key=course_id)
|
||||
if user and user.is_authenticated
|
||||
else None,
|
||||
'i18n': XBlockI18nService,
|
||||
'library_tools': LibraryToolsService(store, user_id=user.id if user else None),
|
||||
'partitions': PartitionService(course_id=course_id, cache=DEFAULT_REQUEST_CACHE.data),
|
||||
'settings': SettingsService(),
|
||||
'user_tags': UserTagsService(user=user, course_id=course_id),
|
||||
'badging': BadgingService(course_id=course_id, modulestore=store) if badges_enabled() else None,
|
||||
'teams': TeamsService(),
|
||||
'teams_configuration': TeamsConfigurationService(),
|
||||
'call_to_action': CallToActionService(),
|
||||
'publish': EventPublishingService(user, course_id, track_function),
|
||||
}
|
||||
|
||||
# pass position specified in URL to module through ModuleSystem
|
||||
block.runtime.get_block_for_descriptor = inner_get_block
|
||||
|
||||
block.runtime.wrappers = block_wrappers
|
||||
block.runtime._runtime_services.update(services) # lint-amnesty, pylint: disable=protected-access
|
||||
block.runtime.request_token = request_token
|
||||
block.runtime.wrap_asides_override = lms_wrappers_aside
|
||||
block.runtime.applicable_aside_types_override = lms_applicable_aside_types
|
||||
|
||||
# pass position specified in URL to runtime
|
||||
if position is not None:
|
||||
try:
|
||||
position = int(position)
|
||||
@@ -637,14 +636,14 @@ def get_module_system_for_user(
|
||||
log.exception('Non-integer %r passed as position.', position)
|
||||
position = None
|
||||
|
||||
system.set('position', position)
|
||||
block.runtime.set('position', position)
|
||||
|
||||
system.set('user_is_staff', user_is_staff)
|
||||
system.set('user_is_admin', bool(has_access(user, 'staff', 'global')))
|
||||
system.set('user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user))
|
||||
system.set('days_early_for_beta', descriptor.days_early_for_beta)
|
||||
block.runtime.set('user_is_staff', user_is_staff)
|
||||
block.runtime.set('user_is_admin', bool(has_access(user, 'staff', 'global')))
|
||||
block.runtime.set('user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user))
|
||||
block.runtime.set('days_early_for_beta', block.days_early_for_beta)
|
||||
|
||||
return system, field_data
|
||||
return field_data
|
||||
|
||||
|
||||
# TODO: Find all the places that this method is called and figure out how to
|
||||
@@ -662,10 +661,10 @@ def get_block_for_descriptor_internal(user, descriptor, student_data, course_id,
|
||||
request_token (str): A unique token for this request, used to isolate xblock rendering
|
||||
"""
|
||||
|
||||
(system, student_data) = get_module_system_for_user(
|
||||
student_data = prepare_runtime_for_user(
|
||||
user=user,
|
||||
student_data=student_data, # These have implicit user bindings, the rest of args are considered not to
|
||||
descriptor=descriptor,
|
||||
block=descriptor,
|
||||
course_id=course_id,
|
||||
track_function=track_function,
|
||||
position=position,
|
||||
@@ -680,7 +679,6 @@ def get_block_for_descriptor_internal(user, descriptor, student_data, course_id,
|
||||
)
|
||||
|
||||
descriptor.bind_for_student(
|
||||
system,
|
||||
user.id,
|
||||
[
|
||||
partial(DateLookupFieldData, course_id=course_id, user=user),
|
||||
|
||||
@@ -33,7 +33,7 @@ from common.djangoapps.util.date_utils import strftime_localized_html
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_MODULESTORE, ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.tests import get_test_descriptor_system, get_test_system # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.tests import get_test_descriptor_system, get_test_system, prepare_block_runtime # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
class BaseTestXmodule(ModuleStoreTestCase):
|
||||
@@ -66,10 +66,12 @@ class BaseTestXmodule(ModuleStoreTestCase):
|
||||
METADATA = {}
|
||||
MODEL_DATA = {'data': '<some_module></some_module>'}
|
||||
|
||||
def new_module_runtime(self, **kwargs):
|
||||
def new_module_runtime(self, runtime=None, **kwargs):
|
||||
"""
|
||||
Generate a new ModuleSystem that is minimally set up for testing
|
||||
Generate a new DescriptorSystem that is minimally set up for testing
|
||||
"""
|
||||
if runtime:
|
||||
return prepare_block_runtime(runtime, course_id=self.course.id, **kwargs)
|
||||
return get_test_system(course_id=self.course.id, **kwargs)
|
||||
|
||||
def new_descriptor_runtime(self, **kwargs):
|
||||
@@ -94,7 +96,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
|
||||
|
||||
if runtime_kwargs is None:
|
||||
runtime_kwargs = {}
|
||||
self.item_descriptor.xmodule_runtime = self.new_module_runtime(**runtime_kwargs)
|
||||
self.new_module_runtime(runtime=self.item_descriptor.runtime, **runtime_kwargs)
|
||||
|
||||
self.item_url = str(self.item_descriptor.location)
|
||||
|
||||
@@ -148,6 +150,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
|
||||
|
||||
class XModuleRenderingTestBase(BaseTestXmodule): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
|
||||
# lint-amnesty, pylint: disable=arguments-differ
|
||||
def new_module_runtime(self, **kwargs):
|
||||
"""
|
||||
Create a runtime that actually does html rendering
|
||||
|
||||
@@ -37,7 +37,7 @@ from xblock.core import XBlock, XBlockAside # lint-amnesty, pylint: disable=wro
|
||||
from xblock.exceptions import NoSuchServiceError
|
||||
from xblock.field_data import FieldData # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.fields import ScopeIds # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.runtime import DictKeyValueStore, KvsFieldData, Runtime # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.runtime import DictKeyValueStore, KvsFieldData # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xblock.test.tools import TestRuntime # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
from xmodule.capa.tests.response_xml_factory import OptionResponseXMLFactory # lint-amnesty, pylint: disable=reimported
|
||||
@@ -58,7 +58,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, Toy
|
||||
from xmodule.modulestore.tests.test_asides import AsideTestType # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.services import RebindUserServiceError
|
||||
from xmodule.video_block import VideoBlock # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.x_module import STUDENT_VIEW, CombinedSystem # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.x_module import STUDENT_VIEW, DescriptorSystem # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from common.djangoapps import static_replace
|
||||
from common.djangoapps.course_modes.models import CourseMode # lint-amnesty, pylint: disable=reimported
|
||||
from common.djangoapps.student.tests.factories import GlobalStaffFactory
|
||||
@@ -1894,9 +1894,10 @@ class TestAnonymousStudentId(SharedModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
location=location,
|
||||
static_asset_path=None,
|
||||
_runtime=Mock(
|
||||
spec=Runtime,
|
||||
spec=DescriptorSystem,
|
||||
resources_fs=None,
|
||||
mixologist=Mock(_mixins=(), name='mixologist'),
|
||||
_services={},
|
||||
name='runtime',
|
||||
),
|
||||
scope_ids=Mock(spec=ScopeIds),
|
||||
@@ -1906,7 +1907,7 @@ class TestAnonymousStudentId(SharedModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
fields={},
|
||||
days_early_for_beta=None,
|
||||
)
|
||||
descriptor.runtime = CombinedSystem(descriptor._runtime, None) # pylint: disable=protected-access
|
||||
descriptor.runtime = DescriptorSystem(None, None, None)
|
||||
# Use the xblock_class's bind_for_student method
|
||||
descriptor.bind_for_student = partial(xblock_class.bind_for_student, descriptor)
|
||||
|
||||
@@ -1922,7 +1923,7 @@ class TestAnonymousStudentId(SharedModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
request_token='request_token',
|
||||
course=self.course,
|
||||
)
|
||||
current_user = block.xmodule_runtime.service(block, 'user').get_current_user()
|
||||
current_user = block.runtime.service(block, 'user').get_current_user()
|
||||
return current_user.opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
|
||||
|
||||
@ddt.data(*PER_STUDENT_ANONYMIZED_XBLOCKS)
|
||||
@@ -1976,7 +1977,7 @@ class TestModuleTrackingContext(SharedModuleStoreTestCase):
|
||||
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
|
||||
@patch('xmodule.modulestore.mongo.base.CachingDescriptorSystem.applicable_aside_types',
|
||||
lambda self, block: ['test_aside'])
|
||||
@patch('lms.djangoapps.lms_xblock.runtime.LmsModuleSystem.applicable_aside_types',
|
||||
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types',
|
||||
lambda self, block: ['test_aside'])
|
||||
def test_context_contains_aside_info(self, mock_tracker):
|
||||
"""
|
||||
@@ -2185,7 +2186,7 @@ class TestRebindBlock(TestSubmittingProblems):
|
||||
# Bind the block to another student, which will remove "correct_map"
|
||||
# from the block's _field_data_cache and _dirty_fields.
|
||||
user2 = UserFactory.create()
|
||||
block.bind_for_student(block.runtime, user2.id)
|
||||
block.bind_for_student(user2.id)
|
||||
|
||||
# XBlock's save method assumes that if a field is in _dirty_fields,
|
||||
# then it's also in _field_data_cache. If this assumption
|
||||
@@ -2194,7 +2195,7 @@ class TestRebindBlock(TestSubmittingProblems):
|
||||
# _field_data cache, but not _dirty_fields, when we bound
|
||||
# this block to the second student. (TNL-2640)
|
||||
user3 = UserFactory.create()
|
||||
block.bind_for_student(block.runtime, user3.id)
|
||||
block.bind_for_student(user3.id)
|
||||
|
||||
def test_rebind_noauth_block_to_user_not_anonymous(self):
|
||||
"""
|
||||
@@ -2263,13 +2264,13 @@ class TestEventPublishing(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
|
||||
class LMSXBlockServiceMixin(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Helper class that initializes the LmsModuleSystem.
|
||||
Helper class that initializes the runtime.
|
||||
"""
|
||||
def _prepare_runtime(self):
|
||||
"""
|
||||
Instantiate the LmsModuleSystem.
|
||||
Instantiate the runtem.
|
||||
"""
|
||||
self.runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2333,7 +2334,7 @@ class LMSXBlockServiceBindingTest(LMSXBlockServiceMixin):
|
||||
"""
|
||||
Tests that the 'user', 'i18n', and 'fs' services are provided by the LMS runtime.
|
||||
"""
|
||||
service = self.runtime.service(self.descriptor, expected_service)
|
||||
service = self.descriptor.runtime.service(self.descriptor, expected_service)
|
||||
assert service is not None
|
||||
|
||||
def test_beta_tester_fields_added(self):
|
||||
@@ -2344,8 +2345,8 @@ class LMSXBlockServiceBindingTest(LMSXBlockServiceMixin):
|
||||
self._prepare_runtime()
|
||||
|
||||
# pylint: disable=no-member
|
||||
assert not self.runtime.user_is_beta_tester
|
||||
assert self.runtime.days_early_for_beta == 5
|
||||
assert not self.descriptor.runtime.user_is_beta_tester
|
||||
assert self.descriptor.runtime.days_early_for_beta == 5
|
||||
|
||||
def test_get_set_tag(self):
|
||||
"""
|
||||
@@ -2355,23 +2356,23 @@ class LMSXBlockServiceBindingTest(LMSXBlockServiceMixin):
|
||||
key = 'key1'
|
||||
|
||||
# test for when we haven't set the tag yet
|
||||
tag = self.runtime.service(self.descriptor, 'user_tags').get_tag(scope, key)
|
||||
tag = self.descriptor.runtime.service(self.descriptor, 'user_tags').get_tag(scope, key)
|
||||
assert tag is None
|
||||
|
||||
# set the tag
|
||||
set_value = 'value'
|
||||
self.runtime.service(self.descriptor, 'user_tags').set_tag(scope, key, set_value)
|
||||
tag = self.runtime.service(self.descriptor, 'user_tags').get_tag(scope, key)
|
||||
self.descriptor.runtime.service(self.descriptor, 'user_tags').set_tag(scope, key, set_value)
|
||||
tag = self.descriptor.runtime.service(self.descriptor, 'user_tags').get_tag(scope, key)
|
||||
|
||||
assert tag == set_value
|
||||
|
||||
# Try to set tag in wrong scope
|
||||
with pytest.raises(ValueError):
|
||||
self.runtime.service(self.descriptor, 'user_tags').set_tag('fake_scope', key, set_value)
|
||||
self.descriptor.runtime.service(self.descriptor, 'user_tags').set_tag('fake_scope', key, set_value)
|
||||
|
||||
# Try to get tag in wrong scope
|
||||
with pytest.raises(ValueError):
|
||||
self.runtime.service(self.descriptor, 'user_tags').get_tag('fake_scope', key)
|
||||
self.descriptor.runtime.service(self.descriptor, 'user_tags').get_tag('fake_scope', key)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -2381,23 +2382,23 @@ class TestBadgingService(LMSXBlockServiceMixin):
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
|
||||
def test_service_rendered(self):
|
||||
self._prepare_runtime()
|
||||
assert self.runtime.service(self.descriptor, 'badging')
|
||||
assert self.descriptor.runtime.service(self.descriptor, 'badging')
|
||||
|
||||
def test_no_service_rendered(self):
|
||||
with pytest.raises(NoSuchServiceError):
|
||||
self.runtime.service(self.descriptor, 'badging')
|
||||
self.descriptor.runtime.service(self.descriptor, 'badging')
|
||||
|
||||
@ddt.data(True, False)
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
|
||||
def test_course_badges_toggle(self, toggle):
|
||||
self.course = CourseFactory.create(metadata={'issue_badges': toggle})
|
||||
self._prepare_runtime()
|
||||
assert self.runtime.service(self.descriptor, 'badging').course_badges_enabled is toggle
|
||||
assert self.descriptor.runtime.service(self.descriptor, 'badging').course_badges_enabled is toggle
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
|
||||
def test_get_badge_class(self):
|
||||
self._prepare_runtime()
|
||||
badge_service = self.runtime.service(self.descriptor, 'badging')
|
||||
badge_service = self.descriptor.runtime.service(self.descriptor, 'badging')
|
||||
premade_badge_class = BadgeClassFactory.create()
|
||||
# Ignore additional parameters. This class already exists.
|
||||
# We should get back the first class we created, rather than a new one.
|
||||
@@ -2421,7 +2422,7 @@ class TestI18nService(LMSXBlockServiceMixin):
|
||||
"""
|
||||
Test: module i18n service in LMS
|
||||
"""
|
||||
i18n_service = self.runtime.service(self.descriptor, 'i18n')
|
||||
i18n_service = self.descriptor.runtime.service(self.descriptor, 'i18n')
|
||||
assert i18n_service is not None
|
||||
assert isinstance(i18n_service, XBlockI18nService)
|
||||
|
||||
@@ -2431,27 +2432,30 @@ class TestI18nService(LMSXBlockServiceMixin):
|
||||
"""
|
||||
self.descriptor.service_declaration = Mock(return_value=None)
|
||||
with pytest.raises(NoSuchServiceError):
|
||||
self.runtime.service(self.descriptor, 'i18n')
|
||||
self.descriptor.runtime.service(self.descriptor, 'i18n')
|
||||
|
||||
def test_no_service_exception_(self):
|
||||
"""
|
||||
Test: NoSuchServiceError should be raised if i18n service is none.
|
||||
"""
|
||||
self.runtime._services['i18n'] = None # pylint: disable=protected-access
|
||||
i18nService = self.descriptor.runtime._services['i18n'] # pylint: disable=protected-access
|
||||
self.descriptor.runtime._runtime_services['i18n'] = None # pylint: disable=protected-access
|
||||
self.descriptor.runtime._services['i18n'] = None # pylint: disable=protected-access
|
||||
with pytest.raises(NoSuchServiceError):
|
||||
self.runtime.service(self.descriptor, 'i18n')
|
||||
self.descriptor.runtime.service(self.descriptor, 'i18n')
|
||||
self.descriptor.runtime._services['i18n'] = i18nService # pylint: disable=protected-access
|
||||
|
||||
def test_i18n_service_callable(self):
|
||||
"""
|
||||
Test: _services dict should contain the callable i18n service in LMS.
|
||||
"""
|
||||
assert callable(self.runtime._services.get('i18n')) # pylint: disable=protected-access
|
||||
assert callable(self.descriptor.runtime._services.get('i18n')) # pylint: disable=protected-access
|
||||
|
||||
def test_i18n_service_not_callable(self):
|
||||
"""
|
||||
Test: i18n service should not be callable in LMS after initialization.
|
||||
"""
|
||||
assert not callable(self.runtime.service(self.descriptor, 'i18n'))
|
||||
assert not callable(self.descriptor.runtime.service(self.descriptor, 'i18n'))
|
||||
|
||||
|
||||
class PureXBlockWithChildren(PureXBlock):
|
||||
@@ -2666,7 +2670,7 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
self.track_function = Mock()
|
||||
self.request_token = Mock()
|
||||
self.contentstore = contentstore()
|
||||
self.runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2686,11 +2690,11 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Tests that the deprecated attributes provided by the user service match expected values.
|
||||
"""
|
||||
assert getattr(self.runtime, attribute) == expected_value
|
||||
assert getattr(self.descriptor.runtime, attribute) == expected_value
|
||||
|
||||
@patch('lms.djangoapps.courseware.block_render.has_access', Mock(return_value=True, autospec=True))
|
||||
def test_user_is_staff(self):
|
||||
runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2699,12 +2703,12 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
self.request_token,
|
||||
course=self.course,
|
||||
)
|
||||
assert runtime.user_is_staff
|
||||
assert runtime.get_user_role() == 'student'
|
||||
assert self.descriptor.runtime.user_is_staff
|
||||
assert self.descriptor.runtime.get_user_role() == 'student'
|
||||
|
||||
@patch('lms.djangoapps.courseware.block_render.get_user_role', Mock(return_value='instructor', autospec=True))
|
||||
def test_get_user_role(self):
|
||||
runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2713,17 +2717,17 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
self.request_token,
|
||||
course=self.course,
|
||||
)
|
||||
assert runtime.get_user_role() == 'instructor'
|
||||
assert self.descriptor.runtime.get_user_role() == 'instructor'
|
||||
|
||||
def test_anonymous_student_id(self):
|
||||
assert self.runtime.anonymous_student_id == anonymous_id_for_user(self.user, self.course.id)
|
||||
assert self.descriptor.runtime.anonymous_student_id == anonymous_id_for_user(self.user, self.course.id)
|
||||
|
||||
def test_anonymous_student_id_bug(self):
|
||||
"""
|
||||
Verifies that subsequent calls to get_module_system_for_user have no effect on each block runtime's
|
||||
Verifies that subsequent calls to prepare_runtime_for_user have no effect on each block runtime's
|
||||
anonymous_student_id value.
|
||||
"""
|
||||
problem_runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.problem_descriptor,
|
||||
@@ -2733,9 +2737,9 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
course=self.course,
|
||||
)
|
||||
# Ensure the problem block returns a per-user anonymous id
|
||||
assert problem_runtime.anonymous_student_id == anonymous_id_for_user(self.user, None)
|
||||
assert self.problem_descriptor.runtime.anonymous_student_id == anonymous_id_for_user(self.user, None)
|
||||
|
||||
vertical_runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2745,13 +2749,13 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
course=self.course,
|
||||
)
|
||||
# Ensure the vertical block returns a per-course+user anonymous id
|
||||
assert vertical_runtime.anonymous_student_id == anonymous_id_for_user(self.user, self.course.id)
|
||||
assert self.descriptor.runtime.anonymous_student_id == anonymous_id_for_user(self.user, self.course.id)
|
||||
|
||||
# Ensure the problem runtime's anonymous student ID is unchanged after the above call.
|
||||
assert problem_runtime.anonymous_student_id == anonymous_id_for_user(self.user, None)
|
||||
assert self.problem_descriptor.runtime.anonymous_student_id == anonymous_id_for_user(self.user, None)
|
||||
|
||||
def test_user_service_with_anonymous_user(self):
|
||||
runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
AnonymousUser(),
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2760,14 +2764,14 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
self.request_token,
|
||||
course=self.course,
|
||||
)
|
||||
assert runtime.anonymous_student_id is None
|
||||
assert runtime.seed == 0
|
||||
assert runtime.user_id is None
|
||||
assert not runtime.user_is_staff
|
||||
assert not runtime.get_user_role()
|
||||
assert self.descriptor.runtime.anonymous_student_id is None
|
||||
assert self.descriptor.runtime.seed == 0
|
||||
assert self.descriptor.runtime.user_id is None
|
||||
assert not self.descriptor.runtime.user_is_staff
|
||||
assert not self.descriptor.runtime.get_user_role()
|
||||
|
||||
def test_get_real_user(self):
|
||||
runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2777,20 +2781,20 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
course=self.course,
|
||||
)
|
||||
course_anonymous_student_id = anonymous_id_for_user(self.user, self.course.id)
|
||||
assert runtime.get_real_user(course_anonymous_student_id) == self.user # pylint: disable=not-callable
|
||||
assert self.descriptor.runtime.get_real_user(course_anonymous_student_id) == self.user # pylint: disable=not-callable
|
||||
|
||||
no_course_anonymous_student_id = anonymous_id_for_user(self.user, None)
|
||||
assert runtime.get_real_user(no_course_anonymous_student_id) == self.user # pylint: disable=not-callable
|
||||
assert self.descriptor.runtime.get_real_user(no_course_anonymous_student_id) == self.user # pylint: disable=not-callable
|
||||
|
||||
# Tests that the default is to use the user service's anonymous_student_id
|
||||
assert runtime.get_real_user() == self.user # pylint: disable=not-callable
|
||||
assert self.descriptor.runtime.get_real_user() == self.user # pylint: disable=not-callable
|
||||
|
||||
def test_render_template(self):
|
||||
rendered = self.runtime.render_template('templates/edxmako.html', {'element_id': 'hi'}) # pylint: disable=not-callable
|
||||
rendered = self.descriptor.runtime.render_template('templates/edxmako.html', {'element_id': 'hi'}) # pylint: disable=not-callable
|
||||
assert rendered == '<div id="hi" ns="main">Testing the MakoService</div>\n'
|
||||
|
||||
def test_xqueue(self):
|
||||
xqueue = self.runtime.xqueue
|
||||
xqueue = self.descriptor.runtime.xqueue
|
||||
assert isinstance(xqueue['interface'], XQueueInterface)
|
||||
assert xqueue['interface'].url == 'http://sandbox-xqueue.edx.org'
|
||||
assert xqueue['default_queuename'] == 'edX-LmsModuleShimTest'
|
||||
@@ -2812,7 +2816,7 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
XQUEUE_WAITTIME_BETWEEN_REQUESTS=15,
|
||||
)
|
||||
def test_xqueue_settings(self):
|
||||
runtime, _ = render.get_module_system_for_user(
|
||||
_ = render.prepare_runtime_for_user(
|
||||
self.user,
|
||||
self.student_data,
|
||||
self.descriptor,
|
||||
@@ -2821,7 +2825,7 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
self.request_token,
|
||||
course=self.course,
|
||||
)
|
||||
xqueue = runtime.xqueue
|
||||
xqueue = self.descriptor.runtime.xqueue
|
||||
assert isinstance(xqueue['interface'], XQueueInterface)
|
||||
assert xqueue['interface'].url == 'http://xqueue.url'
|
||||
assert xqueue['default_queuename'] == 'edX-LmsModuleShimTest'
|
||||
@@ -2832,14 +2836,14 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
|
||||
@override_settings(COURSES_WITH_UNSAFE_CODE=[r'course-v1:edX\+LmsModuleShimTest\+2021_Fall'])
|
||||
def test_can_execute_unsafe_code_when_allowed(self):
|
||||
assert self.runtime.can_execute_unsafe_code()
|
||||
assert self.descriptor.runtime.can_execute_unsafe_code()
|
||||
|
||||
@override_settings(COURSES_WITH_UNSAFE_CODE=[r'course-v1:edX\+full\+2021_Fall'])
|
||||
def test_cannot_execute_unsafe_code_when_disallowed(self):
|
||||
assert not self.runtime.can_execute_unsafe_code()
|
||||
assert not self.descriptor.runtime.can_execute_unsafe_code()
|
||||
|
||||
def test_cannot_execute_unsafe_code(self):
|
||||
assert not self.runtime.can_execute_unsafe_code()
|
||||
assert not self.descriptor.runtime.can_execute_unsafe_code()
|
||||
|
||||
@override_settings(PYTHON_LIB_FILENAME=PYTHON_LIB_FILENAME)
|
||||
def test_get_python_lib_zip(self):
|
||||
@@ -2849,7 +2853,7 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
source_file=self.PYTHON_LIB_SOURCE_FILE,
|
||||
target_filename=self.PYTHON_LIB_FILENAME,
|
||||
)
|
||||
assert self.runtime.get_python_lib_zip() == zipfile
|
||||
assert self.descriptor.runtime.get_python_lib_zip() == zipfile
|
||||
|
||||
def test_no_get_python_lib_zip(self):
|
||||
zipfile = upload_file_to_course(
|
||||
@@ -2858,26 +2862,26 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
source_file=self.PYTHON_LIB_SOURCE_FILE,
|
||||
target_filename=self.PYTHON_LIB_FILENAME,
|
||||
)
|
||||
assert self.runtime.get_python_lib_zip() is None
|
||||
assert self.descriptor.runtime.get_python_lib_zip() is None
|
||||
|
||||
def test_cache(self):
|
||||
assert hasattr(self.runtime.cache, 'get')
|
||||
assert hasattr(self.runtime.cache, 'set')
|
||||
assert hasattr(self.descriptor.runtime.cache, 'get')
|
||||
assert hasattr(self.descriptor.runtime.cache, 'set')
|
||||
|
||||
def test_replace_urls(self):
|
||||
html = '<a href="/static/id">'
|
||||
assert self.runtime.replace_urls(html) == \
|
||||
assert self.descriptor.runtime.replace_urls(html) == \
|
||||
static_replace.replace_static_urls(html, course_id=self.course.id)
|
||||
|
||||
def test_replace_course_urls(self):
|
||||
html = '<a href="/course/id">'
|
||||
assert self.runtime.replace_course_urls(html) == \
|
||||
assert self.descriptor.runtime.replace_course_urls(html) == \
|
||||
static_replace.replace_course_urls(html, course_key=self.course.id)
|
||||
|
||||
def test_replace_jump_to_id_urls(self):
|
||||
html = '<a href="/jump_to_id/id">'
|
||||
jump_to_id_base_url = reverse('jump_to_id', kwargs={'course_id': str(self.course.id), 'module_id': ''})
|
||||
assert self.runtime.replace_jump_to_id_urls(html) == \
|
||||
assert self.descriptor.runtime.replace_jump_to_id_urls(html) == \
|
||||
static_replace.replace_jump_to_id_urls(html, self.course.id, jump_to_id_base_url)
|
||||
|
||||
@XBlock.register_temp_plugin(PureXBlock, 'pure')
|
||||
|
||||
@@ -55,7 +55,6 @@ class TestDiscussionXBlock(XModuleRenderingTestBase):
|
||||
field_data=self.data,
|
||||
scope_ids=scope_ids
|
||||
)
|
||||
self.block.xmodule_runtime = mock.Mock()
|
||||
|
||||
if self.PATCH_DJANGO_USER:
|
||||
self.django_user_canary = UserFactory()
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestLTI(BaseTestXmodule):
|
||||
|
||||
# Note: this course_id is actually a course_key
|
||||
context_id = str(self.item_descriptor.course_id)
|
||||
user_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'user')
|
||||
user_service = self.item_descriptor.runtime.service(self.item_descriptor, 'user')
|
||||
user_id = str(user_service.get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID))
|
||||
hostname = settings.LMS_BASE
|
||||
resource_link_id = str(urllib.parse.quote(f'{hostname}-{self.item_descriptor.location.html_id()}'))
|
||||
@@ -81,8 +81,10 @@ class TestLTI(BaseTestXmodule):
|
||||
'element_id': self.item_descriptor.location.html_id(),
|
||||
'launch_url': 'http://www.example.com', # default value
|
||||
'open_in_a_new_page': True,
|
||||
'form_url': self.item_descriptor.xmodule_runtime.handler_url(self.item_descriptor,
|
||||
'preview_handler').rstrip('/?'),
|
||||
'form_url': self.item_descriptor.runtime.handler_url(
|
||||
self.item_descriptor,
|
||||
'preview_handler'
|
||||
).rstrip('/?'),
|
||||
'hide_launch': False,
|
||||
'has_score': False,
|
||||
'module_score': None,
|
||||
|
||||
@@ -22,6 +22,8 @@ from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: di
|
||||
from xmodule.exceptions import NotFoundError # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
# noinspection PyUnresolvedReferences
|
||||
from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import
|
||||
from xmodule.video_block import VideoBlock # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.video_block.transcripts_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
|
||||
Transcript,
|
||||
@@ -29,7 +31,7 @@ from xmodule.video_block.transcripts_utils import ( # lint-amnesty, pylint: dis
|
||||
get_transcript,
|
||||
subs_filename,
|
||||
)
|
||||
from xmodule.x_module import STUDENT_VIEW # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
|
||||
from .helpers import BaseTestXmodule
|
||||
from .test_video_xml import SOURCE_XML
|
||||
@@ -131,6 +133,7 @@ def attach_bumper_transcript(item, filename, lang="en"):
|
||||
item.video_bumper["transcripts"][lang] = filename
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("override_descriptor_system")
|
||||
class BaseTestVideoXBlock(BaseTestXmodule):
|
||||
"""Base class for VideoXBlock tests."""
|
||||
|
||||
@@ -232,7 +235,7 @@ class TestVideo(BaseTestVideoXBlock):
|
||||
"""
|
||||
Return the URL for the specified handler on self.item_descriptor.
|
||||
"""
|
||||
return self.item_descriptor.xmodule_runtime.handler_url(
|
||||
return self.item_descriptor.runtime.handler_url(
|
||||
self.item_descriptor, handler, suffix
|
||||
).rstrip('/?')
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_MODULESTORE, TEST_DATA_SPLIT_MODULESTORE
|
||||
# noinspection PyUnresolvedReferences
|
||||
from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import
|
||||
from xmodule.tests.test_import import DummySystem
|
||||
from xmodule.tests.test_video import VideoBlockTestBase
|
||||
from xmodule.video_block import VideoBlock, bumper_utils, video_utils
|
||||
@@ -136,7 +138,7 @@ class TestVideoYouTube(TestVideo): # lint-amnesty, pylint: disable=missing-clas
|
||||
'public_video_url': None,
|
||||
}
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -220,7 +222,7 @@ class TestVideoNonYouTube(TestVideo): # pylint: disable=test-inherits-tests
|
||||
'public_video_url': None,
|
||||
}
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
expected_result = get_context_dict_from_string(
|
||||
mako_service.render_template('video.html', expected_context)
|
||||
)
|
||||
@@ -241,14 +243,16 @@ class TestVideoPublicAccess(BaseTestVideoXBlock):
|
||||
@ddt.data(
|
||||
(True, False),
|
||||
(False, False),
|
||||
(False, True),
|
||||
(True, True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_public_video_url(self, is_lms_platform, enable_public_share):
|
||||
"""Test public video url."""
|
||||
assert self.item_descriptor.public_access is True
|
||||
with patch.object(self.item_descriptor, '_is_lms_platform', return_value=is_lms_platform), \
|
||||
patch.object(PUBLIC_VIDEO_SHARE, 'is_enabled', return_value=enable_public_share):
|
||||
if not is_lms_platform:
|
||||
self.item_descriptor.runtime.is_author_mode = True
|
||||
with patch.object(PUBLIC_VIDEO_SHARE, 'is_enabled', return_value=enable_public_share):
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
# public video url iif PUBLIC_VIDEO_SHARE waffle and is_lms_platform, public_access are true
|
||||
assert bool(get_context_dict_from_string(context)['public_video_url']) \
|
||||
@@ -305,7 +309,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
Return the URL for the specified handler on the block represented by
|
||||
self.item_descriptor.
|
||||
"""
|
||||
return self.item_descriptor.xmodule_runtime.handler_url(
|
||||
return self.item_descriptor.runtime.handler_url(
|
||||
self.item_descriptor, handler, suffix
|
||||
).rstrip('/?')
|
||||
|
||||
@@ -422,7 +426,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
'metadata': json.dumps(metadata)
|
||||
})
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -533,7 +537,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
'metadata': json.dumps(expected_context['metadata'])
|
||||
})
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -676,7 +680,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
'metadata': json.dumps(expected_context['metadata'])
|
||||
})
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -704,7 +708,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
# context returned by get_html when provided with above data
|
||||
# expected_context, a dict to assert with context
|
||||
context, expected_context = self.helper_get_html_with_edx_video_id(data)
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -735,7 +739,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
# expected_context, a dict to assert with context
|
||||
context, expected_context = self.helper_get_html_with_edx_video_id(data)
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -936,7 +940,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
self.initialize_block(data=DATA, runtime_kwargs={
|
||||
'user_location': 'CN',
|
||||
})
|
||||
user_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'user')
|
||||
user_service = self.item_descriptor.runtime.service(self.item_descriptor, 'user')
|
||||
user_location = user_service.get_current_user().opt_attrs[ATTR_KEY_REQUEST_COUNTRY_CODE]
|
||||
assert user_location == 'CN'
|
||||
context = self.item_descriptor.render('student_view').content
|
||||
@@ -954,7 +958,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
'metadata': json.dumps(expected_context['metadata'])
|
||||
})
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -1059,7 +1063,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
|
||||
'metadata': json.dumps(expected_context['metadata'])
|
||||
})
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
assert get_context_dict_from_string(context) ==\
|
||||
get_context_dict_from_string(mako_service.render_template('video.html', expected_context))
|
||||
|
||||
@@ -2312,7 +2316,7 @@ class TestVideoWithBumper(TestVideo): # pylint: disable=test-inherits-tests
|
||||
}))
|
||||
}
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
expected_content = mako_service.render_template('video.html', expected_context)
|
||||
assert get_context_dict_from_string(content) == get_context_dict_from_string(expected_content)
|
||||
|
||||
@@ -2368,10 +2372,10 @@ class TestAutoAdvanceVideo(TestVideo): # lint-amnesty, pylint: disable=test-inh
|
||||
'ytTestTimeout': 1500,
|
||||
'ytApiUrl': 'https://www.youtube.com/iframe_api',
|
||||
'lmsRootURL': settings.LMS_ROOT_URL,
|
||||
'transcriptTranslationUrl': self.item_descriptor.xmodule_runtime.handler_url(
|
||||
'transcriptTranslationUrl': self.item_descriptor.runtime.handler_url(
|
||||
self.item_descriptor, 'transcript', 'translation/__lang__'
|
||||
).rstrip('/?'),
|
||||
'transcriptAvailableTranslationsUrl': self.item_descriptor.xmodule_runtime.handler_url(
|
||||
'transcriptAvailableTranslationsUrl': self.item_descriptor.runtime.handler_url(
|
||||
self.item_descriptor, 'transcript', 'available_translations'
|
||||
).rstrip('/?'),
|
||||
'autohideHtml5': False,
|
||||
@@ -2407,7 +2411,7 @@ class TestAutoAdvanceVideo(TestVideo): # lint-amnesty, pylint: disable=test-inh
|
||||
autoadvance_flag=autoadvance_must_be,
|
||||
)
|
||||
|
||||
mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako')
|
||||
mako_service = self.item_descriptor.runtime.service(self.item_descriptor, 'mako')
|
||||
with override_settings(FEATURES=self.FEATURES):
|
||||
expected_content = mako_service.render_template('video.html', expected_context)
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
"""Word cloud integration tests using mongo modulestore."""
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
import json
|
||||
from operator import itemgetter
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
|
||||
from .helpers import BaseTestXmodule
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("override_descriptor_system")
|
||||
class TestWordCloud(BaseTestXmodule):
|
||||
"""Integration test for Word Cloud Block."""
|
||||
CATEGORY = "word_cloud"
|
||||
|
||||
@@ -1487,7 +1487,7 @@ def enclosing_sequence_for_gating_checks(block):
|
||||
|
||||
if ancestor:
|
||||
# get_parent() returns a parent block instance cached on the block which does not
|
||||
# have the ModuleSystem bound to it so we need to get it again with get_block() which will set up everything.
|
||||
# have user data bound to it so we need to get it again with get_block() which will set up everything.
|
||||
return block.runtime.get_block(ancestor.location)
|
||||
return None
|
||||
|
||||
|
||||
@@ -351,7 +351,7 @@ def _get_module_instance_for_task(course_id, student, module_descriptor, xblock_
|
||||
'''
|
||||
Make a tracking function that logs what happened.
|
||||
|
||||
For insertion into ModuleSystem, and used by CapaModule, which will
|
||||
For insertion into runtime, and used by CapaModule, which will
|
||||
provide the event_type (as string) and event (as dict) as arguments.
|
||||
The request_info and task_info (and page) are provided here.
|
||||
'''
|
||||
@@ -375,7 +375,7 @@ def _get_track_function_for_task(student, xblock_instance_args=None, source_page
|
||||
"""
|
||||
Make a tracking function that logs what happened.
|
||||
|
||||
For insertion into ModuleSystem, and used by CapaModule, which will
|
||||
For insertion into runtime, and used by CapaModule, which will
|
||||
provide the event_type (as string) and event (as dict) as arguments.
|
||||
The request_info and task_info (and page) are provided here.
|
||||
"""
|
||||
|
||||
@@ -16,6 +16,7 @@ from datetime import datetime, timedelta
|
||||
from unittest.mock import ANY, MagicMock, Mock, patch
|
||||
|
||||
import ddt
|
||||
import pytest
|
||||
import unicodecsv
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
@@ -70,6 +71,8 @@ from openedx.core.lib.teams_config import TeamsConfig
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.partitions.partitions import Group, UserPartition # lint-amnesty, pylint: disable=wrong-import-order
|
||||
# noinspection PyUnresolvedReferences
|
||||
from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import
|
||||
|
||||
from ..models import ReportStore
|
||||
from ..tasks_helper.utils import UPDATE_STATUS_FAILED, UPDATE_STATUS_SUCCEEDED
|
||||
@@ -1080,6 +1083,7 @@ class TestProblemReportSplitTestContent(TestReportMixin, TestConditionalContent,
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@pytest.mark.usefixtures("override_descriptor_system")
|
||||
class TestProblemReportCohortedContent(TestReportMixin, ContentGroupTestCase, InstructorTaskModuleTestCase):
|
||||
"""
|
||||
Test the problem report on a course that has cohorted content.
|
||||
@@ -1729,6 +1733,7 @@ class TestCohortStudents(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
|
||||
@ddt.ddt
|
||||
@patch('lms.djangoapps.instructor_task.tasks_helper.misc.DefaultStorage', new=MockDefaultStorage)
|
||||
@pytest.mark.usefixtures("override_descriptor_system")
|
||||
class TestGradeReport(TestReportMixin, InstructorTaskModuleTestCase):
|
||||
"""
|
||||
Test that grade report has correct grade values.
|
||||
|
||||
@@ -9,7 +9,6 @@ from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
|
||||
from openedx.core.djangoapps.user_api.course_tag import api as user_course_tag_api
|
||||
from openedx.core.lib.url_utils import quote_slashes
|
||||
from openedx.core.lib.xblock_utils import wrap_xblock_aside, xblock_local_resource_url
|
||||
from xmodule.x_module import ModuleSystem # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
def handler_url(block, handler_name, suffix='', query='', thirdparty=False):
|
||||
@@ -72,6 +71,64 @@ def local_resource_url(block, uri):
|
||||
return xblock_local_resource_url(block, uri)
|
||||
|
||||
|
||||
def lms_wrappers_aside(block, aside, view, frag, context, request_token=None):
|
||||
"""
|
||||
Creates a div which identifies the aside, points to the original block,
|
||||
and writes out the json_init_args into a script tag.
|
||||
|
||||
The default implementation creates a frag to wraps frag w/ a div identifying the xblock. If you have
|
||||
javascript, you'll need to override this impl
|
||||
"""
|
||||
if not frag.content:
|
||||
return frag
|
||||
|
||||
runtime_class = 'LmsRuntime'
|
||||
extra_data = {
|
||||
'block-id': quote_slashes(str(block.scope_ids.usage_id)),
|
||||
'course-id': quote_slashes(str(block.scope_ids.usage_id.context_key)),
|
||||
'url-selector': 'asideBaseUrl',
|
||||
'runtime-class': runtime_class,
|
||||
}
|
||||
if request_token:
|
||||
extra_data['request-token'] = request_token
|
||||
|
||||
return wrap_xblock_aside(
|
||||
runtime_class,
|
||||
aside,
|
||||
view,
|
||||
frag,
|
||||
context,
|
||||
usage_id_serializer=str,
|
||||
request_token=request_token,
|
||||
extra_data=extra_data,
|
||||
)
|
||||
|
||||
|
||||
def lms_applicable_aside_types(block, applicable_aside_types=None):
|
||||
"""
|
||||
Return all of the asides which might be decorating this `block`.
|
||||
|
||||
Arguments:
|
||||
block (:class:`.XBlock`): The block to render retrieve asides for.
|
||||
"""
|
||||
|
||||
config = XBlockAsidesConfig.current()
|
||||
|
||||
if not config.enabled:
|
||||
return []
|
||||
|
||||
if block.scope_ids.block_type in config.disabled_blocks.split():
|
||||
return []
|
||||
|
||||
# TODO: aside_type != 'acid_aside' check should be removed once AcidBlock is only installed during tests
|
||||
# (see https://openedx.atlassian.net/browse/TE-811)
|
||||
return [
|
||||
aside_type
|
||||
for aside_type in applicable_aside_types(block)
|
||||
if aside_type != 'acid_aside'
|
||||
]
|
||||
|
||||
|
||||
class UserTagsService:
|
||||
"""
|
||||
A runtime class that provides an interface to the user service. It handles filling in
|
||||
@@ -114,92 +171,3 @@ class UserTagsService:
|
||||
self._user,
|
||||
self._course_id, key, value
|
||||
)
|
||||
|
||||
|
||||
class LmsModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
"""
|
||||
ModuleSystem specialized to the LMS
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
self.request_token = kwargs.pop('request_token', None)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def handler_url(self, *args, **kwargs): # lint-amnesty, pylint: disable=signature-differs
|
||||
"""
|
||||
Implement the XBlock runtime handler_url interface.
|
||||
|
||||
This is mostly just proxying to the module level `handler_url` function
|
||||
defined higher up in this file.
|
||||
|
||||
We're doing this indirection because the module level `handler_url`
|
||||
logic is also needed by the `DescriptorSystem`. The particular
|
||||
`handler_url` that a `DescriptorSystem` needs will be different when
|
||||
running an LMS process or a CMS/Studio process. That's accomplished by
|
||||
monkey-patching a global. It's a long story, but please know that you
|
||||
can't just refactor and fold that logic into here without breaking
|
||||
things.
|
||||
|
||||
https://openedx.atlassian.net/wiki/display/PLAT/Convert+from+Storage-centric+runtimes+to+Application-centric+runtimes
|
||||
|
||||
See :method:`xblock.runtime:Runtime.handler_url`
|
||||
"""
|
||||
return handler_url(*args, **kwargs)
|
||||
|
||||
def local_resource_url(self, *args, **kwargs):
|
||||
return local_resource_url(*args, **kwargs)
|
||||
|
||||
def wrap_aside(self, block, aside, view, frag, context):
|
||||
"""
|
||||
Creates a div which identifies the aside, points to the original block,
|
||||
and writes out the json_init_args into a script tag.
|
||||
|
||||
The default implementation creates a frag to wraps frag w/ a div identifying the xblock. If you have
|
||||
javascript, you'll need to override this impl
|
||||
"""
|
||||
if not frag.content:
|
||||
return frag
|
||||
|
||||
runtime_class = 'LmsRuntime'
|
||||
extra_data = {
|
||||
'block-id': quote_slashes(str(block.scope_ids.usage_id)),
|
||||
'course-id': quote_slashes(str(block.course_id)),
|
||||
'url-selector': 'asideBaseUrl',
|
||||
'runtime-class': runtime_class,
|
||||
}
|
||||
if self.request_token:
|
||||
extra_data['request-token'] = self.request_token
|
||||
|
||||
return wrap_xblock_aside(
|
||||
runtime_class,
|
||||
aside,
|
||||
view,
|
||||
frag,
|
||||
context,
|
||||
usage_id_serializer=str,
|
||||
request_token=self.request_token,
|
||||
extra_data=extra_data,
|
||||
)
|
||||
|
||||
def applicable_aside_types(self, block):
|
||||
"""
|
||||
Return all of the asides which might be decorating this `block`.
|
||||
|
||||
Arguments:
|
||||
block (:class:`.XBlock`): The block to render retrieve asides for.
|
||||
"""
|
||||
|
||||
config = XBlockAsidesConfig.current()
|
||||
|
||||
if not config.enabled:
|
||||
return []
|
||||
|
||||
if block.scope_ids.block_type in config.disabled_blocks.split():
|
||||
return []
|
||||
|
||||
# TODO: aside_type != 'acid_aside' check should be removed once AcidBlock is only installed during tests
|
||||
# (see https://openedx.atlassian.net/browse/TE-811)
|
||||
return [
|
||||
aside_type
|
||||
for aside_type in super().applicable_aside_types(block)
|
||||
if aside_type != 'acid_aside'
|
||||
]
|
||||
|
||||
@@ -11,7 +11,8 @@ from django.test import TestCase
|
||||
from opaque_keys.edx.locations import BlockUsageLocator, CourseLocator
|
||||
from xblock.fields import ScopeIds
|
||||
|
||||
from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem
|
||||
from xmodule.x_module import DescriptorSystem
|
||||
from lms.djangoapps.lms_xblock.runtime import handler_url
|
||||
|
||||
|
||||
class BlockMock(Mock):
|
||||
@@ -50,10 +51,13 @@ class TestHandlerUrl(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.block = BlockMock(name='block')
|
||||
self.runtime = LmsModuleSystem(
|
||||
get_block=Mock(),
|
||||
descriptor_runtime=Mock(),
|
||||
self.runtime = DescriptorSystem(
|
||||
load_item=Mock(name='get_test_descriptor_system.load_item'),
|
||||
resources_fs=Mock(name='get_test_descriptor_system.resources_fs'),
|
||||
error_tracker=Mock(name='get_test_descriptor_system.error_tracker')
|
||||
)
|
||||
self.runtime.get_block_for_descriptor = Mock()
|
||||
self.runtime.handler_url_override = handler_url
|
||||
|
||||
def test_trailing_characters(self):
|
||||
assert not self.runtime.handler_url(self.block, 'handler').endswith('?')
|
||||
|
||||
@@ -256,13 +256,13 @@ class XBlockRuntime(RuntimeShim, Runtime):
|
||||
elif service_name == 'replace_urls':
|
||||
return ReplaceURLService(xblock=block, lookup_asset_url=self._lookup_asset_url)
|
||||
elif service_name == 'rebind_user':
|
||||
# this service should ideally be initialized with all the arguments of get_module_system_for_user
|
||||
# this service should ideally be initialized with all the arguments of prepare_runtime_for_user
|
||||
# but only the positional arguments are passed here as the other arguments are too
|
||||
# specific to the lms.block_render module
|
||||
return RebindUserService(
|
||||
self.user,
|
||||
context_key,
|
||||
block_render.get_module_system_for_user,
|
||||
block_render.prepare_runtime_for_user,
|
||||
track_function=make_track_function(),
|
||||
request_token=request_token(crum.get_current_request()),
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from xmodule.library_tools import LibraryToolsService
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, LibraryFactory
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.tests import prepare_block_runtime
|
||||
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
@@ -121,20 +121,17 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
"""
|
||||
Bind a block (part of self.course) so we can access student-specific data.
|
||||
"""
|
||||
module_system = get_test_system(course_id=block.location.course_key)
|
||||
module_system.descriptor_runtime = block.runtime._descriptor_system # pylint: disable=protected-access
|
||||
module_system._services['library_tools'] = LibraryToolsService(self.store, self.user.id) # pylint: disable=protected-access
|
||||
prepare_block_runtime(block.runtime, course_id=block.location.course_key)
|
||||
block.runtime._services.update({'library_tools': LibraryToolsService(self.store, self.user.id)}) # lint-amnesty, pylint: disable=protected-access
|
||||
|
||||
def get_block(descriptor):
|
||||
"""Mocks module_system get_block_for_descriptor function"""
|
||||
sub_module_system = get_test_system(course_id=block.location.course_key)
|
||||
sub_module_system.get_block_for_descriptor = get_block
|
||||
sub_module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access
|
||||
descriptor.bind_for_student(sub_module_system, self.user.id)
|
||||
prepare_block_runtime(descriptor.runtime, course_id=block.location.course_key)
|
||||
descriptor.runtime.get_block_for_descriptor = get_block
|
||||
descriptor.bind_for_student(self.user.id)
|
||||
return descriptor
|
||||
|
||||
module_system.get_block_for_descriptor = get_block
|
||||
block.xmodule_runtime = module_system
|
||||
block.runtime.get_block_for_descriptor = get_block
|
||||
|
||||
def test_completion_service(self):
|
||||
# Only the completions for the user and course specified for the CompletionService
|
||||
|
||||
@@ -1186,7 +1186,7 @@ xblock[django]==1.6.2
|
||||
# xblock-google-drive
|
||||
# xblock-poll
|
||||
# xblock-utils
|
||||
xblock-drag-and-drop-v2==3.1.2
|
||||
xblock-drag-and-drop-v2==3.2.0
|
||||
# via -r requirements/edx/base.in
|
||||
xblock-google-drive==0.3.0
|
||||
# via -r requirements/edx/base.in
|
||||
|
||||
@@ -1715,7 +1715,7 @@ xblock[django]==1.6.2
|
||||
# xblock-google-drive
|
||||
# xblock-poll
|
||||
# xblock-utils
|
||||
xblock-drag-and-drop-v2==3.1.2
|
||||
xblock-drag-and-drop-v2==3.2.0
|
||||
# via -r requirements/edx/testing.txt
|
||||
xblock-google-drive==0.3.0
|
||||
# via -r requirements/edx/testing.txt
|
||||
|
||||
@@ -1583,7 +1583,7 @@ xblock[django]==1.6.2
|
||||
# xblock-google-drive
|
||||
# xblock-poll
|
||||
# xblock-utils
|
||||
xblock-drag-and-drop-v2==3.1.2
|
||||
xblock-drag-and-drop-v2==3.2.0
|
||||
# via -r requirements/edx/base.txt
|
||||
xblock-google-drive==0.3.0
|
||||
# via -r requirements/edx/base.txt
|
||||
|
||||
@@ -93,7 +93,7 @@ class LoncapaSystem(object):
|
||||
i18n: an object implementing the `gettext.Translations` interface so
|
||||
that we can use `.ugettext` to localize strings.
|
||||
|
||||
See :class:`ModuleSystem` for documentation of other attributes.
|
||||
See :class:`DescriptorSystem` for documentation of other attributes.
|
||||
|
||||
"""
|
||||
def __init__(
|
||||
|
||||
@@ -19,12 +19,11 @@ class MakoDescriptorSystem(DescriptorSystem): # lint-amnesty, pylint: disable=a
|
||||
|
||||
# Add the MakoService to the descriptor system.
|
||||
#
|
||||
# This is not needed by most XBlocks, because they are initialized with a full runtime ModuleSystem that already
|
||||
# has the MakoService.
|
||||
# However, there are a few cases where the XBlock only has the descriptor system instead of the full module
|
||||
# This is not needed by most XBlocks, because the MakoService is added to their runtimes.
|
||||
# However, there are a few cases where the MakoService is not added to the XBlock's
|
||||
# runtime. Specifically:
|
||||
# * in the Instructor Dashboard bulk emails tab, when rendering the HtmlBlock for its WYSIWYG editor.
|
||||
# * during testing, when using the ModuleSystemTestCase to fetch factory-created blocks.
|
||||
# * during testing, when fetching factory-created blocks.
|
||||
from common.djangoapps.edxmako.services import MakoService
|
||||
self._services['mako'] = MakoService()
|
||||
|
||||
|
||||
@@ -1386,10 +1386,3 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
|
||||
"""
|
||||
if self.signal_handler:
|
||||
self.signal_handler.send("item_deleted", usage_key=usage_key, user_id=user_id)
|
||||
|
||||
|
||||
def only_xmodules(identifier, entry_points):
|
||||
"""Only use entry_points that are supplied by the xmodule package"""
|
||||
from_xmodule = [entry_point for entry_point in entry_points if entry_point.dist.key == 'xmodule']
|
||||
|
||||
return default_select(identifier, from_xmodule)
|
||||
|
||||
@@ -300,11 +300,11 @@ class SequenceBlock(
|
||||
|
||||
self.gated_sequence_paywall = None
|
||||
|
||||
def bind_for_student(self, xmodule_runtime, user_id, wrappers=None):
|
||||
def bind_for_student(self, user_id, wrappers=None):
|
||||
# The position of the child XBlock to select can also be passed in via the URL.
|
||||
# In such cases the value is set on the ModuleSystem in get_module_system_for_user()
|
||||
# and needs to be read here after the ModuleSystem has been set on the XBlock.
|
||||
super().bind_for_student(xmodule_runtime, user_id, wrappers)
|
||||
# In such cases the value is set in the runtime inside prepare_runtime_for_user()
|
||||
# and needs to be read here after the value has been set.
|
||||
super().bind_for_student(user_id, wrappers)
|
||||
# If position is specified in system, then use that instead.
|
||||
position = getattr(self.runtime, 'position', None)
|
||||
if position is not None:
|
||||
|
||||
@@ -147,9 +147,8 @@ class RebindUserService(Service):
|
||||
"""
|
||||
An XBlock Service that allows modules to get rebound to real users if it was previously bound to an AnonymousUser.
|
||||
|
||||
This used to be a local function inside the `lms.djangoapps.courseware.block_render.get_module_system_for_user`
|
||||
method, and was passed as a constructor argument to x_module.ModuleSystem. This has been refactored out into a
|
||||
service to simplify the ModuleSystem and lives in this module temporarily.
|
||||
This used to be a local function inside the `lms.djangoapps.courseware.block_render.prepare_runtime_for_user`
|
||||
method. This has been refactored out into a service and lives in this module temporarily.
|
||||
|
||||
TODO: Only the old LTI XBlock uses it in 2 places for LTI 2.0 integration. As the LTI XBlock is deprecated in
|
||||
favour of the LTI Consumer XBlock, this should be removed when the LTI XBlock is removed.
|
||||
@@ -158,18 +157,18 @@ class RebindUserService(Service):
|
||||
user (User) - A Django User object
|
||||
course_id (str) - Course ID
|
||||
course (Course) - Course Object
|
||||
get_module_system_for_user (function) - The helper function that will be called to create a module system
|
||||
prepare_runtime_for_user (function) - The helper function that will be called to create a module system
|
||||
for a specfic user. This is the parent function from which this service was reactored out.
|
||||
`lms.djangoapps.courseware.block_render.get_module_system_for_user`
|
||||
kwargs (dict) - all the keyword arguments that need to be passed to the `get_module_system_for_user`
|
||||
`lms.djangoapps.courseware.block_render.prepare_runtime_for_user`
|
||||
kwargs (dict) - all the keyword arguments that need to be passed to the `prepare_runtime_for_user`
|
||||
function when it is called during rebinding
|
||||
"""
|
||||
def __init__(self, user, course_id, get_module_system_for_user, **kwargs):
|
||||
def __init__(self, user, course_id, prepare_runtime_for_user, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.user = user
|
||||
self.course_id = course_id
|
||||
self._ref = {
|
||||
"get_module_system_for_user": get_module_system_for_user
|
||||
"prepare_runtime_for_user": prepare_runtime_for_user
|
||||
}
|
||||
self._kwargs = kwargs
|
||||
|
||||
@@ -202,17 +201,16 @@ class RebindUserService(Service):
|
||||
with modulestore().bulk_operations(self.course_id):
|
||||
course = modulestore().get_course(course_key=self.course_id)
|
||||
|
||||
(inner_system, inner_student_data) = self._ref["get_module_system_for_user"](
|
||||
inner_student_data = self._ref["prepare_runtime_for_user"](
|
||||
user=real_user,
|
||||
student_data=student_data_real_user, # These have implicit user bindings, rest of args considered not to
|
||||
descriptor=block,
|
||||
block=block,
|
||||
course_id=self.course_id,
|
||||
course=course,
|
||||
**self._kwargs
|
||||
)
|
||||
|
||||
block.bind_for_student(
|
||||
inner_system,
|
||||
real_user.id,
|
||||
[
|
||||
partial(DateLookupFieldData, course_id=self.course_id, user=self.user),
|
||||
@@ -222,17 +220,14 @@ class RebindUserService(Service):
|
||||
)
|
||||
|
||||
block.scope_ids = block.scope_ids._replace(user_id=real_user.id)
|
||||
# now bind the module to the new ModuleSystem instance and vice-versa
|
||||
block.runtime = inner_system
|
||||
inner_system.xmodule_instance = block
|
||||
|
||||
|
||||
class EventPublishingService(Service):
|
||||
"""
|
||||
An XBlock Service that allows XModules to publish events (e.g. grading, completion).
|
||||
|
||||
We have separated it from the ModuleSystem to be able to alter its behavior when using a different context:
|
||||
LMS, Studio, or Instructor tasks.
|
||||
We have implemented it as a seperate service to be able to alter its behavior when using
|
||||
a different context: LMS, Studio, or Instructor tasks.
|
||||
"""
|
||||
def __init__(self, user, course_id, track_function, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@@ -33,7 +33,10 @@ class StudioEditableBlock(XBlockMixin):
|
||||
'content': rendered_child.content
|
||||
})
|
||||
|
||||
fragment.add_content(self.runtime.service(self, 'mako').render_template("studio_render_children_view.html", { # pylint: disable=no-member
|
||||
# 'lms.' namespace_prefix is required for rendering in studio
|
||||
mako_service = self.runtime.service(self, 'mako')
|
||||
mako_service.namespace_prefix = 'lms.'
|
||||
fragment.add_content(mako_service.render_template("studio_render_children_view.html", { # pylint: disable=no-member
|
||||
'items': contents,
|
||||
'xblock_context': context,
|
||||
'can_add': can_add,
|
||||
|
||||
@@ -36,7 +36,7 @@ from xmodule.modulestore.inheritance import InheritanceMixin
|
||||
from xmodule.modulestore.xml import CourseLocationManager
|
||||
from xmodule.tests.helpers import StubReplaceURLService, mock_render_template, StubMakoService, StubUserService
|
||||
from xmodule.util.sandboxing import SandboxService
|
||||
from xmodule.x_module import DoNothingCache, ModuleSystem, XModuleMixin
|
||||
from xmodule.x_module import DoNothingCache, XModuleMixin
|
||||
from openedx.core.lib.cache_utils import CacheService
|
||||
|
||||
|
||||
@@ -46,9 +46,35 @@ MODULE_DIR = path(__file__).dirname()
|
||||
DATA_DIR = MODULE_DIR.parent.parent / "common" / "test" / "data"
|
||||
|
||||
|
||||
class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
def handler_url(block, handler, suffix='', query='', thirdparty=False): # lint-amnesty, pylint: disable=arguments-differ
|
||||
return '{usage_id}/{handler}{suffix}?{query}'.format(
|
||||
usage_id=str(block.scope_ids.usage_id),
|
||||
handler=handler,
|
||||
suffix=suffix,
|
||||
query=query,
|
||||
)
|
||||
|
||||
|
||||
def local_resource_url(block, uri):
|
||||
return 'resource/{usage_id}/{uri}'.format(
|
||||
usage_id=str(block.scope_ids.usage_id),
|
||||
uri=uri,
|
||||
)
|
||||
|
||||
|
||||
# Disable XBlockAsides in most tests
|
||||
def get_asides(block):
|
||||
return []
|
||||
|
||||
|
||||
@property
|
||||
def resources_fs():
|
||||
return Mock(name='TestDescriptorSystem.resources_fs', root_path='.')
|
||||
|
||||
|
||||
class TestDescriptorSystem(MakoDescriptorSystem): # pylint: disable=abstract-method
|
||||
"""
|
||||
ModuleSystem for testing
|
||||
DescriptorSystem for testing
|
||||
"""
|
||||
def handler_url(self, block, handler, suffix='', query='', thirdparty=False): # lint-amnesty, pylint: disable=arguments-differ
|
||||
return '{usage_id}/{handler}{suffix}?{query}'.format(
|
||||
@@ -68,9 +94,8 @@ class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
def get_asides(self, block):
|
||||
return []
|
||||
|
||||
@property
|
||||
def resources_fs(self):
|
||||
return Mock(name='TestModuleSystem.resources_fs', root_path='.')
|
||||
def resources_fs(self): # lint-amnesty, pylint: disable=method-hidden
|
||||
return Mock(name='TestDescriptorSystem.resources_fs', root_path='.')
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
@@ -94,13 +119,19 @@ def get_test_system(
|
||||
user_is_staff=False,
|
||||
user_location=None,
|
||||
render_template=None,
|
||||
add_get_block_overrides=False
|
||||
):
|
||||
"""
|
||||
Construct a test ModuleSystem instance.
|
||||
Construct a test DescriptorSystem instance.
|
||||
|
||||
By default, the descriptor system's render_template() method simply returns the repr of the
|
||||
context it is passed. You can override this by passing in a different render_template argument.
|
||||
"""
|
||||
|
||||
id_manager = CourseLocationManager(course_id)
|
||||
|
||||
descriptor_system = get_test_descriptor_system(id_reader=id_manager, id_generator=id_manager)
|
||||
|
||||
if not user:
|
||||
user = Mock(name='get_test_system.user', is_staff=False)
|
||||
if not user_location:
|
||||
@@ -117,63 +148,128 @@ def get_test_system(
|
||||
|
||||
replace_url_service = StubReplaceURLService()
|
||||
|
||||
descriptor_system = get_test_descriptor_system()
|
||||
|
||||
id_manager = CourseLocationManager(course_id)
|
||||
|
||||
def get_block(descriptor):
|
||||
def get_block(block):
|
||||
"""Mocks module_system get_block function"""
|
||||
|
||||
# Unlike XBlock Runtimes or DescriptorSystems,
|
||||
# each XModule is provided with a new ModuleSystem.
|
||||
# Construct one for the new XModule.
|
||||
module_system = get_test_system()
|
||||
prepare_block_runtime(block.runtime, add_overrides=add_get_block_overrides)
|
||||
block.runtime.get_block_for_descriptor = get_block
|
||||
block.bind_for_student(user.id)
|
||||
|
||||
# Descriptors can all share a single DescriptorSystem.
|
||||
# So, bind to the same one as the current descriptor.
|
||||
module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access
|
||||
return block
|
||||
|
||||
descriptor.bind_for_student(module_system, user.id)
|
||||
services = {
|
||||
'user': user_service,
|
||||
'mako': mako_service,
|
||||
'xqueue': XQueueService(
|
||||
url='http://xqueue.url',
|
||||
django_auth={},
|
||||
basic_auth=[],
|
||||
default_queuename='testqueue',
|
||||
waittime=10,
|
||||
construct_callback=Mock(name='get_test_system.xqueue.construct_callback', side_effect="/"),
|
||||
),
|
||||
'replace_urls': replace_url_service,
|
||||
'cache': CacheService(DoNothingCache()),
|
||||
'field-data': DictFieldData({}),
|
||||
'sandbox': SandboxService(contentstore, course_id),
|
||||
}
|
||||
|
||||
return descriptor
|
||||
descriptor_system.get_block_for_descriptor = get_block # lint-amnesty, pylint: disable=attribute-defined-outside-init
|
||||
descriptor_system._services.update(services) # lint-amnesty, pylint: disable=protected-access
|
||||
|
||||
return TestModuleSystem(
|
||||
get_block=get_block,
|
||||
services={
|
||||
'user': user_service,
|
||||
'mako': mako_service,
|
||||
'xqueue': XQueueService(
|
||||
url='http://xqueue.url',
|
||||
django_auth={},
|
||||
basic_auth=[],
|
||||
default_queuename='testqueue',
|
||||
waittime=10,
|
||||
construct_callback=Mock(name='get_test_system.xqueue.construct_callback', side_effect="/"),
|
||||
),
|
||||
'replace_urls': replace_url_service,
|
||||
'cache': CacheService(DoNothingCache()),
|
||||
'field-data': DictFieldData({}),
|
||||
'sandbox': SandboxService(contentstore, course_id),
|
||||
},
|
||||
descriptor_runtime=descriptor_system,
|
||||
id_reader=id_manager,
|
||||
id_generator=id_manager,
|
||||
return descriptor_system
|
||||
|
||||
|
||||
def prepare_block_runtime(
|
||||
runtime,
|
||||
course_id=CourseKey.from_string('/'.join(['org', 'course', 'run'])),
|
||||
user=None,
|
||||
user_is_staff=False,
|
||||
user_location=None,
|
||||
render_template=None,
|
||||
add_overrides=False,
|
||||
add_get_block=False,
|
||||
):
|
||||
"""
|
||||
Sets properties in the runtime of the specified descriptor that is
|
||||
required for tests.
|
||||
"""
|
||||
|
||||
if not user:
|
||||
user = Mock(name='get_test_system.user', is_staff=False)
|
||||
if not user_location:
|
||||
user_location = Mock(name='get_test_system.user_location')
|
||||
user_service = StubUserService(
|
||||
user=user,
|
||||
anonymous_user_id='student',
|
||||
user_is_staff=user_is_staff,
|
||||
user_role='student',
|
||||
request_country_code=user_location,
|
||||
)
|
||||
|
||||
mako_service = StubMakoService(render_template=render_template)
|
||||
|
||||
def get_test_descriptor_system(render_template=None):
|
||||
replace_url_service = StubReplaceURLService()
|
||||
|
||||
def get_block(block):
|
||||
"""Mocks module_system get_block function"""
|
||||
|
||||
prepare_block_runtime(block.runtime)
|
||||
block.bind_for_student(user.id)
|
||||
|
||||
return block
|
||||
|
||||
services = {
|
||||
'user': user_service,
|
||||
'mako': mako_service,
|
||||
'xqueue': XQueueService(
|
||||
url='http://xqueue.url',
|
||||
django_auth={},
|
||||
basic_auth=[],
|
||||
default_queuename='testqueue',
|
||||
waittime=10,
|
||||
construct_callback=Mock(name='get_test_system.xqueue.construct_callback', side_effect="/"),
|
||||
),
|
||||
'replace_urls': replace_url_service,
|
||||
'cache': CacheService(DoNothingCache()),
|
||||
'field-data': DictFieldData({}),
|
||||
'sandbox': SandboxService(contentstore, course_id),
|
||||
}
|
||||
|
||||
if add_overrides:
|
||||
runtime.handler_url_override = handler_url
|
||||
runtime.local_resource_url = local_resource_url
|
||||
runtime.get_asides = get_asides
|
||||
runtime.resources_fs = resources_fs
|
||||
|
||||
if add_get_block:
|
||||
runtime.get_block_for_descriptor = get_block
|
||||
|
||||
runtime._services.update(services) # lint-amnesty, pylint: disable=protected-access
|
||||
|
||||
# runtime.load_item=Mock(name='get_test_descriptor_system.load_item')
|
||||
# runtime.resources_fs=Mock(name='get_test_descriptor_system.resources_fs')
|
||||
# runtime.error_tracker=Mock(name='get_test_descriptor_system.error_tracker')
|
||||
# runtime.render_template=render_template or mock_render_template,
|
||||
# runtime.mixins=(InheritanceMixin, XModuleMixin)
|
||||
|
||||
return runtime
|
||||
|
||||
|
||||
def get_test_descriptor_system(render_template=None, **kwargs):
|
||||
"""
|
||||
Construct a test DescriptorSystem instance.
|
||||
"""
|
||||
field_data = DictFieldData({})
|
||||
|
||||
descriptor_system = MakoDescriptorSystem(
|
||||
descriptor_system = TestDescriptorSystem(
|
||||
load_item=Mock(name='get_test_descriptor_system.load_item'),
|
||||
resources_fs=Mock(name='get_test_descriptor_system.resources_fs'),
|
||||
error_tracker=Mock(name='get_test_descriptor_system.error_tracker'),
|
||||
render_template=render_template or mock_render_template,
|
||||
mixins=(InheritanceMixin, XModuleMixin),
|
||||
services={'field-data': field_data},
|
||||
**kwargs
|
||||
)
|
||||
descriptor_system.get_asides = lambda block: []
|
||||
return descriptor_system
|
||||
|
||||
@@ -6,8 +6,10 @@ Utility methods for unit tests.
|
||||
import filecmp
|
||||
import pprint
|
||||
|
||||
import pytest
|
||||
from path import Path as path
|
||||
from xblock.reference.user_service import UserService, XBlockUser
|
||||
from xmodule.x_module import DescriptorSystem
|
||||
|
||||
|
||||
def directories_equal(directory1, directory2):
|
||||
@@ -72,6 +74,7 @@ class StubUserService(UserService):
|
||||
self.user_role = user_role
|
||||
self.anonymous_user_id = anonymous_user_id
|
||||
self.request_country_code = request_country_code
|
||||
self._django_user = user
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def get_current_user(self):
|
||||
@@ -109,3 +112,17 @@ class StubReplaceURLService:
|
||||
Invokes the configured render_template method.
|
||||
"""
|
||||
return text
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def override_descriptor_system(monkeypatch):
|
||||
"""
|
||||
Fixture to override get_block method of DescriptorSystem
|
||||
"""
|
||||
|
||||
def get_block(self, usage_id, for_parent=None):
|
||||
"""See documentation for `xblock.runtime:Runtime.get_block`"""
|
||||
block = self.load_item(usage_id, for_parent=for_parent)
|
||||
return block
|
||||
|
||||
monkeypatch.setattr(DescriptorSystem, "get_block", get_block)
|
||||
|
||||
@@ -16,7 +16,7 @@ from xblock.fields import ScopeIds
|
||||
from xmodule.conditional_block import ConditionalBlock
|
||||
from xmodule.error_block import ErrorBlock
|
||||
from xmodule.modulestore.xml import CourseLocationManager, ImportSystem, XMLModuleStore
|
||||
from xmodule.tests import DATA_DIR, get_test_descriptor_system, get_test_system
|
||||
from xmodule.tests import DATA_DIR, get_test_system, prepare_block_runtime
|
||||
from xmodule.tests.xml import XModuleXmlImportTest
|
||||
from xmodule.tests.xml import factories as xml
|
||||
from xmodule.validation import StudioValidationMessage
|
||||
@@ -41,7 +41,8 @@ class DummySystem(ImportSystem): # lint-amnesty, pylint: disable=abstract-metho
|
||||
load_error_blocks=load_error_blocks,
|
||||
)
|
||||
|
||||
def render_template(self, template, context): # lint-amnesty, pylint: disable=method-hidden
|
||||
@property
|
||||
def render_template(self): # lint-amnesty, pylint: disable=method-hidden
|
||||
raise Exception("Shouldn't be called")
|
||||
|
||||
|
||||
@@ -65,7 +66,6 @@ class ConditionalFactory:
|
||||
|
||||
if the source_is_error_block flag is set, create a real ErrorBlock for the source.
|
||||
"""
|
||||
descriptor_system = get_test_descriptor_system()
|
||||
|
||||
# construct source descriptor and module:
|
||||
source_location = BlockUsageLocator(CourseLocator("edX", "conditional_test", "test_run", deprecated=True),
|
||||
@@ -83,17 +83,16 @@ class ConditionalFactory:
|
||||
source_descriptor.location = source_location
|
||||
|
||||
source_descriptor.visible_to_staff_only = source_visible_to_staff_only
|
||||
source_descriptor.runtime = descriptor_system
|
||||
source_descriptor.render = lambda view, context=None: descriptor_system.render(source_descriptor, view, context)
|
||||
source_descriptor.runtime = system
|
||||
source_descriptor.render = lambda view, context=None: system.render(source_descriptor, view, context)
|
||||
|
||||
# construct other descriptors:
|
||||
child_descriptor = Mock(name='child_descriptor')
|
||||
child_descriptor.visible_to_staff_only = False
|
||||
child_descriptor._xmodule.student_view.return_value = Fragment(content='<p>This is a secret</p>') # lint-amnesty, pylint: disable=protected-access
|
||||
child_descriptor.student_view = child_descriptor._xmodule.student_view # lint-amnesty, pylint: disable=protected-access
|
||||
child_descriptor.runtime = descriptor_system
|
||||
child_descriptor.xmodule_runtime = get_test_system()
|
||||
child_descriptor.render = lambda view, context=None: descriptor_system.render(child_descriptor, view, context)
|
||||
child_descriptor.runtime = system
|
||||
child_descriptor.render = lambda view, context=None: system.render(child_descriptor, view, context)
|
||||
child_descriptor.location = source_location.replace(category='html', name='child')
|
||||
|
||||
def visible_to_nonstaff_users(desc):
|
||||
@@ -109,9 +108,7 @@ class ConditionalFactory:
|
||||
source_location: source_descriptor
|
||||
}.get(usage_id)
|
||||
|
||||
descriptor_system.load_item = load_item
|
||||
|
||||
system.descriptor_runtime = descriptor_system
|
||||
system.load_item = load_item
|
||||
|
||||
# construct conditional block:
|
||||
cond_location = BlockUsageLocator(CourseLocator("edX", "conditional_test", "test_run", deprecated=True),
|
||||
@@ -125,11 +122,10 @@ class ConditionalFactory:
|
||||
})
|
||||
|
||||
cond_descriptor = ConditionalBlock(
|
||||
descriptor_system,
|
||||
system,
|
||||
field_data,
|
||||
ScopeIds(None, None, cond_location, cond_location)
|
||||
)
|
||||
cond_descriptor.xmodule_runtime = system
|
||||
system.get_block_for_descriptor = lambda desc: desc if visible_to_nonstaff_users(desc) else None
|
||||
cond_descriptor.get_required_blocks = [
|
||||
system.get_block_for_descriptor(source_descriptor),
|
||||
@@ -165,7 +161,7 @@ class ConditionalBlockBasicTest(unittest.TestCase):
|
||||
# because get_test_system returns the repr of the context dict passed to render_template,
|
||||
# we reverse it here
|
||||
html = blocks['cond_block'].render(STUDENT_VIEW).content
|
||||
mako_service = blocks['cond_block'].xmodule_runtime.service(blocks['cond_block'], 'mako')
|
||||
mako_service = blocks['cond_block'].runtime.service(blocks['cond_block'], 'mako')
|
||||
expected = mako_service.render_template('conditional_ajax.html', {
|
||||
'ajax_url': blocks['cond_block'].ajax_url,
|
||||
'element_id': 'i4x-edX-conditional_test-conditional-SampleConditional',
|
||||
@@ -220,7 +216,12 @@ class ConditionalBlockXmlTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.test_system = get_test_system()
|
||||
|
||||
def add_block_as_child_node(block, node):
|
||||
child = etree.SubElement(node, "unknown")
|
||||
block.add_xml_to_node(child)
|
||||
self.test_system = get_test_system(add_get_block_overrides=True)
|
||||
self.test_system.add_block_as_child_node = add_block_as_child_node
|
||||
self.modulestore = XMLModuleStore(DATA_DIR, source_dirs=['conditional_and_poll'])
|
||||
courses = self.modulestore.get_courses()
|
||||
assert len(courses) == 1
|
||||
@@ -241,7 +242,7 @@ class ConditionalBlockXmlTest(unittest.TestCase):
|
||||
|
||||
block = self.get_block_for_location(location)
|
||||
html = block.render(STUDENT_VIEW).content
|
||||
mako_service = block.xmodule_runtime.service(block, 'mako')
|
||||
mako_service = block.runtime.service(block, 'mako')
|
||||
html_expect = mako_service.render_template(
|
||||
'conditional_ajax.html',
|
||||
{
|
||||
@@ -355,12 +356,9 @@ class ConditionalBlockStudioTest(XModuleXmlImportTest):
|
||||
self.sequence = self.course.get_children()[0]
|
||||
self.conditional = self.sequence.get_children()[0]
|
||||
|
||||
self.module_system = get_test_system()
|
||||
self.module_system.descriptor_runtime = self.course._runtime # pylint: disable=protected-access
|
||||
|
||||
user = Mock(username='ma', email='ma@edx.org', is_staff=False, is_active=True)
|
||||
self.conditional.runtime = prepare_block_runtime(self.course.runtime)
|
||||
self.conditional.bind_for_student(
|
||||
self.module_system,
|
||||
user.id
|
||||
)
|
||||
|
||||
@@ -380,11 +378,11 @@ class ConditionalBlockStudioTest(XModuleXmlImportTest):
|
||||
}
|
||||
|
||||
context = create_studio_context(self.conditional, False)
|
||||
html = self.module_system.render(self.conditional, AUTHOR_VIEW, context).content
|
||||
html = self.course.runtime.render(self.conditional, AUTHOR_VIEW, context).content
|
||||
assert 'This is a secret HTML' in html
|
||||
|
||||
context = create_studio_context(self.sequence, True)
|
||||
html = self.module_system.render(self.conditional, AUTHOR_VIEW, context).content
|
||||
html = self.course.runtime.render(self.conditional, AUTHOR_VIEW, context).content
|
||||
assert 'This is a secret HTML' not in html
|
||||
|
||||
def test_non_editable_settings(self):
|
||||
|
||||
@@ -38,7 +38,7 @@ class TestErrorBlock(SetupTestErrorBlock):
|
||||
self.error_msg
|
||||
)
|
||||
assert isinstance(descriptor, ErrorBlock)
|
||||
descriptor.xmodule_runtime = self.system
|
||||
descriptor.runtime = self.system
|
||||
context_repr = self.system.render(descriptor, STUDENT_VIEW).content
|
||||
assert self.error_msg in context_repr
|
||||
assert repr(self.valid_xml) in context_repr
|
||||
|
||||
@@ -17,7 +17,6 @@ from xblock.fields import Integer, Scope, String
|
||||
from xblock.runtime import DictKeyValueStore, KvsFieldData
|
||||
|
||||
from xmodule.fields import Date
|
||||
from xmodule.modulestore import only_xmodules
|
||||
from xmodule.modulestore.inheritance import InheritanceMixin, compute_inherited_metadata
|
||||
from xmodule.modulestore.xml import ImportSystem, LibraryXMLModuleStore, XMLModuleStore
|
||||
from xmodule.tests import DATA_DIR
|
||||
@@ -52,9 +51,6 @@ class DummySystem(ImportSystem): # lint-amnesty, pylint: disable=abstract-metho
|
||||
services={'field-data': KvsFieldData(DictKeyValueStore())},
|
||||
)
|
||||
|
||||
def render_template(self, _template, _context): # lint-amnesty, pylint: disable=method-hidden
|
||||
raise Exception("Shouldn't be called")
|
||||
|
||||
|
||||
class BaseCourseTestCase(TestCase):
|
||||
'''Make sure block imports work properly, including for malformed inputs'''
|
||||
@@ -72,7 +68,6 @@ class BaseCourseTestCase(TestCase):
|
||||
DATA_DIR,
|
||||
source_dirs=[name],
|
||||
xblock_mixins=(InheritanceMixin,),
|
||||
xblock_select=only_xmodules,
|
||||
)
|
||||
courses = modulestore.get_courses()
|
||||
assert len(courses) == 1
|
||||
|
||||
@@ -19,7 +19,7 @@ from xmodule.library_tools import LibraryToolsService
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, LibraryFactory
|
||||
from xmodule.modulestore.tests.utils import MixedSplitTestCase
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.tests import prepare_block_runtime
|
||||
from xmodule.validation import StudioValidationMessage
|
||||
from xmodule.x_module import AUTHOR_VIEW
|
||||
from xmodule.capa_block import ProblemBlock
|
||||
@@ -58,20 +58,17 @@ class LibraryContentTest(MixedSplitTestCase):
|
||||
"""
|
||||
Bind a block (part of self.course) so we can access student-specific data.
|
||||
"""
|
||||
module_system = get_test_system(course_id=block.location.course_key)
|
||||
module_system.descriptor_runtime = block.runtime._descriptor_system # pylint: disable=protected-access
|
||||
module_system._services['library_tools'] = self.tools # pylint: disable=protected-access
|
||||
prepare_block_runtime(block.runtime, course_id=block.location.course_key)
|
||||
block.runtime._runtime_services.update({'library_tools': self.tools}) # lint-amnesty, pylint: disable=protected-access
|
||||
|
||||
def get_block(descriptor):
|
||||
"""Mocks module_system get_block function"""
|
||||
sub_module_system = get_test_system(course_id=block.location.course_key)
|
||||
sub_module_system.get_block_for_descriptor = get_block
|
||||
sub_module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access
|
||||
descriptor.bind_for_student(sub_module_system, self.user_id)
|
||||
prepare_block_runtime(descriptor.runtime, course_id=block.location.course_key)
|
||||
descriptor.runtime.get_block_for_descriptor = get_block
|
||||
descriptor.bind_for_student(self.user_id)
|
||||
return descriptor
|
||||
|
||||
module_system.get_block_for_descriptor = get_block
|
||||
block.xmodule_runtime = module_system
|
||||
block.runtime.get_block_for_descriptor = get_block
|
||||
|
||||
|
||||
class TestLibraryContentExportImport(LibraryContentTest):
|
||||
@@ -99,7 +96,7 @@ class TestLibraryContentExportImport(LibraryContentTest):
|
||||
|
||||
# Set the virtual FS to export the olx to.
|
||||
self.export_fs = MemoryFS()
|
||||
self.lc_block.runtime._descriptor_system.export_fs = self.export_fs # pylint: disable=protected-access
|
||||
self.lc_block.runtime.export_fs = self.export_fs # pylint: disable=protected-access
|
||||
|
||||
# Prepare runtime for the import.
|
||||
self.runtime = TestImportSystem(load_error_blocks=True, course_id=self.lc_block.location.course_key)
|
||||
@@ -517,7 +514,7 @@ class TestLibraryContentAnalytics(LibraryContentTest):
|
||||
self.lc_block.refresh_children()
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_block(self.lc_block)
|
||||
self.lc_block.xmodule_runtime.publish = self.publisher
|
||||
self.lc_block.runtime.publish = self.publisher
|
||||
|
||||
def _assert_event_was_published(self, event_type):
|
||||
"""
|
||||
@@ -571,7 +568,7 @@ class TestLibraryContentAnalytics(LibraryContentTest):
|
||||
with self.store.branch_setting(ModuleStoreEnum.Branch.published_only):
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_block(self.lc_block)
|
||||
self.lc_block.xmodule_runtime.publish = self.publisher
|
||||
self.lc_block.runtime.publish = self.publisher
|
||||
self.test_assigned_event()
|
||||
|
||||
def test_assigned_descendants(self):
|
||||
@@ -590,7 +587,7 @@ class TestLibraryContentAnalytics(LibraryContentTest):
|
||||
# Reload lc_block and set it up for a student:
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_block(self.lc_block)
|
||||
self.lc_block.xmodule_runtime.publish = self.publisher
|
||||
self.lc_block.runtime.publish = self.publisher
|
||||
|
||||
# Get the keys of each of our blocks, as they appear in the course:
|
||||
course_usage_main_vertical = self.lc_block.children[0]
|
||||
|
||||
@@ -6,7 +6,7 @@ from openedx.core.djangoapps.content_libraries.tests.base import ContentLibrarie
|
||||
from common.djangoapps.student.roles import CourseInstructorRole
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.tests import prepare_block_runtime
|
||||
from xmodule.x_module import STUDENT_VIEW # lint-amnesty, pylint: disable=unused-import
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ class LibrarySourcedBlockTestCase(ContentLibrariesRestApiTest):
|
||||
"""
|
||||
context = context or {}
|
||||
block = self.store.get_item(block.location)
|
||||
module_system = get_test_system(block)
|
||||
module_system.descriptor_runtime = block._runtime # pylint: disable=protected-access
|
||||
block.bind_for_student(module_system, self.user.id)
|
||||
return module_system.render(block, view, context).content
|
||||
prepare_block_runtime(block.runtime)
|
||||
block.bind_for_student(self.user.id)
|
||||
return block.runtime.render(block, view, context).content
|
||||
|
||||
@@ -370,7 +370,7 @@ class LTI20RESTResultServiceTest(unittest.TestCase):
|
||||
Test that we get a 404 when the supplied user does not exist
|
||||
"""
|
||||
self.setup_system_xblock_mocks_for_lti20_request_test()
|
||||
self.runtime._services['user'] = StubUserService(user=None) # pylint: disable=protected-access
|
||||
self.runtime._runtime_services['user'] = StubUserService(user=None) # pylint: disable=protected-access
|
||||
mock_request = self.get_signed_lti20_mock_request(self.GOOD_JSON_PUT)
|
||||
response = self.xblock.lti_2_0_result_rest_handler(mock_request, "user/abcd")
|
||||
assert response.status_code == 404
|
||||
|
||||
@@ -65,7 +65,7 @@ class LTIBlockTest(TestCase):
|
||||
self.course_id = CourseKey.from_string('org/course/run')
|
||||
self.system = get_test_system(self.course_id)
|
||||
self.system.publish = Mock()
|
||||
self.system._services['rebind_user'] = Mock() # pylint: disable=protected-access
|
||||
self.system._runtime_services['rebind_user'] = Mock() # pylint: disable=protected-access
|
||||
|
||||
self.xblock = LTIBlock(
|
||||
self.system,
|
||||
@@ -178,7 +178,7 @@ class LTIBlockTest(TestCase):
|
||||
"""
|
||||
If we have no real user, we should send back failure response.
|
||||
"""
|
||||
self.system._services['user'] = StubUserService(user=None) # pylint: disable=protected-access
|
||||
self.system._runtime_services['user'] = StubUserService(user=None) # pylint: disable=protected-access
|
||||
self.xblock.verify_oauth_body_sign = Mock()
|
||||
self.xblock.has_score = True
|
||||
request = Request(self.environ)
|
||||
|
||||
@@ -9,7 +9,7 @@ from lxml import etree
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.utils import MixedSplitTestCase
|
||||
from xmodule.randomize_block import RandomizeBlock
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.tests import prepare_block_runtime
|
||||
|
||||
from .test_course_block import DummySystem as TestImportSystem
|
||||
|
||||
@@ -42,9 +42,7 @@ class RandomizeBlockTest(MixedSplitTestCase):
|
||||
Bind module system to block so we can access student-specific data.
|
||||
"""
|
||||
user = Mock(name='get_test_system.user', id=user_id, is_staff=False)
|
||||
module_system = get_test_system(course_id=block.location.course_key, user=user)
|
||||
module_system.descriptor_runtime = block.runtime._descriptor_system # pylint: disable=protected-access
|
||||
block.xmodule_runtime = module_system
|
||||
prepare_block_runtime(block.runtime, course_id=block.location.course_key, user=user)
|
||||
|
||||
def test_xml_export_import_cycle(self):
|
||||
"""
|
||||
@@ -64,7 +62,7 @@ class RandomizeBlockTest(MixedSplitTestCase):
|
||||
|
||||
export_fs = MemoryFS()
|
||||
# Set the virtual FS to export the olx to.
|
||||
randomize_block.runtime._descriptor_system.export_fs = export_fs # pylint: disable=protected-access
|
||||
randomize_block.runtime.export_fs = export_fs # pylint: disable=protected-access
|
||||
|
||||
# Export the olx.
|
||||
node = etree.Element("unknown_root")
|
||||
|
||||
@@ -19,7 +19,7 @@ from web_fragments.fragment import Fragment
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from xmodule.seq_block import TIMED_EXAM_GATING_WAFFLE_FLAG, SequenceBlock
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.tests import get_test_system, prepare_block_runtime
|
||||
from xmodule.tests.helpers import StubUserService
|
||||
from xmodule.tests.xml import XModuleXmlImportTest
|
||||
from xmodule.tests.xml import factories as xml
|
||||
@@ -94,11 +94,8 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
|
||||
self._set_up_module_system(block)
|
||||
|
||||
block.xmodule_runtime._services['bookmarks'] = Mock() # pylint: disable=protected-access
|
||||
block.xmodule_runtime._services['completion'] = Mock( # pylint: disable=protected-access
|
||||
return_value=Mock(vertical_is_complete=Mock(return_value=True))
|
||||
)
|
||||
block.xmodule_runtime._services['user'] = StubUserService(user=Mock()) # pylint: disable=protected-access
|
||||
block.runtime._runtime_services['bookmarks'] = Mock() # pylint: disable=protected-access
|
||||
block.runtime._runtime_services['user'] = StubUserService(user=Mock()) # pylint: disable=protected-access
|
||||
block.parent = parent.location
|
||||
return block
|
||||
|
||||
@@ -106,14 +103,12 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
"""
|
||||
Sets up the test module system for the given block.
|
||||
"""
|
||||
module_system = get_test_system()
|
||||
module_system.descriptor_runtime = block._runtime # pylint: disable=protected-access
|
||||
block.xmodule_runtime = module_system
|
||||
prepare_block_runtime(block.runtime)
|
||||
|
||||
# The render operation will ask modulestore for the current course to get some data. As these tests were
|
||||
# originally not written to be compatible with a real modulestore, we've mocked out the relevant return values.
|
||||
module_system.modulestore = Mock()
|
||||
module_system.modulestore.get_course.return_value = self.course
|
||||
block.runtime.modulestore = Mock()
|
||||
block.runtime.modulestore.get_course.return_value = self.course
|
||||
|
||||
def _get_rendered_view(self,
|
||||
sequence,
|
||||
@@ -130,7 +125,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
context.update(extra_context)
|
||||
|
||||
self.course.self_paced = self_paced
|
||||
return sequence.xmodule_runtime.render(sequence, view, context).content
|
||||
return sequence.runtime.render(sequence, view, context).content
|
||||
|
||||
def _assert_view_at_position(self, rendered_html, expected_position):
|
||||
"""
|
||||
@@ -139,10 +134,10 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
assert f"'position': {expected_position}" in rendered_html
|
||||
|
||||
def test_student_view_init(self):
|
||||
module_system = get_test_system()
|
||||
module_system.position = 2
|
||||
seq_block = SequenceBlock(runtime=module_system, scope_ids=Mock())
|
||||
seq_block.bind_for_student(module_system, 34)
|
||||
runtime = get_test_system()
|
||||
runtime.position = 2
|
||||
seq_block = SequenceBlock(runtime=runtime, scope_ids=Mock())
|
||||
seq_block.bind_for_student(34)
|
||||
assert seq_block.position == 2
|
||||
# matches position set in the runtime
|
||||
|
||||
@@ -335,7 +330,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
False,
|
||||
{'url': 'PrereqUrl', 'display_name': 'PrereqSectionName', 'id': 'mockId'}
|
||||
]
|
||||
self.sequence_1_2.xmodule_runtime._services['gating'] = gating_mock_1_2 # pylint: disable=protected-access
|
||||
self.sequence_1_2.runtime._services['gating'] = gating_mock_1_2 # pylint: disable=protected-access
|
||||
self.sequence_1_2.display_name = 'sequence_1_2'
|
||||
|
||||
html = self._get_rendered_view(
|
||||
@@ -373,6 +368,9 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
|
||||
def test_xblock_handler_get_completion_success(self):
|
||||
"""Test that the completion data is returned successfully on targeted vertical through ajax call"""
|
||||
self.sequence_3_1.runtime._runtime_services['completion'] = Mock( # pylint: disable=protected-access
|
||||
return_value=Mock(vertical_is_complete=Mock(return_value=True))
|
||||
)
|
||||
for child in self.sequence_3_1.get_children():
|
||||
usage_key = str(child.location)
|
||||
request = RequestFactory().post(
|
||||
@@ -382,6 +380,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
)
|
||||
completion_return = self.sequence_3_1.handle('get_completion', request)
|
||||
assert completion_return.json == {'complete': True}
|
||||
self.sequence_3_1.runtime._runtime_services['completion'] = None # pylint: disable=protected-access
|
||||
|
||||
def test_xblock_handler_get_completion_bad_key(self):
|
||||
"""Test that the completion data is returned as False when usage key is None through ajax call"""
|
||||
@@ -395,10 +394,14 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
|
||||
def test_handle_ajax_get_completion_success(self):
|
||||
"""Test that the old-style ajax handler for completion still works"""
|
||||
self.sequence_3_1.runtime._runtime_services['completion'] = Mock( # pylint: disable=protected-access
|
||||
return_value=Mock(vertical_is_complete=Mock(return_value=True))
|
||||
)
|
||||
for child in self.sequence_3_1.get_children():
|
||||
usage_key = str(child.location)
|
||||
completion_return = self.sequence_3_1.handle_ajax('get_completion', {'usage_key': usage_key})
|
||||
assert json.loads(completion_return) == {'complete': True}
|
||||
self.sequence_3_1.runtime._runtime_services['completion'] = None # pylint: disable=protected-access
|
||||
|
||||
def test_xblock_handler_goto_position_success(self):
|
||||
"""Test that we can set position through ajax call"""
|
||||
@@ -434,7 +437,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
"""Test that the sequence metadata is returned correctly"""
|
||||
# rather than dealing with json serialization of the Mock object,
|
||||
# let's just disable the bookmarks service
|
||||
self.sequence_3_1.xmodule_runtime._services['bookmarks'] = None # lint-amnesty, pylint: disable=protected-access
|
||||
self.sequence_3_1.runtime._services['bookmarks'] = None # lint-amnesty, pylint: disable=protected-access
|
||||
metadata = self.sequence_3_1.get_metadata()
|
||||
assert len(metadata['items']) == 3
|
||||
assert metadata['tag'] == 'sequential'
|
||||
@@ -445,7 +448,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
|
||||
))
|
||||
def test_get_metadata_content_type_gated_content(self):
|
||||
"""The contains_content_type_gated_content field tells whether the item contains content type gated content"""
|
||||
self.sequence_5_1.xmodule_runtime._services['bookmarks'] = None # pylint: disable=protected-access
|
||||
self.sequence_5_1.runtime._services['bookmarks'] = None # pylint: disable=protected-access
|
||||
ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
|
||||
metadata = self.sequence_5_1.get_metadata()
|
||||
assert metadata['items'][0]['contains_content_type_gated_content'] is False
|
||||
|
||||
@@ -18,7 +18,7 @@ from xmodule.split_test_block import (
|
||||
get_split_user_partitions,
|
||||
user_partition_values,
|
||||
)
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.tests import prepare_block_runtime
|
||||
from xmodule.tests.test_course_block import DummySystem as TestImportSystem
|
||||
from xmodule.tests.xml import XModuleXmlImportTest
|
||||
from xmodule.tests.xml import factories as xml
|
||||
@@ -85,9 +85,9 @@ class SplitTestBlockTest(XModuleXmlImportTest, PartitionTestCase):
|
||||
|
||||
self.course = self.process_xml(course)
|
||||
self.course_sequence = self.course.get_children()[0]
|
||||
self.module_system = get_test_system()
|
||||
user = Mock(username='ma', email='ma@edx.org', is_staff=False, is_active=True)
|
||||
prepare_block_runtime(self.course.runtime, user=user, add_get_block=True)
|
||||
|
||||
self.module_system.descriptor_runtime = self.course._runtime # pylint: disable=protected-access
|
||||
self.course.runtime.export_fs = MemoryFS()
|
||||
|
||||
# Create mock partition service, as these tests are running with XML in-memory system.
|
||||
@@ -106,19 +106,15 @@ class SplitTestBlockTest(XModuleXmlImportTest, PartitionTestCase):
|
||||
self.course,
|
||||
course_id=self.course.id,
|
||||
)
|
||||
self.module_system._services['partitions'] = partitions_service # pylint: disable=protected-access
|
||||
self.course.runtime._runtime_services['partitions'] = partitions_service # pylint: disable=protected-access
|
||||
|
||||
# Mock user_service user
|
||||
user_service = Mock()
|
||||
user = Mock(username='ma', email='ma@edx.org', is_staff=False, is_active=True)
|
||||
user_service._django_user = user # lint-amnesty, pylint: disable=protected-access
|
||||
self.module_system._services['user'] = user_service # pylint: disable=protected-access
|
||||
|
||||
self.split_test_block = self.course_sequence.get_children()[0]
|
||||
self.split_test_block.bind_for_student(
|
||||
self.module_system,
|
||||
user.id
|
||||
)
|
||||
self.split_test_block.runtime = self.course.runtime
|
||||
self.split_test_block.bind_for_student(user.id)
|
||||
|
||||
# Create mock modulestore for getting the course. Needed for rendering the HTML
|
||||
# view, since mock services exist and the rendering code will not short-circuit.
|
||||
@@ -152,7 +148,7 @@ class SplitTestBlockLMSTest(SplitTestBlockTest):
|
||||
@ddt.unpack
|
||||
def test_get_html(self, user_tag, child_content):
|
||||
self.user_partition.scheme.current_group = self.user_partition.groups[user_tag]
|
||||
assert child_content in self.module_system.render(self.split_test_block, STUDENT_VIEW).content
|
||||
assert child_content in self.course.runtime.render(self.split_test_block, STUDENT_VIEW).content
|
||||
|
||||
@ddt.data(0, 1)
|
||||
def test_child_missing_tag_value(self, _user_tag):
|
||||
@@ -175,7 +171,7 @@ class SplitTestBlockLMSTest(SplitTestBlockTest):
|
||||
|
||||
# Mock out the process_xml
|
||||
# Expect it to return a child descriptor for the SplitTestDescriptor when called.
|
||||
self.module_system.process_xml = Mock()
|
||||
self.course.runtime.process_xml = Mock()
|
||||
|
||||
# Write out the xml.
|
||||
xml_obj = self.split_test_block.definition_to_xml(MemoryFS())
|
||||
@@ -184,7 +180,7 @@ class SplitTestBlockLMSTest(SplitTestBlockTest):
|
||||
assert xml_obj.get('group_id_to_child') is not None
|
||||
|
||||
# Read the xml back in.
|
||||
fields, children = SplitTestBlock.definition_from_xml(xml_obj, self.module_system)
|
||||
fields, children = SplitTestBlock.definition_from_xml(xml_obj, self.course.runtime)
|
||||
assert fields.get('user_partition_id') == '0'
|
||||
assert fields.get('group_id_to_child') is not None
|
||||
assert len(children) == 2
|
||||
@@ -212,13 +208,13 @@ class SplitTestBlockStudioTest(SplitTestBlockTest):
|
||||
|
||||
# The split_test block should render both its groups when it is the root
|
||||
context = create_studio_context(self.split_test_block)
|
||||
html = self.module_system.render(self.split_test_block, AUTHOR_VIEW, context).content
|
||||
html = self.course.runtime.render(self.split_test_block, AUTHOR_VIEW, context).content
|
||||
assert 'HTML FOR GROUP 0' in html
|
||||
assert 'HTML FOR GROUP 1' in html
|
||||
|
||||
# When rendering as a child, it shouldn't render either of its groups
|
||||
context = create_studio_context(self.course_sequence)
|
||||
html = self.module_system.render(self.split_test_block, AUTHOR_VIEW, context).content
|
||||
html = self.course.runtime.render(self.split_test_block, AUTHOR_VIEW, context).content
|
||||
assert 'HTML FOR GROUP 0' not in html
|
||||
assert 'HTML FOR GROUP 1' not in html
|
||||
|
||||
@@ -228,7 +224,7 @@ class SplitTestBlockStudioTest(SplitTestBlockTest):
|
||||
UserPartition(0, 'first_partition', 'First Partition',
|
||||
[Group("0", 'alpha'), Group("1", 'beta'), Group("2", 'gamma')])
|
||||
]
|
||||
html = self.module_system.render(self.split_test_block, AUTHOR_VIEW, context).content
|
||||
html = self.course.runtime.render(self.split_test_block, AUTHOR_VIEW, context).content
|
||||
assert 'HTML FOR GROUP 0' in html
|
||||
assert 'HTML FOR GROUP 1' in html
|
||||
|
||||
@@ -569,7 +565,7 @@ class SplitTestBlockExportImportTest(MixedSplitTestCase):
|
||||
)
|
||||
export_fs = MemoryFS()
|
||||
# Set the virtual FS to export the olx to.
|
||||
split_test_block.runtime._descriptor_system.export_fs = export_fs # pylint: disable=protected-access
|
||||
split_test_block.runtime.export_fs = export_fs # pylint: disable=protected-access
|
||||
|
||||
# Export the olx.
|
||||
node = lxml.etree.Element("unknown_root")
|
||||
|
||||
@@ -24,6 +24,6 @@ class StudioEditableBlockTestCase(BaseVerticalBlockTest):
|
||||
}
|
||||
|
||||
# Both children of the vertical should be rendered as reorderable
|
||||
self.module_system.render(self.vertical, AUTHOR_VIEW, context).content # pylint: disable=expression-not-assigned
|
||||
self.course.runtime.render(self.vertical, AUTHOR_VIEW, context).content # pylint: disable=expression-not-assigned
|
||||
assert self.vertical.get_children()[0].location in reorderable_items
|
||||
assert self.vertical.get_children()[1].location in reorderable_items
|
||||
|
||||
@@ -18,7 +18,7 @@ from django.test import override_settings
|
||||
from openedx_filters import PipelineStep
|
||||
from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted
|
||||
|
||||
from . import get_test_system
|
||||
from . import prepare_block_runtime
|
||||
from .helpers import StubUserService
|
||||
from .xml import XModuleXmlImportTest
|
||||
from .xml import factories as xml
|
||||
@@ -164,13 +164,12 @@ class BaseVerticalBlockTest(XModuleXmlImportTest):
|
||||
|
||||
self.course = self.process_xml(course)
|
||||
course_seq = self.course.get_children()[0]
|
||||
self.module_system = get_test_system()
|
||||
prepare_block_runtime(self.course.runtime)
|
||||
|
||||
self.module_system.descriptor_runtime = self.course._runtime
|
||||
self.course.runtime.export_fs = MemoryFS()
|
||||
|
||||
self.vertical = course_seq.get_children()[0]
|
||||
self.vertical.xmodule_runtime = self.module_system
|
||||
self.vertical.runtime = self.course.runtime
|
||||
|
||||
self.html_block = self.vertical.get_children()[0]
|
||||
self.problem_block = self.vertical.get_children()[1]
|
||||
@@ -213,17 +212,19 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
"""
|
||||
Test the rendering of the student and public view.
|
||||
"""
|
||||
self.module_system._services['bookmarks'] = Mock()
|
||||
self.course.runtime._runtime_services['bookmarks'] = Mock()
|
||||
now = datetime.now(pytz.UTC)
|
||||
self.vertical.due = now + timedelta(days=days)
|
||||
if view == STUDENT_VIEW:
|
||||
self.module_system._services['user'] = StubUserService(user=Mock(username=self.username))
|
||||
self.module_system._services['completion'] = StubCompletionService(enabled=True,
|
||||
completion_value=completion_value)
|
||||
self.course.runtime._runtime_services['user'] = StubUserService(user=Mock(username=self.username))
|
||||
self.course.runtime._runtime_services['completion'] = StubCompletionService(
|
||||
enabled=True,
|
||||
completion_value=completion_value
|
||||
)
|
||||
elif view == PUBLIC_VIEW:
|
||||
self.module_system._services['user'] = StubUserService(user=AnonymousUser())
|
||||
self.course.runtime._runtime_services['user'] = StubUserService(user=AnonymousUser())
|
||||
|
||||
html = self.module_system.render(
|
||||
html = self.course.runtime.render(
|
||||
self.vertical, view, self.default_context if context is None else context
|
||||
).content
|
||||
assert self.test_html in html
|
||||
@@ -248,15 +249,15 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
"""
|
||||
Test the rendering of the student and public view.
|
||||
"""
|
||||
self.module_system._services['bookmarks'] = Mock()
|
||||
self.module_system._services['user'] = StubUserService(user=Mock())
|
||||
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
self.course.runtime._services['bookmarks'] = Mock()
|
||||
self.course.runtime._services['user'] = StubUserService(user=Mock())
|
||||
self.course.runtime._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
|
||||
now = datetime.now(pytz.UTC)
|
||||
self.vertical.due = now + timedelta(days=-1)
|
||||
self.problem_block.has_score = has_score
|
||||
|
||||
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
html = self.course.runtime.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
if has_score:
|
||||
assert "'has_assignments': True" in html
|
||||
assert "'completed': False" in html
|
||||
@@ -270,14 +271,14 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
@ddt.unpack
|
||||
def test_render_access_denied_blocks(self, node_has_access_error, child_has_access_error):
|
||||
""" Tests access denied blocks are not rendered when hide_access_error_blocks is True """
|
||||
self.module_system._services['bookmarks'] = Mock()
|
||||
self.module_system._services['user'] = StubUserService(user=Mock())
|
||||
self.course.runtime._services['bookmarks'] = Mock()
|
||||
self.course.runtime._services['user'] = StubUserService(user=Mock())
|
||||
self.vertical.due = datetime.now(pytz.UTC) + timedelta(days=-1)
|
||||
self.problem_block.has_access_error = node_has_access_error
|
||||
self.nested_problem_block.has_access_error = child_has_access_error
|
||||
|
||||
context = {'username': self.username, 'hide_access_error_blocks': True}
|
||||
html = self.module_system.render(self.vertical, STUDENT_VIEW, context).content
|
||||
html = self.course.runtime.render(self.vertical, STUDENT_VIEW, context).content
|
||||
|
||||
if node_has_access_error and child_has_access_error:
|
||||
assert self.test_problem not in html
|
||||
@@ -316,11 +317,11 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
Test that mark-completed-on-view-after-delay is only set for relevant child Xblocks.
|
||||
"""
|
||||
with patch.object(self.html_block, 'render') as mock_student_view:
|
||||
self.module_system._services['completion'] = StubCompletionService(
|
||||
self.course.runtime._services['completion'] = StubCompletionService(
|
||||
enabled=completion_enabled,
|
||||
completion_value=completion_value,
|
||||
)
|
||||
self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context)
|
||||
self.course.runtime.render(self.vertical, STUDENT_VIEW, self.default_context)
|
||||
if mark_completed_enabled:
|
||||
assert mock_student_view.call_args[0][1]['wrap_xblock_data']['mark-completed-on-view-after-delay'] ==\
|
||||
9876
|
||||
@@ -335,7 +336,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
context = {
|
||||
'is_unit_page': True
|
||||
}
|
||||
html = self.module_system.render(self.vertical, AUTHOR_VIEW, context).content
|
||||
html = self.course.runtime.render(self.vertical, AUTHOR_VIEW, context).content
|
||||
assert self.test_html not in html
|
||||
assert self.test_problem not in html
|
||||
|
||||
@@ -345,7 +346,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
'is_unit_page': False,
|
||||
'reorderable_items': reorderable_items,
|
||||
}
|
||||
html = self.module_system.render(self.vertical, AUTHOR_VIEW, context).content
|
||||
html = self.course.runtime.render(self.vertical, AUTHOR_VIEW, context).content
|
||||
assert self.test_html in html
|
||||
assert self.test_problem in html
|
||||
|
||||
@@ -363,11 +364,11 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
"""
|
||||
Test the VerticalBlockChildRenderStarted filter's effects on student view.
|
||||
"""
|
||||
self.module_system._services['bookmarks'] = Mock()
|
||||
self.module_system._services['user'] = StubUserService(user=Mock())
|
||||
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
self.course.runtime._services['bookmarks'] = Mock()
|
||||
self.course.runtime._services['user'] = StubUserService(user=Mock())
|
||||
self.course.runtime._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
|
||||
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
html = self.course.runtime.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
|
||||
assert TestVerticalBlockChildRenderStep.filter_content in html
|
||||
|
||||
@@ -385,11 +386,11 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
"""
|
||||
Test VerticalBlockChildRenderStarted filter can be used to skip child blocks.
|
||||
"""
|
||||
self.module_system._services['bookmarks'] = Mock()
|
||||
self.module_system._services['user'] = StubUserService(user=Mock())
|
||||
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
self.course.runtime._services['bookmarks'] = Mock()
|
||||
self.course.runtime._services['user'] = StubUserService(user=Mock())
|
||||
self.course.runtime._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
|
||||
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
html = self.course.runtime.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
|
||||
assert self.test_html not in html
|
||||
assert self.test_html_nested not in html
|
||||
@@ -408,11 +409,11 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
"""
|
||||
Test the VerticalBlockRenderCompleted filter's execution.
|
||||
"""
|
||||
self.module_system._services['bookmarks'] = Mock()
|
||||
self.module_system._services['user'] = StubUserService(user=Mock())
|
||||
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
self.course.runtime._services['bookmarks'] = Mock()
|
||||
self.course.runtime._services['user'] = StubUserService(user=Mock())
|
||||
self.course.runtime._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
|
||||
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
html = self.course.runtime.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
|
||||
assert TestVerticalBlockRenderCompletedStep.filter_content in html
|
||||
|
||||
@@ -430,10 +431,10 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
|
||||
"""
|
||||
Test VerticalBlockRenderCompleted filter can be used to prevent vertical block from rendering.
|
||||
"""
|
||||
self.module_system._services['bookmarks'] = Mock()
|
||||
self.module_system._services['user'] = StubUserService(user=Mock())
|
||||
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
self.course.runtime._services['bookmarks'] = Mock()
|
||||
self.course.runtime._services['user'] = StubUserService(user=Mock())
|
||||
self.course.runtime._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
|
||||
|
||||
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
html = self.course.runtime.render(self.vertical, STUDENT_VIEW, self.default_context).content
|
||||
|
||||
assert TestPreventVerticalBlockRenderStep.filter_content == html
|
||||
|
||||
@@ -11,7 +11,6 @@ from fs.osfs import OSFS
|
||||
from lxml import etree
|
||||
from xblock.mixins import HierarchyMixin
|
||||
|
||||
from xmodule.modulestore import only_xmodules
|
||||
from xmodule.modulestore.inheritance import InheritanceMixin
|
||||
from xmodule.x_module import XModuleMixin
|
||||
|
||||
@@ -72,7 +71,6 @@ class XmlImportFactory(Factory):
|
||||
|
||||
filesystem = OSFS(mkdtemp())
|
||||
xblock_mixins = (InheritanceMixin, XModuleMixin, HierarchyMixin)
|
||||
xblock_select = only_xmodules
|
||||
url_name = Sequence(str)
|
||||
attribs = {}
|
||||
policy = {}
|
||||
|
||||
@@ -30,7 +30,6 @@ from xblock.completable import XBlockCompletionMode
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import ScopeIds
|
||||
from xblock.runtime import KvsFieldData
|
||||
from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem
|
||||
|
||||
from common.djangoapps.xblock_django.constants import ATTR_KEY_REQUEST_COUNTRY_CODE
|
||||
from openedx.core.djangoapps.video_config.models import HLSPlaybackEnabledFlag, CourseYoutubeBlockedFlag
|
||||
@@ -481,17 +480,12 @@ class VideoBlock(
|
||||
|
||||
return self.runtime.service(self, 'mako').render_template('video.html', template_context)
|
||||
|
||||
def _is_lms_platform(self):
|
||||
"""
|
||||
Returns True if the platform is LMS.
|
||||
"""
|
||||
return isinstance(self.xmodule_runtime, LmsModuleSystem)
|
||||
|
||||
def _get_public_video_url(self):
|
||||
"""
|
||||
Returns the public video url
|
||||
"""
|
||||
if self.public_access and self._is_lms_platform():
|
||||
is_studio = getattr(self.runtime, "is_author_mode", False)
|
||||
if self.public_access and not is_studio:
|
||||
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
|
||||
if PUBLIC_VIDEO_SHARE.is_enabled(self.location.course_key):
|
||||
return urljoin(
|
||||
|
||||
1021
xmodule/x_module.py
1021
xmodule/x_module.py
@@ -313,24 +313,42 @@ class XModuleMixin(XModuleFields, XBlock):
|
||||
icon_class = 'other'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.xmodule_runtime = None
|
||||
self._asides = []
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def runtime(self):
|
||||
return CombinedSystem(self.xmodule_runtime, self._runtime)
|
||||
return self._runtime
|
||||
|
||||
@runtime.setter
|
||||
def runtime(self, value):
|
||||
self._runtime = value
|
||||
|
||||
@property
|
||||
def xmodule_runtime(self):
|
||||
"""
|
||||
Shim to maintain backward compatibility.
|
||||
|
||||
Deprecated in favor of the runtime property.
|
||||
"""
|
||||
warnings.warn(
|
||||
'xmodule_runtime property is deprecated. Please use the runtime property instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self.runtime
|
||||
|
||||
@property
|
||||
def system(self):
|
||||
"""
|
||||
Return the XBlock runtime (backwards compatibility alias provided for XModules).
|
||||
|
||||
Deprecated in favor of the runtime property.
|
||||
"""
|
||||
warnings.warn(
|
||||
'system property is deprecated. Please use the runtime property instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self.runtime
|
||||
|
||||
@property
|
||||
@@ -593,12 +611,11 @@ class XModuleMixin(XModuleFields, XBlock):
|
||||
"""
|
||||
return None
|
||||
|
||||
def bind_for_student(self, xmodule_runtime, user_id, wrappers=None):
|
||||
def bind_for_student(self, user_id, wrappers=None):
|
||||
"""
|
||||
Set up this XBlock to act as an XModule instead of an XModuleDescriptor.
|
||||
|
||||
Arguments:
|
||||
xmodule_runtime (:class:`ModuleSystem'): the runtime to use when accessing student facing methods
|
||||
user_id: The user_id to set in scope_ids
|
||||
wrappers: These are a list functions that put a wrapper, such as
|
||||
LmsFieldData or OverrideFieldData, around the field_data.
|
||||
@@ -608,8 +625,8 @@ class XModuleMixin(XModuleFields, XBlock):
|
||||
|
||||
# Skip rebinding if we're already bound a user, and it's this user.
|
||||
if self.scope_ids.user_id is not None and user_id == self.scope_ids.user_id:
|
||||
if getattr(xmodule_runtime, 'position', None):
|
||||
self.position = xmodule_runtime.position # update the position of the tab
|
||||
if getattr(self.runtime, 'position', None):
|
||||
self.position = self.runtime.position # update the position of the tab
|
||||
return
|
||||
|
||||
# If we are switching users mid-request, save the data from the old user.
|
||||
@@ -634,9 +651,6 @@ class XModuleMixin(XModuleFields, XBlock):
|
||||
if field in self._dirty_fields:
|
||||
del self._dirty_fields[field]
|
||||
|
||||
# Set the new xmodule_runtime and field_data (which are user-specific)
|
||||
self.xmodule_runtime = xmodule_runtime
|
||||
|
||||
if wrappers is None:
|
||||
wrappers = []
|
||||
|
||||
@@ -1009,10 +1023,11 @@ def descriptor_global_local_resource_url(block, uri):
|
||||
|
||||
class MetricsMixin:
|
||||
"""
|
||||
Mixin for adding metric logging for render and handle methods in the DescriptorSystem and ModuleSystem.
|
||||
Mixin for adding metric logging for render and handle methods in the DescriptorSystem.
|
||||
"""
|
||||
|
||||
def render(self, block, view_name, context=None): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
context = context or {}
|
||||
start_time = time.time()
|
||||
try:
|
||||
return super().render(block, view_name, context=context)
|
||||
@@ -1043,7 +1058,379 @@ class MetricsMixin:
|
||||
)
|
||||
|
||||
|
||||
class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime):
|
||||
class ModuleSystemShim:
|
||||
"""
|
||||
This shim provides the properties formerly available from ModuleSystem which are now being provided by services.
|
||||
|
||||
This shim will be removed, so all properties raise a deprecation warning.
|
||||
"""
|
||||
|
||||
@property
|
||||
def anonymous_student_id(self):
|
||||
"""
|
||||
Returns the anonymous user ID for the current user and course.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.anonymous_student_id is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._runtime_services.get('user') or self._services.get('user')
|
||||
if user_service:
|
||||
return user_service.get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
|
||||
return None
|
||||
|
||||
@property
|
||||
def seed(self):
|
||||
"""
|
||||
Returns the numeric current user id, for use as a random seed.
|
||||
Returns 0 if there is no current user.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.seed is deprecated. Please use the user service `user_id` instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self.user_id or 0
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""
|
||||
Returns the current user id, or None if there is no current user.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.user_id is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._runtime_services.get('user') or self._services.get('user')
|
||||
if user_service:
|
||||
return user_service.get_current_user().opt_attrs.get(ATTR_KEY_USER_ID)
|
||||
return None
|
||||
|
||||
@property
|
||||
def user_is_staff(self):
|
||||
"""
|
||||
Returns whether the current user has staff access to the course.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.user_is_staff is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._runtime_services.get('user') or self._services.get('user')
|
||||
if user_service:
|
||||
return user_service.get_current_user().opt_attrs.get(ATTR_KEY_USER_IS_STAFF)
|
||||
return None
|
||||
|
||||
@property
|
||||
def user_location(self):
|
||||
"""
|
||||
Returns the "country code" associated with the current user's request IP address.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.user_location is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._runtime_services.get('user') or self._services.get('user')
|
||||
if user_service:
|
||||
return user_service.get_current_user().opt_attrs.get(ATTR_KEY_REQUEST_COUNTRY_CODE)
|
||||
return None
|
||||
|
||||
@property
|
||||
def get_real_user(self):
|
||||
"""
|
||||
Returns a function that takes `anonymous_student_id` and returns the Django User object
|
||||
associated with `anonymous_student_id`.
|
||||
|
||||
If no `anonymous_student_id` is provided as an argument to this function, then the user service's anonymous user
|
||||
ID is used instead.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.get_real_user is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._runtime_services.get('user') or self._services.get('user')
|
||||
if user_service:
|
||||
return user_service.get_user_by_anonymous_id
|
||||
return None
|
||||
|
||||
@property
|
||||
def get_user_role(self):
|
||||
"""
|
||||
Returns a function that returns the user's role in the course.
|
||||
|
||||
Implementation is different for LMS and Studio.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.get_user_role is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._runtime_services.get('user') or self._services.get('user')
|
||||
if user_service:
|
||||
return partial(user_service.get_current_user().opt_attrs.get, ATTR_KEY_USER_ROLE)
|
||||
|
||||
@property
|
||||
def render_template(self):
|
||||
"""
|
||||
Returns a function that takes (template_file, context), and returns rendered html.
|
||||
|
||||
Deprecated in favor of the mako service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'Use of runtime.render_template is deprecated. '
|
||||
'Use MakoService.render_template or a JavaScript-based template instead.',
|
||||
DeprecationWarning, stacklevel=2,
|
||||
)
|
||||
if hasattr(self, '_deprecated_render_template'):
|
||||
return self._deprecated_render_template
|
||||
render_service = self._runtime_services.get('mako') or self._services.get('mako')
|
||||
if render_service:
|
||||
return render_service.render_template
|
||||
return None
|
||||
|
||||
@render_template.setter
|
||||
def render_template(self, render_template):
|
||||
"""
|
||||
Set render_template.
|
||||
|
||||
Deprecated in favor of the mako service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'Use of runtime.render_template is deprecated. '
|
||||
'Use MakoService.render_template or a JavaScript-based template instead.',
|
||||
DeprecationWarning, stacklevel=2,
|
||||
)
|
||||
self._deprecated_render_template = render_template
|
||||
|
||||
@property
|
||||
def xqueue(self):
|
||||
"""
|
||||
Returns a dict containing the XQueueInterface object, as well as parameters for the specific StudentModule:
|
||||
* interface: XQueueInterface object
|
||||
* construct_callback: function to construct the fully-qualified LMS callback URL.
|
||||
* default_queuename: default queue name for the course in XQueue
|
||||
* waittime: number of seconds to wait in between calls to XQueue
|
||||
|
||||
Deprecated in favor of the xqueue service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.xqueue is deprecated. Please use the xqueue service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
xqueue_service = self._runtime_services.get('xqueue') or self._services.get('xqueue')
|
||||
if xqueue_service:
|
||||
return {
|
||||
'interface': xqueue_service.interface,
|
||||
'construct_callback': xqueue_service.construct_callback,
|
||||
'default_queuename': xqueue_service.default_queuename,
|
||||
'waittime': xqueue_service.waittime,
|
||||
}
|
||||
return None
|
||||
|
||||
@property
|
||||
def can_execute_unsafe_code(self):
|
||||
"""
|
||||
Returns a function which returns a boolean, indicating whether or not to allow the execution of unsafe,
|
||||
unsandboxed code.
|
||||
|
||||
Deprecated in favor of the sandbox service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.can_execute_unsafe_code is deprecated. Please use the sandbox service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
sandbox_service = self._runtime_services.get('sandbox') or self._services.get('sandbox')
|
||||
if sandbox_service:
|
||||
return sandbox_service.can_execute_unsafe_code
|
||||
# Default to saying "no unsafe code".
|
||||
return lambda: False
|
||||
|
||||
@property
|
||||
def get_python_lib_zip(self):
|
||||
"""
|
||||
Returns a function returning a bytestring or None.
|
||||
|
||||
The bytestring is the contents of a zip file that should be importable by other Python code running in the
|
||||
module.
|
||||
|
||||
Deprecated in favor of the sandbox service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.get_python_lib_zip is deprecated. Please use the sandbox service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
sandbox_service = self._runtime_services.get('sandbox') or self._services.get('sandbox')
|
||||
if sandbox_service:
|
||||
return sandbox_service.get_python_lib_zip
|
||||
# Default to saying "no lib data"
|
||||
return lambda: None
|
||||
|
||||
@property
|
||||
def cache(self):
|
||||
"""
|
||||
Returns a cache object with two methods:
|
||||
* .get(key) returns an object from the cache or None.
|
||||
* .set(key, value, timeout_secs=None) stores a value in the cache with a timeout.
|
||||
|
||||
Deprecated in favor of the cache service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.cache is deprecated. Please use the cache service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self._runtime_services.get('cache') or self._services.get('cache') or DoNothingCache()
|
||||
|
||||
@property
|
||||
def replace_urls(self):
|
||||
"""
|
||||
Returns a function to replace static urls with course specific urls.
|
||||
|
||||
Deprecated in favor of the replace_urls service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.replace_urls is deprecated. Please use the replace_urls service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
replace_urls_service = self._runtime_services.get('replace_urls') or self._services.get('replace_urls')
|
||||
if replace_urls_service:
|
||||
return partial(replace_urls_service.replace_urls, static_replace_only=True)
|
||||
|
||||
@property
|
||||
def replace_course_urls(self):
|
||||
"""
|
||||
Returns a function to replace static urls with course specific urls.
|
||||
|
||||
Deprecated in favor of the replace_urls service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.replace_course_urls is deprecated. Please use the replace_urls service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
replace_urls_service = self._runtime_services.get('replace_urls') or self._services.get('replace_urls')
|
||||
if replace_urls_service:
|
||||
return partial(replace_urls_service.replace_urls)
|
||||
|
||||
@property
|
||||
def replace_jump_to_id_urls(self):
|
||||
"""
|
||||
Returns a function to replace static urls with course specific urls.
|
||||
|
||||
Deprecated in favor of the replace_urls service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.replace_jump_to_id_urls is deprecated. Please use the replace_urls service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
replace_urls_service = self._runtime_services.get('replace_urls') or self._services.get('replace_urls')
|
||||
if replace_urls_service:
|
||||
return partial(replace_urls_service.replace_urls)
|
||||
|
||||
@property
|
||||
def filestore(self):
|
||||
"""
|
||||
A filestore ojbect. Defaults to an instance of OSFS based at settings.DATA_DIR.
|
||||
|
||||
Deprecated in favor of runtime.resources_fs property.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.filestore is deprecated. Please use the runtime.resources_fs service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self.resources_fs
|
||||
|
||||
@property
|
||||
def node_path(self):
|
||||
"""
|
||||
Path to node_modules. Doesn't seem to be used by any ModuleSystem dependent core XBlock anymore.
|
||||
|
||||
Deprecated.
|
||||
"""
|
||||
warnings.warn(
|
||||
'node_path is deprecated. Please use other methods of finding the node_modules location.',
|
||||
DeprecationWarning, stacklevel=3
|
||||
)
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
"""
|
||||
Hostname of the site as set in the Django settings `LMS_BASE`
|
||||
Deprecated in favour of direct import of `django.conf.settings`
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.hostname is deprecated. Please use `LMS_BASE` from `django.conf.settings`.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return settings.LMS_BASE
|
||||
|
||||
@property
|
||||
def rebind_noauth_module_to_user(self):
|
||||
"""
|
||||
A function that was used to bind modules initialized by AnonymousUsers to real users. Mainly used
|
||||
by the LTI Block to connect the right users with the requests from LTI tools.
|
||||
|
||||
Deprecated in favour of the "rebind_user" service.
|
||||
"""
|
||||
warnings.warn(
|
||||
"rebind_noauth_module_to_user is deprecated. Please use the 'rebind_user' service instead.",
|
||||
DeprecationWarning, stacklevel=3
|
||||
)
|
||||
rebind_user_service = self._runtime_services.get('rebind_user') or self._services.get('rebind_user')
|
||||
if rebind_user_service:
|
||||
return partial(rebind_user_service.rebind_noauth_module_to_user)
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
@property
|
||||
def STATIC_URL(self): # pylint: disable=invalid-name
|
||||
"""
|
||||
Returns the base URL for static assets.
|
||||
Deprecated in favor of the settings.STATIC_URL configuration.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.STATIC_URL is deprecated. Please use settings.STATIC_URL instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return settings.STATIC_URL
|
||||
|
||||
@property
|
||||
def course_id(self):
|
||||
"""
|
||||
Old API to get the course ID.
|
||||
|
||||
Deprecated in favor of `runtime.scope_ids.usage_id.context_key`.
|
||||
"""
|
||||
warnings.warn(
|
||||
"`runtime.course_id` is deprecated. Use `context_key` instead: `runtime.scope_ids.usage_id.context_key`.",
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
if hasattr(self, '_deprecated_course_id'):
|
||||
return self._deprecated_course_id.for_branch(None)
|
||||
|
||||
@course_id.setter
|
||||
def course_id(self, course_id):
|
||||
"""
|
||||
Set course_id.
|
||||
|
||||
Deprecated in favor of `runtime.scope_ids.usage_id.context_key`.
|
||||
"""
|
||||
warnings.warn(
|
||||
"`runtime.course_id` is deprecated. Use `context_key` instead: `runtime.scope_ids.usage_id.context_key`.",
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
self._deprecated_course_id = course_id
|
||||
|
||||
|
||||
class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, Runtime):
|
||||
"""
|
||||
Base class for :class:`Runtime`s to be used with :class:`XModuleDescriptor`s
|
||||
"""
|
||||
@@ -1084,10 +1471,25 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime):
|
||||
self.get_policy = lambda u: {}
|
||||
|
||||
self.disabled_xblock_types = disabled_xblock_types
|
||||
self._runtime_services = {}
|
||||
|
||||
def get(self, attr):
|
||||
""" provide uniform access to attributes (like etree)."""
|
||||
return self.__dict__.get(attr)
|
||||
|
||||
def set(self, attr, val):
|
||||
"""provide uniform access to attributes (like etree)"""
|
||||
self.__dict__[attr] = val
|
||||
|
||||
def get_block(self, usage_id, for_parent=None):
|
||||
"""See documentation for `xblock.runtime:Runtime.get_block`"""
|
||||
return self.load_item(usage_id, for_parent=for_parent)
|
||||
block = self.load_item(usage_id, for_parent=for_parent)
|
||||
# get_block_for_descriptor property is used to bind additional data such as user data
|
||||
# to the XBlock and to check if the user has access to the block as may be required for
|
||||
# the LMS or Preview.
|
||||
if getattr(self, 'get_block_for_descriptor', None):
|
||||
return self.get_block_for_descriptor(block)
|
||||
return block
|
||||
|
||||
def load_block_type(self, block_type):
|
||||
"""
|
||||
@@ -1122,10 +1524,11 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime):
|
||||
return result
|
||||
|
||||
def handler_url(self, block, handler_name, suffix='', query='', thirdparty=False):
|
||||
# Currently, Modulestore is responsible for instantiating DescriptorSystems
|
||||
# This means that LMS/CMS don't have a way to define a subclass of DescriptorSystem
|
||||
# that implements the correct handler url. So, for now, instead, we will reference a
|
||||
# global function that the application can override.
|
||||
# When the Modulestore instantiates DescriptorSystems, we will reference a
|
||||
# global function that the application can override, unless a specific function is
|
||||
# defined for LMS/CMS through the handler_url_override property.
|
||||
if getattr(self, 'handler_url_override', None):
|
||||
return self.handler_url_override(block, handler_name, suffix, query, thirdparty)
|
||||
return descriptor_global_handler_url(block, handler_name, suffix, query, thirdparty)
|
||||
|
||||
def local_resource_url(self, block, uri):
|
||||
@@ -1142,11 +1545,12 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime):
|
||||
"""
|
||||
See :meth:`xblock.runtime.Runtime:applicable_aside_types` for documentation.
|
||||
"""
|
||||
# applicable_aside_types_override property can be used by LMS/CMS to define specific filters
|
||||
# and conditions as may be applicable.
|
||||
if getattr(self, 'applicable_aside_types_override', None):
|
||||
return self.applicable_aside_types_override(block, applicable_aside_types=super().applicable_aside_types)
|
||||
|
||||
potential_set = set(super().applicable_aside_types(block))
|
||||
if getattr(block, 'xmodule_runtime', None) is not None:
|
||||
if hasattr(block.xmodule_runtime, 'applicable_aside_types'):
|
||||
application_set = set(block.xmodule_runtime.applicable_aside_types(block))
|
||||
return list(potential_set.intersection(application_set))
|
||||
return list(potential_set)
|
||||
|
||||
def resource_url(self, resource):
|
||||
@@ -1161,8 +1565,12 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime):
|
||||
block.add_xml_to_node(child)
|
||||
|
||||
def publish(self, block, event_type, event): # lint-amnesty, pylint: disable=arguments-differ
|
||||
# A stub publish method that doesn't emit any events from XModuleDescriptors.
|
||||
pass
|
||||
"""
|
||||
Publish events through the `EventPublishingService`.
|
||||
This ensures that the correct track method is used for Instructor tasks.
|
||||
"""
|
||||
if publish_service := self._runtime_services.get('publish') or self._services.get('publish'):
|
||||
publish_service.publish(block, event_type, event)
|
||||
|
||||
def service(self, block, service_name):
|
||||
"""
|
||||
@@ -1178,14 +1586,29 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime):
|
||||
Returns:
|
||||
An object implementing the requested service, or None.
|
||||
"""
|
||||
# getting the service from parent module. making sure of block service declarations.
|
||||
service = super().service(block=block, service_name=service_name)
|
||||
declaration = block.service_declaration(service_name)
|
||||
service = self._runtime_services.get(service_name)
|
||||
if declaration is None or service is None:
|
||||
# getting the service from parent module. making sure of block service declarations.
|
||||
service = super().service(block=block, service_name=service_name)
|
||||
# Passing the block to service if it is callable e.g. XBlockI18nService. It is the responsibility of calling
|
||||
# service to handle the passing argument.
|
||||
if callable(service):
|
||||
return service(block)
|
||||
return service
|
||||
|
||||
def wrap_aside(self, block, aside, view, frag, context):
|
||||
# LMS/CMS can define custom wrap aside using wrap_asides_override as required.
|
||||
if getattr(self, 'wrap_asides_override', None):
|
||||
return self.wrap_asides_override(block, aside, view, frag, context, request_token=self.request_token)
|
||||
return super().wrap_aside(block, aside, view, frag, context)
|
||||
|
||||
def layout_asides(self, block, context, frag, view_name, aside_frag_fns):
|
||||
# LMS/CMS can define custom layout aside using layout_asides_override as required.
|
||||
if getattr(self, 'layout_asides_override', None):
|
||||
return self.layout_asides_override(block, context, frag, view_name, aside_frag_fns)
|
||||
return super().layout_asides(block, context, frag, view_name, aside_frag_fns)
|
||||
|
||||
|
||||
class XMLParsingSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring
|
||||
def __init__(self, process_xml, **kwargs):
|
||||
@@ -1303,556 +1726,8 @@ class XMLParsingSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstr
|
||||
setattr(xblock, field.name, field_value)
|
||||
|
||||
|
||||
class ModuleSystemShim:
|
||||
"""
|
||||
This shim provides the properties formerly available from ModuleSystem which are now being provided by services.
|
||||
|
||||
This shim will be removed, so all properties raise a deprecation warning.
|
||||
"""
|
||||
|
||||
@property
|
||||
def anonymous_student_id(self):
|
||||
"""
|
||||
Returns the anonymous user ID for the current user and course.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.anonymous_student_id is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._services.get('user')
|
||||
if user_service:
|
||||
return user_service.get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
|
||||
return None
|
||||
|
||||
@property
|
||||
def seed(self):
|
||||
"""
|
||||
Returns the numeric current user id, for use as a random seed.
|
||||
Returns 0 if there is no current user.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.seed is deprecated. Please use the user service `user_id` instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self.user_id or 0
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""
|
||||
Returns the current user id, or None if there is no current user.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.user_id is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._services.get('user')
|
||||
if user_service:
|
||||
return user_service.get_current_user().opt_attrs.get(ATTR_KEY_USER_ID)
|
||||
return None
|
||||
|
||||
@property
|
||||
def user_is_staff(self):
|
||||
"""
|
||||
Returns whether the current user has staff access to the course.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.user_is_staff is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._services.get('user')
|
||||
if user_service:
|
||||
return self._services['user'].get_current_user().opt_attrs.get(ATTR_KEY_USER_IS_STAFF)
|
||||
return None
|
||||
|
||||
@property
|
||||
def user_location(self):
|
||||
"""
|
||||
Returns the "country code" associated with the current user's request IP address.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.user_location is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._services.get('user')
|
||||
if user_service:
|
||||
return self._services['user'].get_current_user().opt_attrs.get(ATTR_KEY_REQUEST_COUNTRY_CODE)
|
||||
return None
|
||||
|
||||
@property
|
||||
def get_real_user(self):
|
||||
"""
|
||||
Returns a function that takes `anonymous_student_id` and returns the Django User object
|
||||
associated with `anonymous_student_id`.
|
||||
|
||||
If no `anonymous_student_id` is provided as an argument to this function, then the user service's anonymous user
|
||||
ID is used instead.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.get_real_user is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._services.get('user')
|
||||
if user_service:
|
||||
return self._services['user'].get_user_by_anonymous_id
|
||||
return None
|
||||
|
||||
@property
|
||||
def get_user_role(self):
|
||||
"""
|
||||
Returns a function that returns the user's role in the course.
|
||||
|
||||
Implementation is different for LMS and Studio.
|
||||
|
||||
Deprecated in favor of the user service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.get_user_role is deprecated. Please use the user service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
user_service = self._services.get('user')
|
||||
if user_service:
|
||||
return partial(self._services['user'].get_current_user().opt_attrs.get, ATTR_KEY_USER_ROLE)
|
||||
|
||||
@property
|
||||
def render_template(self):
|
||||
"""
|
||||
Returns a function that takes (template_file, context), and returns rendered html.
|
||||
|
||||
Deprecated in favor of the mako service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'Use of runtime.render_template is deprecated. '
|
||||
'Use MakoService.render_template or a JavaScript-based template instead.',
|
||||
DeprecationWarning, stacklevel=2,
|
||||
)
|
||||
render_service = self._services.get('mako')
|
||||
if render_service:
|
||||
return render_service.render_template
|
||||
return None
|
||||
|
||||
@property
|
||||
def xqueue(self):
|
||||
"""
|
||||
Returns a dict containing the XQueueInterface object, as well as parameters for the specific StudentModule:
|
||||
* interface: XQueueInterface object
|
||||
* construct_callback: function to construct the fully-qualified LMS callback URL.
|
||||
* default_queuename: default queue name for the course in XQueue
|
||||
* waittime: number of seconds to wait in between calls to XQueue
|
||||
|
||||
Deprecated in favor of the xqueue service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.xqueue is deprecated. Please use the xqueue service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
xqueue_service = self._services.get('xqueue')
|
||||
if xqueue_service:
|
||||
return {
|
||||
'interface': xqueue_service.interface,
|
||||
'construct_callback': xqueue_service.construct_callback,
|
||||
'default_queuename': xqueue_service.default_queuename,
|
||||
'waittime': xqueue_service.waittime,
|
||||
}
|
||||
return None
|
||||
|
||||
@property
|
||||
def can_execute_unsafe_code(self):
|
||||
"""
|
||||
Returns a function which returns a boolean, indicating whether or not to allow the execution of unsafe,
|
||||
unsandboxed code.
|
||||
|
||||
Deprecated in favor of the sandbox service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.can_execute_unsafe_code is deprecated. Please use the sandbox service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
sandbox_service = self._services.get('sandbox')
|
||||
if sandbox_service:
|
||||
return sandbox_service.can_execute_unsafe_code
|
||||
# Default to saying "no unsafe code".
|
||||
return lambda: False
|
||||
|
||||
@property
|
||||
def get_python_lib_zip(self):
|
||||
"""
|
||||
Returns a function returning a bytestring or None.
|
||||
|
||||
The bytestring is the contents of a zip file that should be importable by other Python code running in the
|
||||
module.
|
||||
|
||||
Deprecated in favor of the sandbox service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.get_python_lib_zip is deprecated. Please use the sandbox service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
sandbox_service = self._services.get('sandbox')
|
||||
if sandbox_service:
|
||||
return sandbox_service.get_python_lib_zip
|
||||
# Default to saying "no lib data"
|
||||
return lambda: None
|
||||
|
||||
@property
|
||||
def cache(self):
|
||||
"""
|
||||
Returns a cache object with two methods:
|
||||
* .get(key) returns an object from the cache or None.
|
||||
* .set(key, value, timeout_secs=None) stores a value in the cache with a timeout.
|
||||
|
||||
Deprecated in favor of the cache service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.cache is deprecated. Please use the cache service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self._services.get('cache') or DoNothingCache()
|
||||
|
||||
@property
|
||||
def replace_urls(self):
|
||||
"""
|
||||
Returns a function to replace static urls with course specific urls.
|
||||
|
||||
Deprecated in favor of the replace_urls service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.replace_urls is deprecated. Please use the replace_urls service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
replace_urls_service = self._services.get('replace_urls')
|
||||
if replace_urls_service:
|
||||
return partial(replace_urls_service.replace_urls, static_replace_only=True)
|
||||
|
||||
@property
|
||||
def replace_course_urls(self):
|
||||
"""
|
||||
Returns a function to replace static urls with course specific urls.
|
||||
|
||||
Deprecated in favor of the replace_urls service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.replace_course_urls is deprecated. Please use the replace_urls service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
replace_urls_service = self._services.get('replace_urls')
|
||||
if replace_urls_service:
|
||||
return partial(replace_urls_service.replace_urls)
|
||||
|
||||
@property
|
||||
def replace_jump_to_id_urls(self):
|
||||
"""
|
||||
Returns a function to replace static urls with course specific urls.
|
||||
|
||||
Deprecated in favor of the replace_urls service.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.replace_jump_to_id_urls is deprecated. Please use the replace_urls service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
replace_urls_service = self._services.get('replace_urls')
|
||||
if replace_urls_service:
|
||||
return partial(replace_urls_service.replace_urls)
|
||||
|
||||
@property
|
||||
def filestore(self):
|
||||
"""
|
||||
A filestore ojbect. Defaults to an instance of OSFS based at settings.DATA_DIR.
|
||||
|
||||
Deprecated in favor of runtime.resources_fs property.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.filestore is deprecated. Please use the runtime.resources_fs service instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self.resources_fs
|
||||
|
||||
@property
|
||||
def node_path(self):
|
||||
"""
|
||||
Path to node_modules. Doesn't seem to be used by any ModuleSystem dependent core XBlock anymore.
|
||||
|
||||
Deprecated.
|
||||
"""
|
||||
warnings.warn(
|
||||
'node_path is deprecated. Please use other methods of finding the node_modules location.',
|
||||
DeprecationWarning, stacklevel=3
|
||||
)
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
"""
|
||||
Hostname of the site as set in the Django settings `LMS_BASE`
|
||||
Deprecated in favour of direct import of `django.conf.settings`
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.hostname is deprecated. Please use `LMS_BASE` from `django.conf.settings`.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return settings.LMS_BASE
|
||||
|
||||
@property
|
||||
def rebind_noauth_module_to_user(self):
|
||||
"""
|
||||
A function that was used to bind modules initialized by AnonymousUsers to real users. Mainly used
|
||||
by the LTI Block to connect the right users with the requests from LTI tools.
|
||||
|
||||
Deprecated in favour of the "rebind_user" service.
|
||||
"""
|
||||
warnings.warn(
|
||||
"rebind_noauth_module_to_user is deprecated. Please use the 'rebind_user' service instead.",
|
||||
DeprecationWarning, stacklevel=3
|
||||
)
|
||||
rebind_user_service = self._services.get('rebind_user')
|
||||
if rebind_user_service:
|
||||
return partial(rebind_user_service.rebind_noauth_module_to_user)
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
@property
|
||||
def STATIC_URL(self): # pylint: disable=invalid-name
|
||||
"""
|
||||
Returns the base URL for static assets.
|
||||
Deprecated in favor of the settings.STATIC_URL configuration.
|
||||
"""
|
||||
warnings.warn(
|
||||
'runtime.STATIC_URL is deprecated. Please use settings.STATIC_URL instead.',
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return settings.STATIC_URL
|
||||
|
||||
@property
|
||||
def course_id(self):
|
||||
"""
|
||||
Old API to get the course ID.
|
||||
|
||||
Deprecated in favor of `runtime.scope_ids.usage_id.context_key`.
|
||||
"""
|
||||
warnings.warn(
|
||||
"`runtime.course_id` is deprecated. Use `context_key` instead: `runtime.scope_ids.usage_id.context_key`.",
|
||||
DeprecationWarning, stacklevel=3,
|
||||
)
|
||||
return self.descriptor_runtime.course_id.for_branch(None)
|
||||
|
||||
|
||||
class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, Runtime):
|
||||
"""
|
||||
This is an abstraction such that x_modules can function independent
|
||||
of the courseware (e.g. import into other types of courseware, LMS,
|
||||
or if we want to have a sandbox server for user-contributed content)
|
||||
|
||||
ModuleSystem objects are passed to x_modules to provide access to system
|
||||
functionality.
|
||||
|
||||
Note that these functions can be closures over e.g. a django request
|
||||
and user, or other environment-specific info.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
get_block,
|
||||
descriptor_runtime,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Create a closure around the system environment.
|
||||
|
||||
get_block - function that takes a descriptor and returns a corresponding
|
||||
block instance object. If the current user does not have
|
||||
access to that location, returns None.
|
||||
|
||||
descriptor_runtime - A `DescriptorSystem` to use for loading xblocks by id
|
||||
"""
|
||||
|
||||
kwargs.setdefault('id_reader', getattr(descriptor_runtime, 'id_reader', OpaqueKeyReader()))
|
||||
kwargs.setdefault('id_generator', getattr(descriptor_runtime, 'id_generator', AsideKeyGenerator()))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.get_block_for_descriptor = get_block
|
||||
|
||||
self.xmodule_instance = None
|
||||
|
||||
self.descriptor_runtime = descriptor_runtime
|
||||
|
||||
def get(self, attr):
|
||||
""" provide uniform access to attributes (like etree)."""
|
||||
return self.__dict__.get(attr)
|
||||
|
||||
def set(self, attr, val):
|
||||
"""provide uniform access to attributes (like etree)"""
|
||||
self.__dict__[attr] = val
|
||||
|
||||
def __repr__(self):
|
||||
kwargs = self.__dict__.copy()
|
||||
|
||||
# Remove value set transiently by XBlock
|
||||
kwargs.pop('_view_name')
|
||||
|
||||
return f"{self.__class__.__name__}{kwargs}"
|
||||
|
||||
@property
|
||||
def ajax_url(self):
|
||||
"""
|
||||
The url prefix to be used by XModules to call into handle_ajax
|
||||
"""
|
||||
assert self.xmodule_instance is not None
|
||||
return self.handler_url(self.xmodule_instance, 'xmodule_handler', '', '').rstrip('/?')
|
||||
|
||||
def get_block(self, block_id, for_parent=None): # lint-amnesty, pylint: disable=arguments-differ
|
||||
return self.get_block_for_descriptor(self.descriptor_runtime.get_block(block_id, for_parent=for_parent))
|
||||
|
||||
def resource_url(self, resource):
|
||||
raise NotImplementedError("edX Platform doesn't currently implement XBlock resource urls")
|
||||
|
||||
def publish(self, block, event_type, event): # lint-amnesty, pylint: disable=arguments-differ
|
||||
"""
|
||||
Publish events through the `EventPublishingService`.
|
||||
This ensures that the correct track method is used for Instructor tasks.
|
||||
"""
|
||||
if publish_service := self._services.get('publish'):
|
||||
publish_service.publish(block, event_type, event)
|
||||
|
||||
def service(self, block, service_name):
|
||||
"""
|
||||
Runtime-specific override for the XBlock service manager. If a service is not currently
|
||||
instantiated and is declared as a critical requirement, an attempt is made to load the
|
||||
module.
|
||||
|
||||
Arguments:
|
||||
block (an XBlock): this block's class will be examined for service
|
||||
decorators.
|
||||
service_name (string): the name of the service requested.
|
||||
|
||||
Returns:
|
||||
An object implementing the requested service, or None.
|
||||
"""
|
||||
# getting the service from parent module. making sure of block service declarations.
|
||||
service = super().service(block=block, service_name=service_name)
|
||||
# Passing the block to service if it is callable e.g. XBlockI18nService. It is the responsibility of calling
|
||||
# service to handle the passing argument.
|
||||
if callable(service):
|
||||
return service(block)
|
||||
return service
|
||||
|
||||
|
||||
class CombinedSystem:
|
||||
"""
|
||||
This class is a shim to allow both pure XBlocks and XModuleDescriptors
|
||||
that have been bound as XModules to access both the attributes of ModuleSystem
|
||||
and of DescriptorSystem as a single runtime.
|
||||
"""
|
||||
|
||||
__slots__ = ('_module_system', '_descriptor_system')
|
||||
|
||||
# This system doesn't override a number of methods that are provided by ModuleSystem and DescriptorSystem,
|
||||
# namely handler_url, local_resource_url, query, and resource_url.
|
||||
#
|
||||
# At runtime, the ModuleSystem and/or DescriptorSystem will define those methods
|
||||
#
|
||||
def __init__(self, module_system, descriptor_system):
|
||||
# These attributes are set directly to __dict__ below to avoid a recursion in getattr/setattr.
|
||||
self._module_system = module_system
|
||||
self._descriptor_system = descriptor_system
|
||||
|
||||
def render(self, block, view_name, context=None):
|
||||
"""
|
||||
Render a block by invoking its view.
|
||||
|
||||
Finds the view named `view_name` on `block`. The default view will be
|
||||
used if a specific view hasn't be registered. If there is no default
|
||||
view, an exception will be raised.
|
||||
|
||||
The view is invoked, passing it `context`. The value returned by the
|
||||
view is returned, with possible modifications by the runtime to
|
||||
integrate it into a larger whole.
|
||||
|
||||
"""
|
||||
context = context or {}
|
||||
return self.__getattr__('render')(block, view_name, context) # pylint: disable=unnecessary-dunder-call
|
||||
|
||||
def service(self, block, service_name):
|
||||
"""Return a service, or None.
|
||||
|
||||
Services are objects implementing arbitrary other interfaces. They are
|
||||
requested by agreed-upon names, see [XXX TODO] for a list of possible
|
||||
services. The object returned depends on the service requested.
|
||||
|
||||
XBlocks must announce their intention to request services with the
|
||||
`XBlock.needs` or `XBlock.wants` decorators. Use `needs` if you assume
|
||||
that the service is available, or `wants` if your code is flexible and
|
||||
can accept a None from this method.
|
||||
|
||||
Runtimes can override this method if they have different techniques for
|
||||
finding and delivering services.
|
||||
|
||||
Arguments:
|
||||
block (an XBlock): this block's class will be examined for service
|
||||
decorators.
|
||||
service_name (string): the name of the service requested.
|
||||
|
||||
Returns:
|
||||
An object implementing the requested service, or None.
|
||||
|
||||
"""
|
||||
service = None
|
||||
|
||||
if self._module_system:
|
||||
service = self._module_system.service(block, service_name)
|
||||
|
||||
if service is None:
|
||||
service = self._descriptor_system.service(block, service_name)
|
||||
|
||||
return service
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
If the ModuleSystem doesn't have an attribute, try returning the same attribute from the
|
||||
DescriptorSystem, instead. This allows XModuleDescriptors that are bound as XModules
|
||||
to still function as XModuleDescriptors.
|
||||
"""
|
||||
# First we try a lookup in the module system...
|
||||
try:
|
||||
return getattr(self._module_system, name)
|
||||
except AttributeError:
|
||||
return getattr(self._descriptor_system, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""
|
||||
If the ModuleSystem is set, set the attr on it.
|
||||
Always set the attr on the DescriptorSystem.
|
||||
"""
|
||||
if name in self.__slots__:
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
if self._module_system:
|
||||
setattr(self._module_system, name, value)
|
||||
setattr(self._descriptor_system, name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
"""
|
||||
If the ModuleSystem is set, delete the attribute from it.
|
||||
Always delete the attribute from the DescriptorSystem.
|
||||
"""
|
||||
if self._module_system:
|
||||
delattr(self._module_system, name)
|
||||
delattr(self._descriptor_system, name)
|
||||
|
||||
def __repr__(self):
|
||||
return f"CombinedSystem({self._module_system!r}, {self._descriptor_system!r})"
|
||||
|
||||
|
||||
class DoNothingCache:
|
||||
"""A duck-compatible object to use in ModuleSystem when there's no cache."""
|
||||
"""A duck-compatible object to use in ModuleSystemShim when there's no cache."""
|
||||
def get(self, _key):
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user