Merge pull request #30715 from open-craft/agrendalath/bd-13-deprecate_course_id
refactor: deprecate course_id from ModuleSystem [BD-13]
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -296,7 +296,7 @@ class CmsModuleSystemShimTest(ModuleStoreTestCase):
|
||||
def test_replace_urls(self):
|
||||
html = '<a href="/static/id">'
|
||||
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'
|
||||
|
||||
@@ -7,10 +7,12 @@ import json
|
||||
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.urls import reverse
|
||||
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.exceptions import NoSuchHandlerError, NoSuchViewError
|
||||
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.courseware.services import UserStateService
|
||||
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 get_module_system_for_user(
|
||||
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 get_module_system_for_user(
|
||||
'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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -34,6 +34,7 @@ from pyquery import PyQuery # lint-amnesty, pylint: disable=wrong-import-order
|
||||
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.contentstore.django import contentstore
|
||||
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 GlobalStaffFactory
|
||||
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 @@ from common.djangoapps.xblock_django.models import XBlockConfiguration
|
||||
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 @@ class TestEventPublishing(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
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 @@ class LMSXBlockServiceBindingTest(SharedModuleStoreTestCase):
|
||||
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 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase):
|
||||
def test_replace_urls(self):
|
||||
html = '<a href="/static/id">'
|
||||
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 = '<a href="/course/id">'
|
||||
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 = '<a href="/jump_to_id/id">'
|
||||
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
|
||||
|
||||
@@ -1424,7 +1424,6 @@ class TestVideoBlockStudentViewJson(BaseTestVideoXBlock, CacheIsolationTestCase)
|
||||
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 @@ class TestVideoBlockStudentViewJson(BaseTestVideoXBlock, CacheIsolationTestCase)
|
||||
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)
|
||||
|
||||
@@ -33,8 +33,8 @@ def edxnotes(cls):
|
||||
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 edxnotes(cls):
|
||||
),
|
||||
"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,
|
||||
|
||||
@@ -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 @@ class EdxNotesDecoratorTest(ModuleStoreTestCase):
|
||||
"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 @@ class EdxNotesDecoratorTest(ModuleStoreTestCase):
|
||||
"""
|
||||
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):
|
||||
|
||||
@@ -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!'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,29 +3,27 @@ Tests of the LMS XBlock Runtime and associated utilities
|
||||
"""
|
||||
|
||||
|
||||
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 @@ class BlockMock(Mock):
|
||||
"""
|
||||
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 @@ class TestHandlerUrl(TestCase):
|
||||
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'))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,7 +36,7 @@ from xmodule.editing_module import EditingMixin
|
||||
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 @@ class ProblemBlock(
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -213,7 +213,7 @@ class ProctoringFields:
|
||||
"""
|
||||
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 @@ class SequenceBlock(
|
||||
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 @@ class SequenceBlock(
|
||||
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 @@ class SequenceBlock(
|
||||
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 @@ class SequenceBlock(
|
||||
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 @@ class SequenceBlock(
|
||||
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 @@ class SequenceBlock(
|
||||
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 = {
|
||||
|
||||
@@ -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_test_system(
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import pytest
|
||||
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 @@ class LTIBlockTest(TestCase):
|
||||
</imsx_POXBody>
|
||||
</imsx_POXEnvelopeRequest>
|
||||
""")
|
||||
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 @@ class LTIBlockTest(TestCase):
|
||||
|
||||
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 @@ class LTIBlockTest(TestCase):
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -5,6 +5,7 @@ import unittest
|
||||
|
||||
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(
|
||||
|
||||
@@ -701,7 +701,6 @@ class VideoExportTestCase(VideoBlockTestBase):
|
||||
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 @@ class VideoExportTestCase(VideoBlockTestBase):
|
||||
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 @@ class VideoExportTestCase(VideoBlockTestBase):
|
||||
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 @@ class VideoBlockStudentViewDataTestCase(unittest.TestCase):
|
||||
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 @@ class VideoBlockStudentViewDataTestCase(unittest.TestCase):
|
||||
}
|
||||
|
||||
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}}
|
||||
|
||||
@@ -371,7 +371,7 @@ class VideoBlock(
|
||||
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 @@ class VideoBlock(
|
||||
# (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 @@ class VideoBlock(
|
||||
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.
|
||||
|
||||
@@ -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 @@ class MetricsMixin:
|
||||
|
||||
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 @@ class ModuleSystemShim:
|
||||
)
|
||||
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 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim,
|
||||
get_module,
|
||||
descriptor_runtime,
|
||||
publish=None,
|
||||
course_id=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@@ -1755,8 +1739,6 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim,
|
||||
|
||||
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 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim,
|
||||
|
||||
self.track_function = track_function
|
||||
self.get_module = get_module
|
||||
self.course_id = course_id
|
||||
|
||||
if publish:
|
||||
self.publish = publish
|
||||
|
||||
Reference in New Issue
Block a user