diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py
index bbffe1ff8196..4100c2dd47a0 100644
--- a/cms/djangoapps/contentstore/views/preview.py
+++ b/cms/djangoapps/contentstore/views/preview.py
@@ -211,7 +211,6 @@ def _preview_module_system(request, descriptor, field_data):
track_function=lambda event_type, event: None,
get_module=partial(_load_preview_module, request),
mixins=settings.XBLOCK_MIXINS,
- course_id=course_id,
# Set up functions to modify the fragment produced by student_view
wrappers=wrappers,
diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py
index 66e934911f06..a46f35cef5f3 100644
--- a/cms/djangoapps/contentstore/views/tests/test_preview.py
+++ b/cms/djangoapps/contentstore/views/tests/test_preview.py
@@ -296,7 +296,7 @@ def test_cache(self):
def test_replace_urls(self):
html = ''
assert self.runtime.replace_urls(html) == \
- static_replace.replace_static_urls(html, course_id=self.runtime.course_id)
+ 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'
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index bd64896cb792..04015787c6f1 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -7,10 +7,12 @@
import logging
import textwrap
from collections import OrderedDict
+
from functools import partial
from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH
from completion.models import BlockCompletion
+from completion.services import CompletionService
from django.conf import settings
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.core.cache import cache
@@ -22,7 +24,7 @@
from django.utils.text import slugify
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
-from edx_django_utils.cache import RequestCache
+from edx_django_utils.cache import DEFAULT_REQUEST_CACHE, RequestCache
from edx_django_utils.monitoring import set_custom_attributes_for_course_key, set_monitoring_transaction_name
from edx_proctoring.api import get_attempt_status_summary
from edx_proctoring.services import ProctoringService
@@ -39,12 +41,18 @@
from xblock.reference.plugins import FSService
from xblock.runtime import KvsFieldData
+from lms.djangoapps.badges.service import BadgingService
+from lms.djangoapps.badges.utils import badges_enabled
+from lms.djangoapps.teams.services import TeamsService
+from openedx.core.lib.xblock_services.call_to_action import CallToActionService
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError, ProcessingError
-from xmodule.modulestore.django import modulestore
+from xmodule.library_tools import LibraryToolsService
+from xmodule.modulestore.django import ModuleI18nService, modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
+from xmodule.partitions.partitions_service import PartitionService
from xmodule.util.sandboxing import SandboxService
-from xmodule.services import RebindUserService
+from xmodule.services import RebindUserService, SettingsService, TeamsConfigurationService
from common.djangoapps.static_replace.services import ReplaceURLService
from common.djangoapps.static_replace.wrapper import replace_urls_wrapper
from common.djangoapps.xblock_django.constants import ATTR_KEY_USER_ID
@@ -63,7 +71,7 @@
from lms.djangoapps.grades.api import GradesUtilService
from lms.djangoapps.grades.api import signals as grades_signals
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
-from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem
+from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem, UserTagsService
from lms.djangoapps.verify_student.services import XBlockVerificationService
from openedx.core.djangoapps.bookmarks.services import BookmarksService
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
@@ -678,12 +686,12 @@ def handle_deprecated_progress_event(block, event):
field_data = DateLookupFieldData(descriptor._field_data, course_id, user) # pylint: disable=protected-access
field_data = LmsFieldData(field_data, student_data)
+ store = modulestore()
+
system = LmsModuleSystem(
track_function=track_function,
get_module=inner_get_module,
- user=user,
publish=publish,
- course_id=course_id,
# 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,
@@ -706,6 +714,18 @@ def handle_deprecated_progress_event(block, event):
'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': ModuleI18nService,
+ '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(),
},
descriptor_runtime=descriptor._runtime, # pylint: disable=protected-access
request_token=request_token,
diff --git a/lms/djangoapps/courseware/student_field_overrides.py b/lms/djangoapps/courseware/student_field_overrides.py
index 4eceebf9fc77..2d5ad21fb985 100644
--- a/lms/djangoapps/courseware/student_field_overrides.py
+++ b/lms/djangoapps/courseware/student_field_overrides.py
@@ -57,7 +57,7 @@ def _get_overrides_for_user(user, block):
location = block.location
query = StudentFieldOverride.objects.filter(
- course_id=block.runtime.course_id,
+ course_id=block.scope_ids.usage_id.context_key,
location=location,
student_id=user.id,
)
@@ -76,7 +76,7 @@ def override_field_for_user(user, block, name, value):
value to set for the given field.
"""
override, _ = StudentFieldOverride.objects.get_or_create(
- course_id=block.runtime.course_id,
+ course_id=block.scope_ids.usage_id.context_key,
location=block.location,
student_id=user.id,
field=name)
@@ -94,7 +94,7 @@ def clear_override_for_user(user, block, name):
"""
try:
StudentFieldOverride.objects.get(
- course_id=block.runtime.course_id,
+ course_id=block.scope_ids.usage_id.context_key,
student_id=user.id,
location=block.location,
field=name).delete()
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index 15b33844790d..9e187fd1ebe2 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -34,6 +34,7 @@
from web_fragments.fragment import Fragment # lint-amnesty, pylint: disable=wrong-import-order
from xblock.completable import CompletableXBlockMixin # lint-amnesty, pylint: disable=wrong-import-order
from xblock.core import XBlock, XBlockAside # lint-amnesty, pylint: disable=wrong-import-order
+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
@@ -46,7 +47,7 @@
from xmodule.html_module import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock
from xmodule.lti_module import LTIBlock
from xmodule.modulestore import ModuleStoreEnum
-from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.django import ModuleI18nService, modulestore
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_MONGO_AMNESTY_MODULESTORE,
ModuleStoreTestCase,
@@ -64,6 +65,8 @@
from common.djangoapps.student.tests.factories import RequestFactoryNoCsrf
from common.djangoapps.student.tests.factories import UserFactory
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
+from lms.djangoapps.badges.tests.factories import BadgeClassFactory
+from lms.djangoapps.badges.tests.test_models import get_image
from lms.djangoapps.courseware import module_render as render
from lms.djangoapps.courseware.access_response import AccessResponse
from lms.djangoapps.courseware.courses import get_course_info_section, get_course_with_access
@@ -91,11 +94,34 @@
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
-@XBlock.needs("field-data")
-@XBlock.needs("i18n")
-@XBlock.needs("fs")
-@XBlock.needs("user")
-@XBlock.needs("bookmarks")
+@XBlock.needs('fs')
+@XBlock.needs('field-data')
+@XBlock.needs('mako')
+@XBlock.needs('user')
+@XBlock.needs('verification')
+@XBlock.needs('proctoring')
+@XBlock.needs('milestones')
+@XBlock.needs('credit')
+@XBlock.needs('bookmarks')
+@XBlock.needs('gating')
+@XBlock.needs('grade_utils')
+@XBlock.needs('user_state')
+@XBlock.needs('content_type_gating')
+@XBlock.needs('cache')
+@XBlock.needs('sandbox')
+@XBlock.needs('xqueue')
+@XBlock.needs('replace_urls')
+@XBlock.needs('rebind_user')
+@XBlock.needs('completion')
+@XBlock.needs('i18n')
+@XBlock.needs('library_tools')
+@XBlock.needs('partitions')
+@XBlock.needs('settings')
+@XBlock.needs('user_tags')
+@XBlock.needs('badging')
+@XBlock.needs('teams')
+@XBlock.needs('teams_configuration')
+@XBlock.needs('call_to_action')
class PureXBlock(XBlock):
"""
Pure XBlock to use in tests.
@@ -2232,12 +2258,25 @@ def test_event_publishing(self, mock_track_function):
mock_track_function.return_value.assert_called_once_with(event_type, event)
-@ddt.ddt
-class LMSXBlockServiceBindingTest(SharedModuleStoreTestCase):
+class LMSXBlockServiceMixin(SharedModuleStoreTestCase):
"""
- Tests that the LMS Module System (XBlock Runtime) provides an expected set of services.
+ Helper class that initializes the LmsModuleSystem.
"""
+ def _prepare_runtime(self):
+ """
+ Instantiate the LmsModuleSystem.
+ """
+ self.runtime, _ = render.get_module_system_for_user(
+ self.user,
+ self.student_data,
+ self.descriptor,
+ self.course.id,
+ self.track_function,
+ self.request_token,
+ course=self.course
+ )
+ @XBlock.register_temp_plugin(PureXBlock, identifier='pure')
def setUp(self):
"""
Set up the user and other fields that will be used to instantiate the runtime.
@@ -2248,46 +2287,168 @@ def setUp(self):
self.student_data = Mock()
self.track_function = Mock()
self.request_token = Mock()
+ self.descriptor = ItemFactory(category="pure", parent=self.course)
+ self._prepare_runtime()
- @XBlock.register_temp_plugin(PureXBlock, identifier='pure')
- @ddt.data("user", "i18n", "fs", "field-data", "bookmarks")
+
+@ddt.ddt
+class LMSXBlockServiceBindingTest(LMSXBlockServiceMixin):
+ """
+ Tests that the LMS Module System (XBlock Runtime) provides an expected set of services.
+ """
+
+ @ddt.data(
+ 'fs',
+ 'field-data',
+ 'mako',
+ 'user',
+ 'verification',
+ 'proctoring',
+ 'milestones',
+ 'credit',
+ 'bookmarks',
+ 'gating',
+ 'grade_utils',
+ 'user_state',
+ 'content_type_gating',
+ 'cache',
+ 'sandbox',
+ 'xqueue',
+ 'replace_urls',
+ 'rebind_user',
+ 'completion',
+ 'i18n',
+ 'library_tools',
+ 'partitions',
+ 'settings',
+ 'user_tags',
+ 'teams',
+ 'teams_configuration',
+ 'call_to_action',
+ )
def test_expected_services_exist(self, expected_service):
"""
Tests that the 'user', 'i18n', and 'fs' services are provided by the LMS runtime.
"""
- descriptor = ItemFactory(category="pure", parent=self.course)
- runtime, _ = render.get_module_system_for_user(
- self.user,
- self.student_data,
- descriptor,
- self.course.id,
- self.track_function,
- self.request_token,
- course=self.course
- )
- service = runtime.service(descriptor, expected_service)
+ service = self.runtime.service(self.descriptor, expected_service)
assert service is not None
- @XBlock.register_temp_plugin(PureXBlock, identifier='pure')
def test_beta_tester_fields_added(self):
"""
Tests that the beta tester fields are set on LMS runtime.
"""
- descriptor = ItemFactory(category="pure", parent=self.course)
- descriptor.days_early_for_beta = 5
- runtime, _ = render.get_module_system_for_user(
- self.user,
- self.student_data,
- descriptor,
- self.course.id,
- self.track_function,
- self.request_token,
- course=self.course
- )
+ self.descriptor.days_early_for_beta = 5
+ self._prepare_runtime()
# pylint: disable=no-member
- assert not runtime.user_is_beta_tester
- assert runtime.days_early_for_beta == 5
+ assert not self.runtime.user_is_beta_tester
+ assert self.runtime.days_early_for_beta == 5
+
+ def test_get_set_tag(self):
+ """
+ Tests the user service interface.
+ """
+ scope = 'course'
+ key = 'key1'
+
+ # test for when we haven't set the tag yet
+ tag = self.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)
+
+ 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)
+
+ # Try to get tag in wrong scope
+ with pytest.raises(ValueError):
+ self.runtime.service(self.descriptor, 'user_tags').get_tag('fake_scope', key)
+
+
+@ddt.ddt
+class TestBadgingService(LMSXBlockServiceMixin):
+ """Test the badging service interface"""
+
+ @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
+ def test_service_rendered(self):
+ self._prepare_runtime()
+ assert self.runtime.service(self.descriptor, 'badging')
+
+ def test_no_service_rendered(self):
+ with pytest.raises(NoSuchServiceError):
+ self.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
+
+ @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
+ def test_get_badge_class(self):
+ self._prepare_runtime()
+ badge_service = self.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.
+ with get_image('good') as image_handle:
+ badge_class = badge_service.get_badge_class(
+ slug='test_slug', issuing_component='test_component', description='Attempted override',
+ criteria='test', display_name='Testola', image_file_handle=image_handle
+ )
+ # These defaults are set on the factory.
+ assert badge_class.criteria == 'https://example.com/syllabus'
+ assert badge_class.display_name == 'Test Badge'
+ assert badge_class.description == "Yay! It's a test badge."
+ # File name won't always be the same.
+ assert badge_class.image.path == premade_badge_class.image.path
+
+
+class TestI18nService(LMSXBlockServiceMixin):
+ """ Test ModuleI18nService """
+
+ def test_module_i18n_lms_service(self):
+ """
+ Test: module i18n service in LMS
+ """
+ i18n_service = self.runtime.service(self.descriptor, 'i18n')
+ assert i18n_service is not None
+ assert isinstance(i18n_service, ModuleI18nService)
+
+ def test_no_service_exception_with_none_declaration_(self):
+ """
+ Test: NoSuchServiceError should be raised block declaration returns none
+ """
+ self.descriptor.service_declaration = Mock(return_value=None)
+ with pytest.raises(NoSuchServiceError):
+ self.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
+ with pytest.raises(NoSuchServiceError):
+ self.runtime.service(self.descriptor, 'i18n')
+
+ 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
+
+ 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'))
class PureXBlockWithChildren(PureXBlock):
@@ -2706,15 +2867,22 @@ def test_cache(self):
def test_replace_urls(self):
html = ''
assert self.runtime.replace_urls(html) == \
- static_replace.replace_static_urls(html, course_id=self.runtime.course_id)
+ static_replace.replace_static_urls(html, course_id=self.course.id)
def test_replace_course_urls(self):
html = ''
assert self.runtime.replace_course_urls(html) == \
- static_replace.replace_course_urls(html, course_key=self.runtime.course_id)
+ static_replace.replace_course_urls(html, course_key=self.course.id)
def test_replace_jump_to_id_urls(self):
html = ''
- jump_to_id_base_url = reverse('jump_to_id', kwargs={'course_id': str(self.runtime.course_id), 'module_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) == \
- static_replace.replace_jump_to_id_urls(html, self.runtime.course_id, jump_to_id_base_url)
+ static_replace.replace_jump_to_id_urls(html, self.course.id, jump_to_id_base_url)
+
+ @XBlock.register_temp_plugin(PureXBlockWithChildren, identifier='xblock')
+ def test_course_id(self):
+ descriptor = ItemFactory(category="pure", parent=self.course)
+
+ block = render.get_module(self.user, Mock(), descriptor.location, None)
+ assert str(block.runtime.course_id) == self.COURSE_ID
diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py
index c84bf675b794..bb4feb852d9e 100644
--- a/lms/djangoapps/courseware/tests/test_video_mongo.py
+++ b/lms/djangoapps/courseware/tests/test_video_mongo.py
@@ -1424,7 +1424,6 @@ def setUp(self):
self.initialize_block(data=sample_xml)
self.video = self.item_descriptor
self.video.runtime.handler_url = Mock(return_value=self.transcript_url)
- self.video.runtime.course_id = MagicMock()
def setup_val_video(self, associate_course_in_val=False):
"""
@@ -1527,7 +1526,6 @@ def test_no_edx_video_id_and_no_fallback(self):
self.initialize_block(data=sample_xml)
self.video = self.item_descriptor
self.video.runtime.handler_url = Mock(return_value=self.transcript_url)
- self.video.runtime.course_id = MagicMock()
result = self.get_result()
self.verify_result_with_youtube_url(result)
@@ -1595,7 +1593,6 @@ class VideoBlockTest(TestCase, VideoBlockTestBase):
def setUp(self):
super().setUp()
self.descriptor.runtime.handler_url = MagicMock()
- self.descriptor.runtime.course_id = MagicMock()
self.temp_dir = mkdtemp()
file_system = OSFS(self.temp_dir)
self.file_system = file_system.makedir(EXPORT_IMPORT_COURSE_DIR, recreate=True)
diff --git a/lms/djangoapps/edxnotes/decorators.py b/lms/djangoapps/edxnotes/decorators.py
index c641bd230c66..247bb6b913f4 100644
--- a/lms/djangoapps/edxnotes/decorators.py
+++ b/lms/djangoapps/edxnotes/decorators.py
@@ -33,8 +33,8 @@ def get_html(self, *args, **kwargs):
if not hasattr(runtime, 'modulestore'):
return original_get_html(self, *args, **kwargs)
- is_studio = getattr(self.system, "is_author_mode", False)
- course = getattr(self, 'descriptor', self).runtime.modulestore.get_course(self.runtime.course_id)
+ is_studio = getattr(self.runtime, "is_author_mode", False)
+ course = getattr(self, 'descriptor', self).runtime.modulestore.get_course(self.scope_ids.usage_id.context_key)
# Must be disabled when:
# - in Studio
@@ -57,10 +57,10 @@ def get_html(self, *args, **kwargs):
),
"params": {
# Use camelCase to name keys.
- "usageId": str(self.scope_ids.usage_id),
- "courseId": str(self.runtime.course_id),
+ "usageId": self.scope_ids.usage_id,
+ "courseId": course.id,
"token": get_edxnotes_id_token(user),
- "tokenUrl": get_token_url(self.runtime.course_id),
+ "tokenUrl": get_token_url(course.id),
"endpoint": get_public_endpoint(),
"debug": settings.DEBUG,
"eventStringLimit": settings.TRACK_MAX_EVENT / 6,
diff --git a/lms/djangoapps/edxnotes/tests.py b/lms/djangoapps/edxnotes/tests.py
index a67ba7e0fa2e..b89b4a0afe13 100644
--- a/lms/djangoapps/edxnotes/tests.py
+++ b/lms/djangoapps/edxnotes/tests.py
@@ -79,11 +79,10 @@ class TestProblem:
The purpose of this class is to imitate any problem.
"""
def __init__(self, course, user=None):
- self.system = MagicMock(is_author_mode=False)
- self.scope_ids = MagicMock(usage_id="test_usage_id")
+ self.scope_ids = MagicMock(usage_id=course.id.make_usage_key('test_problem', 'test_usage_id'))
user = user or UserFactory()
user_service = StubUserService(user)
- self.runtime = MagicMock(course_id=course.id, service=lambda _a, _b: user_service)
+ self.runtime = MagicMock(service=lambda _a, _b: user_service, is_author_mode=False)
self.descriptor = MagicMock()
self.descriptor.runtime.modulestore.get_course.return_value = course
@@ -136,7 +135,7 @@ def test_edxnotes_enabled(self, mock_generate_uid, mock_get_id_token, mock_get_t
"uid": "uid",
"edxnotes_visibility": "true",
"params": {
- "usageId": "test_usage_id",
+ "usageId": problem.scope_ids.usage_id,
"courseId": course.id,
"token": "token",
"tokenUrl": "/tokenUrl",
@@ -167,7 +166,7 @@ def test_edxnotes_studio(self):
"""
Tests that get_html is not wrapped when problem is rendered in Studio.
"""
- self.problem.system.is_author_mode = True
+ self.problem.runtime.is_author_mode = True
assert 'original_get_html' == self.problem.get_html()
def test_edxnotes_blockstore_runtime(self):
diff --git a/lms/djangoapps/instructor/tasks.py b/lms/djangoapps/instructor/tasks.py
index eedfe07fee1d..b28f5387f87c 100644
--- a/lms/djangoapps/instructor/tasks.py
+++ b/lms/djangoapps/instructor/tasks.py
@@ -69,10 +69,10 @@ def update_exam_completion_task(user_identifier: str, content_id: str, completio
# Now evil modulestore magic to inflate our descriptor with user state and
# permissions checks.
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
- root_descriptor.course_id, user, root_descriptor, read_only=True,
+ root_descriptor.scope_ids.usage_id.context_key, user, root_descriptor, read_only=True,
)
root_module = get_module_for_descriptor(
- user, request, root_descriptor, field_data_cache, root_descriptor.course_id,
+ user, request, root_descriptor, field_data_cache, root_descriptor.scope_ids.usage_id.context_key,
)
if not root_module:
err_msg = err_msg_prefix + 'Module unable to be created from descriptor!'
diff --git a/lms/djangoapps/lms_xblock/runtime.py b/lms/djangoapps/lms_xblock/runtime.py
index 859d01c1568e..f79f75da0879 100644
--- a/lms/djangoapps/lms_xblock/runtime.py
+++ b/lms/djangoapps/lms_xblock/runtime.py
@@ -2,25 +2,13 @@
Module implementing `xblock.runtime.Runtime` functionality for the LMS
"""
-
-import xblock.reference.plugins
-from completion.services import CompletionService
from django.conf import settings
from django.urls import reverse
-from edx_django_utils.cache import DEFAULT_REQUEST_CACHE
-from lms.djangoapps.badges.service import BadgingService
-from lms.djangoapps.badges.utils import badges_enabled
from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
-from lms.djangoapps.teams.services import TeamsService
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_services.call_to_action import CallToActionService
from openedx.core.lib.xblock_utils import wrap_xblock_aside, xblock_local_resource_url
-from xmodule.library_tools import LibraryToolsService # lint-amnesty, pylint: disable=wrong-import-order
-from xmodule.modulestore.django import ModuleI18nService, modulestore # lint-amnesty, pylint: disable=wrong-import-order
-from xmodule.partitions.partitions_service import PartitionService # lint-amnesty, pylint: disable=wrong-import-order
-from xmodule.services import SettingsService, TeamsConfigurationService # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.x_module import ModuleSystem # lint-amnesty, pylint: disable=wrong-import-order
@@ -51,7 +39,7 @@ def handler_url(block, handler_name, suffix='', query='', thirdparty=False):
view_name = 'xblock_handler_noauth'
url = reverse(view_name, kwargs={
- 'course_id': str(block.location.course_key),
+ 'course_id': str(block.scope_ids.usage_id.context_key),
'usage_id': quote_slashes(str(block.scope_ids.usage_id)),
'handler': handler_name,
'suffix': suffix,
@@ -132,32 +120,8 @@ class LmsModuleSystem(ModuleSystem): # pylint: disable=abstract-method
"""
ModuleSystem specialized to the LMS
"""
- def __init__(self, user, **kwargs):
- request_cache_dict = DEFAULT_REQUEST_CACHE.data
- store = modulestore()
- course_id = kwargs.get('course_id')
-
- services = kwargs.setdefault('services', {})
- if user and user.is_authenticated:
- services['completion'] = CompletionService(user=user, context_key=course_id)
- services['fs'] = xblock.reference.plugins.FSService()
- services['i18n'] = ModuleI18nService
- services['library_tools'] = LibraryToolsService(store, user_id=user.id if user else None)
- services['partitions'] = PartitionService(
- course_id=course_id,
- cache=request_cache_dict
- )
- services['settings'] = SettingsService()
- services['user_tags'] = UserTagsService(
- user=user,
- course_id=course_id,
- )
- if badges_enabled():
- services['badging'] = BadgingService(course_id=course_id, modulestore=store)
+ def __init__(self, **kwargs):
self.request_token = kwargs.pop('request_token', None)
- services['teams'] = TeamsService()
- services['teams_configuration'] = TeamsConfigurationService()
- services['call_to_action'] = CallToActionService()
super().__init__(**kwargs)
def handler_url(self, *args, **kwargs): # lint-amnesty, pylint: disable=signature-differs
diff --git a/lms/djangoapps/lms_xblock/test/test_runtime.py b/lms/djangoapps/lms_xblock/test/test_runtime.py
index f90722a746ec..08a7ff47a02c 100644
--- a/lms/djangoapps/lms_xblock/test/test_runtime.py
+++ b/lms/djangoapps/lms_xblock/test/test_runtime.py
@@ -3,29 +3,27 @@
"""
-from unittest.mock import Mock, patch
+from unittest.mock import Mock
from urllib.parse import urlparse
-import pytest
-from ddt import data, ddt
from django.conf import settings
from django.test import TestCase
-from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import BlockUsageLocator, CourseLocator
-from xblock.exceptions import NoSuchServiceError
from xblock.fields import ScopeIds
-from common.djangoapps.student.tests.factories import UserFactory
-from lms.djangoapps.badges.tests.factories import BadgeClassFactory
-from lms.djangoapps.badges.tests.test_models import get_image
from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem
-from xmodule.modulestore.django import ModuleI18nService # lint-amnesty, pylint: disable=wrong-import-order
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
-from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
class BlockMock(Mock):
"""Mock class that we fill with our "handler" methods."""
+ scope_ids = ScopeIds(
+ None,
+ None,
+ None,
+ BlockUsageLocator(
+ CourseLocator(org="mockx", course="100", run="2015"), block_type='mock_type', block_id="mock_id"
+ ),
+ )
def handler(self, _context):
"""
@@ -45,25 +43,16 @@ def handler_a(self, _context):
"""
pass # lint-amnesty, pylint: disable=unnecessary-pass
- @property
- def location(self):
- """Create a functional BlockUsageLocator for testing URL generation."""
- course_key = CourseLocator(org="mockx", course="100", run="2015")
- return BlockUsageLocator(course_key, block_type='mock_type', block_id="mock_id")
-
class TestHandlerUrl(TestCase):
"""Test the LMS handler_url"""
def setUp(self):
super().setUp()
- self.block = BlockMock(name='block', scope_ids=ScopeIds(None, None, None, 'dummy'))
- self.course_key = CourseLocator("org", "course", "run")
+ self.block = BlockMock(name='block')
self.runtime = LmsModuleSystem(
track_function=Mock(),
get_module=Mock(),
- course_id=self.course_key,
- user=Mock(),
descriptor_runtime=Mock(),
)
@@ -113,161 +102,3 @@ def test_not_thirdparty_rel(self):
parsed_fq_url = urlparse(self.runtime.handler_url(self.block, 'handler', thirdparty=False))
assert parsed_fq_url.scheme == ''
assert parsed_fq_url.hostname is None
-
-
-class TestUserServiceAPI(TestCase):
- """Test the user service interface"""
-
- def setUp(self):
- super().setUp()
- self.course_id = CourseLocator("org", "course", "run")
- self.user = UserFactory.create()
-
- self.runtime = LmsModuleSystem(
- track_function=Mock(),
- get_module=Mock(),
- user=self.user,
- course_id=self.course_id,
- descriptor_runtime=Mock(),
- )
- self.scope = 'course'
- self.key = 'key1'
-
- self.mock_block = Mock()
- self.mock_block.service_declaration.return_value = 'needs'
-
- def test_get_set_tag(self):
- # test for when we haven't set the tag yet
- tag = self.runtime.service(self.mock_block, 'user_tags').get_tag(self.scope, self.key)
- assert tag is None
-
- # set the tag
- set_value = 'value'
- self.runtime.service(self.mock_block, 'user_tags').set_tag(self.scope, self.key, set_value)
- tag = self.runtime.service(self.mock_block, 'user_tags').get_tag(self.scope, self.key)
-
- assert tag == set_value
-
- # Try to set tag in wrong scope
- with pytest.raises(ValueError):
- self.runtime.service(self.mock_block, 'user_tags').set_tag('fake_scope', self.key, set_value)
-
- # Try to get tag in wrong scope
- with pytest.raises(ValueError):
- self.runtime.service(self.mock_block, 'user_tags').get_tag('fake_scope', self.key)
-
-
-@ddt
-class TestBadgingService(ModuleStoreTestCase):
- """Test the badging service interface"""
-
- def setUp(self):
- super().setUp()
- self.course_id = CourseKey.from_string('course-v1:org+course+run')
-
- self.mock_block = Mock()
- self.mock_block.service_declaration.return_value = 'needs'
-
- def create_runtime(self):
- """
- Create the testing runtime.
- """
- return LmsModuleSystem(
- track_function=Mock(),
- get_module=Mock(),
- course_id=self.course_id,
- user=self.user,
- descriptor_runtime=Mock(),
- )
-
- @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
- def test_service_rendered(self):
- runtime = self.create_runtime()
- assert runtime.service(self.mock_block, 'badging')
-
- @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': False})
- def test_no_service_rendered(self):
- runtime = self.create_runtime()
- assert not runtime.service(self.mock_block, 'badging')
-
- @data(True, False)
- @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
- def test_course_badges_toggle(self, toggle):
- self.course_id = CourseFactory.create(metadata={'issue_badges': toggle}).location.course_key
- runtime = self.create_runtime()
- assert runtime.service(self.mock_block, 'badging').course_badges_enabled is toggle
-
- @patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
- def test_get_badge_class(self):
- runtime = self.create_runtime()
- badge_service = runtime.service(self.mock_block, '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.
- with get_image('good') as image_handle:
- badge_class = badge_service.get_badge_class(
- slug='test_slug', issuing_component='test_component', description='Attempted override',
- criteria='test', display_name='Testola', image_file_handle=image_handle
- )
- # These defaults are set on the factory.
- assert badge_class.criteria == 'https://example.com/syllabus'
- assert badge_class.display_name == 'Test Badge'
- assert badge_class.description == "Yay! It's a test badge."
- # File name won't always be the same.
- assert badge_class.image.path == premade_badge_class.image.path
-
-
-class TestI18nService(ModuleStoreTestCase):
- """ Test ModuleI18nService """
-
- def setUp(self):
- """ Setting up tests """
- super().setUp()
- self.course = CourseFactory.create()
- self.test_language = 'dummy language'
- self.runtime = LmsModuleSystem(
- track_function=Mock(),
- get_module=Mock(),
- course_id=self.course.id,
- user=Mock(),
- descriptor_runtime=Mock(),
- )
-
- self.mock_block = Mock()
- self.mock_block.service_declaration.return_value = 'need'
-
- def test_module_i18n_lms_service(self):
- """
- Test: module i18n service in LMS
- """
- i18n_service = self.runtime.service(self.mock_block, 'i18n')
- assert i18n_service is not None
- assert isinstance(i18n_service, ModuleI18nService)
-
- def test_no_service_exception_with_none_declaration_(self):
- """
- Test: NoSuchServiceError should be raised block declaration returns none
- """
- self.mock_block.service_declaration.return_value = None
- with pytest.raises(NoSuchServiceError):
- self.runtime.service(self.mock_block, '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
- with pytest.raises(NoSuchServiceError):
- self.runtime.service(self.mock_block, 'i18n')
-
- 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
-
- 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.mock_block, 'i18n'))
diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py
index cee69dd4f3e9..ff817a315054 100644
--- a/openedx/features/course_duration_limits/access.py
+++ b/openedx/features/course_duration_limits/access.py
@@ -249,7 +249,7 @@ def course_expiration_wrapper(user, block, view, frag, context): # pylint: disa
return frag
course_expiration_fragment = generate_course_expired_fragment_from_key(
- user, block.course_id
+ user, block.scope_ids.usage_id.context_key
)
if not course_expiration_fragment:
return frag
diff --git a/xmodule/capa_module.py b/xmodule/capa_module.py
index 4e1822dd28e3..71be84139302 100644
--- a/xmodule/capa_module.py
+++ b/xmodule/capa_module.py
@@ -36,7 +36,7 @@
from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.graders import ShowCorrectness
from xmodule.raw_module import RawMixin
-from xmodule.util.sandboxing import get_python_lib_zip
+from xmodule.util.sandboxing import SandboxService
from xmodule.util.xmodule_django import add_webpack_to_fragment
from xmodule.x_module import (
HTMLSnippet,
@@ -684,7 +684,9 @@ def generate_report_data(self, user_state_iterator, limit_responses=None):
anonymous_student_id=None,
cache=None,
can_execute_unsafe_code=lambda: None,
- get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, self.runtime.course_id)),
+ get_python_lib_zip=(
+ lambda: SandboxService(contentstore, self.scope_ids.usage_id.context_key).get_python_lib_zip()
+ ),
DEBUG=None,
i18n=self.runtime.service(self, "i18n"),
render_template=None,
diff --git a/xmodule/discussion_block.py b/xmodule/discussion_block.py
index c7eb09d2a278..3e0d4ed254f0 100644
--- a/xmodule/discussion_block.py
+++ b/xmodule/discussion_block.py
@@ -73,13 +73,6 @@ class DiscussionXBlock(XBlock, StudioEditableXBlockMixin, XmlParserMixin): # li
@property
def course_key(self):
- """
- :return: int course id
-
- NB: The goal is to move this XBlock out of edx-platform, and so we use
- scope_ids.usage_id instead of runtime.course_id so that the code will
- continue to work with workbench-based testing.
- """
return getattr(self.scope_ids.usage_id, 'course_key', None)
@property
diff --git a/xmodule/seq_module.py b/xmodule/seq_module.py
index f8c4ce95b898..a0b11ee793a6 100644
--- a/xmodule/seq_module.py
+++ b/xmodule/seq_module.py
@@ -213,7 +213,7 @@ def _get_course(self):
"""
Return course by course id.
"""
- return self.runtime.modulestore.get_course(self.course_id) # pylint: disable=no-member
+ return self.runtime.modulestore.get_course(self.scope_ids.usage_id.context_key) # pylint: disable=no-member
@property
def is_timed_exam(self):
@@ -451,7 +451,7 @@ def gate_entire_sequence_if_it_is_a_timed_exam_and_contains_content_type_gated_p
content_type_gating_service = self.runtime.service(self, 'content_type_gating')
if content_type_gating_service:
self.gated_sequence_paywall = content_type_gating_service.check_children_for_content_type_gating_paywall(
- self, self.course_id
+ self, self.scope_ids.usage_id.context_key
)
def student_view(self, context):
@@ -614,7 +614,7 @@ def _student_or_public_view(self, context, prereq_met, prereq_meta_info, banner_
if SHOW_PROGRESS_BAR.is_enabled() and getattr(settings, 'COMPLETION_AGGREGATOR_URL', ''):
parent_block_id = self.get_parent().scope_ids.usage_id.block_id
params['chapter_completion_aggregator_url'] = '/'.join(
- [settings.COMPLETION_AGGREGATOR_URL, str(self.course_id), parent_block_id]) + '/'
+ [settings.COMPLETION_AGGREGATOR_URL, str(self.scope_ids.usage_id.context_key), parent_block_id]) + '/'
fragment.add_content(self.runtime.service(self, 'mako').render_template("seq_module.html", params))
self._capture_full_seq_item_metrics(display_items)
@@ -655,7 +655,7 @@ def _is_gate_fulfilled(self):
if gating_service:
user_id = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_USER_ID)
fulfilled = gating_service.is_gate_fulfilled(
- self.course_id, self.location, user_id
+ self.scope_ids.usage_id.context_key, self.location, user_id
)
return fulfilled
@@ -671,7 +671,7 @@ def _required_prereq(self):
gating_service = self.runtime.service(self, 'gating')
if gating_service:
milestone = gating_service.required_prereq(
- self.course_id, self.location, 'requires'
+ self.scope_ids.usage_id.context_key, self.location, 'requires'
)
return milestone
@@ -810,7 +810,7 @@ def _render_student_view_for_items(self, context, display_items, fragment, view=
contains_content_type_gated_content = False
if content_type_gating_service:
contains_content_type_gated_content = content_type_gating_service.check_children_for_content_type_gating_paywall( # pylint:disable=line-too-long
- item, self.course_id
+ item, self.scope_ids.usage_id.context_key
) is not None
iteminfo = {
'content': content,
@@ -931,7 +931,7 @@ def _time_limited_student_view(self):
user_id = current_user.opt_attrs.get(ATTR_KEY_USER_ID)
user_is_staff = current_user.opt_attrs.get(ATTR_KEY_USER_IS_STAFF)
user_role_in_course = 'staff' if user_is_staff else 'student'
- course_id = self.runtime.course_id
+ course_id = self.scope_ids.usage_id.context_key
content_id = self.location
context = {
diff --git a/xmodule/tests/__init__.py b/xmodule/tests/__init__.py
index c5808ab5a32d..4554d2e19f0b 100644
--- a/xmodule/tests/__init__.py
+++ b/xmodule/tests/__init__.py
@@ -50,19 +50,6 @@ class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
"""
ModuleSystem for testing
"""
- def __init__(self, **kwargs):
- course_id = kwargs['course_id']
- id_manager = CourseLocationManager(course_id)
- kwargs.setdefault('id_reader', id_manager)
- kwargs.setdefault('id_generator', id_manager)
-
- services = kwargs.get('services', {})
- services.setdefault('cache', CacheService(DoNothingCache()))
- services.setdefault('field-data', DictFieldData({}))
- services.setdefault('sandbox', SandboxService(contentstore, course_id))
- kwargs['services'] = services
- super().__init__(**kwargs)
-
def handler_url(self, 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),
@@ -132,6 +119,8 @@ def get_test_system(
descriptor_system = get_test_descriptor_system()
+ id_manager = CourseLocationManager(course_id)
+
def get_module(descriptor):
"""Mocks module_system get_module function"""
@@ -162,10 +151,14 @@ def get_module(descriptor):
waittime=10,
construct_callback=Mock(name='get_test_system.xqueue.construct_callback', side_effect="/"),
),
- 'replace_urls': replace_url_service
+ 'replace_urls': replace_url_service,
+ 'cache': CacheService(DoNothingCache()),
+ 'field-data': DictFieldData({}),
+ 'sandbox': SandboxService(contentstore, course_id),
},
- course_id=course_id,
descriptor_runtime=descriptor_system,
+ id_reader=id_manager,
+ id_generator=id_manager,
)
diff --git a/xmodule/tests/test_lti_unit.py b/xmodule/tests/test_lti_unit.py
index 2ca2a6492c19..6ef8cb846e9e 100644
--- a/xmodule/tests/test_lti_unit.py
+++ b/xmodule/tests/test_lti_unit.py
@@ -12,6 +12,7 @@
from django.conf import settings
from django.test import TestCase, override_settings
from lxml import etree
+from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import BlockUsageLocator
from pytz import UTC
from webob.request import Request
@@ -61,14 +62,15 @@ def setUp(self):
""")
- self.system = get_test_system()
+ 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.xmodule = LTIBlock(
self.system,
DictFieldData({}),
- ScopeIds(None, None, None, BlockUsageLocator(self.system.course_id, 'lti', 'name'))
+ ScopeIds(None, None, None, BlockUsageLocator(self.course_id, 'lti', 'name'))
)
current_user = self.system.service(self.xmodule, 'user').get_current_user()
self.user_id = current_user.opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
@@ -319,7 +321,7 @@ def test_resource_link_id(self):
def test_lis_result_sourcedid(self):
expected_sourced_id = ':'.join(parse.quote(i) for i in (
- str(self.system.course_id),
+ str(self.course_id),
self.xmodule.get_resource_link_id(),
self.user_id
))
@@ -539,4 +541,4 @@ def test_context_id(self):
"""
Tests that LTI parameter context_id is equal to course_id.
"""
- assert str(self.system.course_id) == self.xmodule.context_id
+ assert str(self.course_id) == self.xmodule.context_id
diff --git a/xmodule/tests/test_poll.py b/xmodule/tests/test_poll.py
index b7a58bda94bf..ea0f6049db62 100644
--- a/xmodule/tests/test_poll.py
+++ b/xmodule/tests/test_poll.py
@@ -5,6 +5,7 @@
from unittest.mock import Mock
+from opaque_keys.edx.keys import CourseKey
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xmodule.poll_module import PollBlock
@@ -24,8 +25,9 @@ class PollBlockTest(unittest.TestCase):
def setUp(self):
super().setUp()
- self.system = get_test_system()
- usage_key = self.system.course_id.make_usage_key(PollBlock.category, 'test_loc')
+ course_key = CourseKey.from_string('org/course/run')
+ self.system = get_test_system(course_key)
+ usage_key = course_key.make_usage_key(PollBlock.category, 'test_loc')
# ScopeIds has 4 fields: user_id, block_type, def_id, usage_id
scope_ids = ScopeIds(1, PollBlock.category, usage_key, usage_key)
self.xmodule = PollBlock(
diff --git a/xmodule/tests/test_video.py b/xmodule/tests/test_video.py
index d82b43ecaff1..79e2522ce230 100644
--- a/xmodule/tests/test_video.py
+++ b/xmodule/tests/test_video.py
@@ -701,7 +701,6 @@ def test_export_to_xml(self, mock_val_api):
self.descriptor.download_video = True
self.descriptor.transcripts = {'ua': 'ukrainian_translation.srt', 'ge': 'german_translation.srt'}
self.descriptor.edx_video_id = edx_video_id
- self.descriptor.runtime.course_id = MagicMock()
xml = self.descriptor.definition_to_xml(self.file_system)
parser = etree.XMLParser(remove_blank_text=True)
@@ -731,7 +730,7 @@ def test_export_to_xml(self, mock_val_api):
video_id=edx_video_id,
static_dir=EXPORT_IMPORT_STATIC_DIR,
resource_fs=self.file_system,
- course_id=str(self.descriptor.runtime.course_id.for_branch(None)),
+ course_id=self.descriptor.scope_ids.usage_id.context_key,
)
@patch('xmodule.video_module.video_module.edxval_api')
@@ -740,7 +739,6 @@ def test_export_to_xml_val_error(self, mock_val_api):
mock_val_api.ValVideoNotFoundError = _MockValVideoNotFoundError
mock_val_api.export_to_xml = Mock(side_effect=mock_val_api.ValVideoNotFoundError)
self.descriptor.edx_video_id = 'test_edx_video_id'
- self.descriptor.runtime.course_id = MagicMock()
xml = self.descriptor.definition_to_xml(self.file_system)
parser = etree.XMLParser(remove_blank_text=True)
@@ -861,7 +859,6 @@ def test_student_view_data(self, field_data, expected_student_view_data):
Ensure that student_view_data returns the expected results for video modules.
"""
descriptor = instantiate_descriptor(**field_data)
- descriptor.runtime.course_id = MagicMock()
student_view_data = descriptor.student_view_data()
assert student_view_data == expected_student_view_data
@@ -896,7 +893,6 @@ def test_student_view_data_with_hls_flag(self, mock_get_video_info, mock_get_vid
}
descriptor = instantiate_descriptor(edx_video_id='example_id', only_on_web=False)
- descriptor.runtime.course_id = MagicMock()
descriptor.runtime.handler_url = MagicMock()
student_view_data = descriptor.student_view_data()
expected_video_data = {'hls': {'url': 'http://www.meowmix.com', 'file_size': 25556}}
diff --git a/xmodule/video_module/video_module.py b/xmodule/video_module/video_module.py
index 4527907102c4..a7aad184ff98 100644
--- a/xmodule/video_module/video_module.py
+++ b/xmodule/video_module/video_module.py
@@ -371,7 +371,7 @@ def get_html(self, view=STUDENT_VIEW): # lint-amnesty, pylint: disable=argument
poster = None
if edxval_api and self.edx_video_id:
poster = edxval_api.get_course_video_image_url(
- course_id=self.runtime.course_id.for_branch(None),
+ course_id=self.scope_ids.usage_id.context_key.for_branch(None),
edx_video_id=self.edx_video_id.strip()
)
@@ -741,12 +741,11 @@ def definition_to_xml(self, resource_fs): # lint-amnesty, pylint: disable=too-m
# (i.e. `self.transcripts`) on import and older open-releases (<= ginkgo),
# who do not have deprecated contentstore yet, can also import and use new-style
# transcripts into their openedX instances.
-
exported_metadata = edxval_api.export_to_xml(
video_id=edx_video_id,
resource_fs=resource_fs,
static_dir=EXPORT_IMPORT_STATIC_DIR,
- course_id=str(self.runtime.course_id.for_branch(None))
+ course_id=self.scope_ids.usage_id.context_key.for_branch(None),
)
# Update xml with edxval metadata
xml.append(exported_metadata['xml'])
@@ -832,7 +831,7 @@ def get_youtube_link(video_id):
if self.edx_video_id and edxval_api:
val_profiles = ['youtube', 'desktop_webm', 'desktop_mp4']
- if HLSPlaybackEnabledFlag.feature_enabled(self.runtime.course_id.for_branch(None)):
+ if HLSPlaybackEnabledFlag.feature_enabled(self.scope_ids.usage_id.context_key.for_branch(None)):
val_profiles.append('hls')
# Get video encodings for val profiles.
diff --git a/xmodule/x_module.py b/xmodule/x_module.py
index c617d78613a8..7859bc8370bc 100644
--- a/xmodule/x_module.py
+++ b/xmodule/x_module.py
@@ -1073,25 +1073,11 @@ class MetricsMixin:
def render(self, block, view_name, context=None): # lint-amnesty, pylint: disable=missing-function-docstring
start_time = time.time()
- status = "success"
try:
return super().render(block, view_name, context=context)
- except:
- status = "failure"
- raise
-
finally:
end_time = time.time()
duration = end_time - start_time
- course_id = getattr(self, 'course_id', '')
- tags = [ # lint-amnesty, pylint: disable=unused-variable
- f'view_name:{view_name}',
- 'action:render',
- f'action_status:{status}',
- f'course_id:{course_id}',
- f'block_type:{block.scope_ids.block_type}',
- f'block_family:{block.entry_point}',
- ]
log.debug(
"%.3fs - render %s.%s (%s)",
duration,
@@ -1102,25 +1088,11 @@ def render(self, block, view_name, context=None): # lint-amnesty, pylint: disab
def handle(self, block, handler_name, request, suffix=''): # lint-amnesty, pylint: disable=missing-function-docstring
start_time = time.time()
- status = "success"
try:
return super().handle(block, handler_name, request, suffix=suffix)
- except:
- status = "failure"
- raise
-
finally:
end_time = time.time()
duration = end_time - start_time
- course_id = getattr(self, 'course_id', '')
- tags = [ # lint-amnesty, pylint: disable=unused-variable
- f'handler_name:{handler_name}',
- 'action:handle',
- f'action_status:{status}',
- f'course_id:{course_id}',
- f'block_type:{block.scope_ids.block_type}',
- f'block_family:{block.entry_point}',
- ]
log.debug(
"%.3fs - handle %s.%s (%s)",
duration,
@@ -1718,6 +1690,19 @@ def STATIC_URL(self): # pylint: disable=invalid-name
)
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):
"""
@@ -1738,7 +1723,6 @@ def __init__(
get_module,
descriptor_runtime,
publish=None,
- course_id=None,
**kwargs,
):
"""
@@ -1755,8 +1739,6 @@ def __init__(
descriptor_runtime - A `DescriptorSystem` to use for loading xblocks by id
- course_id - the course_id containing this module
-
publish(event) - A function that allows XModules to publish events (such as grade changes)
"""
@@ -1766,7 +1748,6 @@ def __init__(
self.track_function = track_function
self.get_module = get_module
- self.course_id = course_id
if publish:
self.publish = publish