refactor: convert course_module term to course_block
This commit is contained in:
@@ -111,9 +111,9 @@ class AdvancedCourseSettingsView(DeveloperErrorViewMixin, APIView):
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
if not has_studio_read_access(request.user, course_key):
|
||||
self.permission_denied(request)
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
return Response(CourseMetadata.fetch_all(
|
||||
course_module,
|
||||
course_block,
|
||||
filter_fields=filter_query_data.cleaned_data['filter_fields'],
|
||||
))
|
||||
|
||||
@@ -174,6 +174,6 @@ class AdvancedCourseSettingsView(DeveloperErrorViewMixin, APIView):
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
if not has_studio_write_access(request.user, course_key):
|
||||
self.permission_denied(request)
|
||||
course_module = modulestore().get_course(course_key)
|
||||
updated_data = update_course_advanced_settings(course_module, request.data, request.user)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
updated_data = update_course_advanced_settings(course_block, request.data, request.user)
|
||||
return Response(updated_data)
|
||||
|
||||
@@ -81,8 +81,8 @@ class CourseTabListView(DeveloperErrorViewMixin, APIView):
|
||||
if not has_studio_read_access(request.user, course_key):
|
||||
self.permission_denied(request)
|
||||
|
||||
course_module = modulestore().get_course(course_key)
|
||||
tabs_to_render = get_course_tabs(course_module, request.user)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
tabs_to_render = get_course_tabs(course_block, request.user)
|
||||
return Response(CourseTabSerializer(tabs_to_render, many=True).data)
|
||||
|
||||
|
||||
@@ -147,12 +147,12 @@ class CourseTabSettingsView(DeveloperErrorViewMixin, APIView):
|
||||
tab_id_locator = TabIDLocatorSerializer(data=request.query_params)
|
||||
tab_id_locator.is_valid(raise_exception=True)
|
||||
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
serializer = CourseTabUpdateSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
edit_tab_handler(
|
||||
course_module,
|
||||
course_block,
|
||||
{
|
||||
"tab_id_locator": tab_id_locator.data,
|
||||
**serializer.data,
|
||||
@@ -216,11 +216,11 @@ class CourseTabReorderView(DeveloperErrorViewMixin, APIView):
|
||||
if not has_studio_write_access(request.user, course_key):
|
||||
self.permission_denied(request)
|
||||
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
tab_id_locators = TabIDLocatorSerializer(data=request.data, many=True)
|
||||
tab_id_locators.is_valid(raise_exception=True)
|
||||
reorder_tabs_handler(
|
||||
course_module,
|
||||
course_block,
|
||||
tab_id_locators.validated_data,
|
||||
request.user,
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ class ProctoringExamSettingsTestMixin():
|
||||
response = self.make_request()
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
def test_404_no_course_module(self):
|
||||
def test_404_no_course_block(self):
|
||||
course_id = 'course-v1:edX+ToyX_Nonexistent_Course+Toy_Course'
|
||||
self.client.login(username=self.global_staff, password=self.password)
|
||||
response = self.make_request(course_id=course_id)
|
||||
|
||||
@@ -92,8 +92,8 @@ class ProctoredExamSettingsView(APIView):
|
||||
def get(self, request, course_id):
|
||||
""" GET handler """
|
||||
with modulestore().bulk_operations(CourseKey.from_string(course_id)):
|
||||
course_module = self._get_and_validate_course_access(request.user, course_id)
|
||||
course_metadata = CourseMetadata().fetch_all(course_module)
|
||||
course_block = self._get_and_validate_course_access(request.user, course_id)
|
||||
course_metadata = CourseMetadata().fetch_all(course_block)
|
||||
proctored_exam_settings = self._get_proctored_exam_setting_values(course_metadata)
|
||||
|
||||
data = {}
|
||||
@@ -123,8 +123,8 @@ class ProctoredExamSettingsView(APIView):
|
||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
with modulestore().bulk_operations(CourseKey.from_string(course_id)):
|
||||
course_module = self._get_and_validate_course_access(request.user, course_id)
|
||||
course_metadata = CourseMetadata().fetch_all(course_module)
|
||||
course_block = self._get_and_validate_course_access(request.user, course_id)
|
||||
course_metadata = CourseMetadata().fetch_all(course_block)
|
||||
|
||||
models_to_update = {}
|
||||
for setting_key, value in exam_config.data.items():
|
||||
@@ -133,9 +133,9 @@ class ProctoredExamSettingsView(APIView):
|
||||
models_to_update[setting_key] = copy.deepcopy(model)
|
||||
models_to_update[setting_key]['value'] = value
|
||||
|
||||
# validate data formats and update the course module object
|
||||
# validate data formats and update the course block object
|
||||
is_valid, errors, updated_data = CourseMetadata.validate_and_update_from_json(
|
||||
course_module,
|
||||
course_block,
|
||||
models_to_update,
|
||||
user=request.user,
|
||||
)
|
||||
@@ -148,7 +148,7 @@ class ProctoredExamSettingsView(APIView):
|
||||
)
|
||||
|
||||
# save to mongo
|
||||
modulestore().update_item(course_module, request.user.id)
|
||||
modulestore().update_item(course_block, request.user.id)
|
||||
|
||||
# merge updated settings with all existing settings.
|
||||
# do this because fields that could not be modified are excluded from the result
|
||||
@@ -171,14 +171,14 @@ class ProctoredExamSettingsView(APIView):
|
||||
"""
|
||||
Check if course_id exists and is accessible by the user.
|
||||
|
||||
Returns a course_module object
|
||||
Returns a course_block object
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course_module = get_course_and_check_access(course_key, user)
|
||||
course_block = get_course_and_check_access(course_key, user)
|
||||
|
||||
if not course_module:
|
||||
if not course_block:
|
||||
raise NotFound(
|
||||
f'Course with course_id {course_id} does not exist.'
|
||||
)
|
||||
|
||||
return course_module
|
||||
return course_block
|
||||
|
||||
@@ -167,7 +167,7 @@ def rerun_course(source_course_key_string, destination_course_key_string, user_i
|
||||
# cleanup any remnants of the course
|
||||
modulestore().delete_course(destination_course_key, user_id)
|
||||
except ItemNotFoundError:
|
||||
# it's possible there was an error even before the course module was created
|
||||
# it's possible there was an error even before the course block was created
|
||||
pass
|
||||
|
||||
return "exception: " + str(exc)
|
||||
@@ -335,13 +335,13 @@ def export_olx(self, user_id, course_key_string, language):
|
||||
return
|
||||
|
||||
|
||||
def create_export_tarball(course_module, course_key, context, status=None):
|
||||
def create_export_tarball(course_block, course_key, context, status=None):
|
||||
"""
|
||||
Generates the export tarball, or returns None if there was an error.
|
||||
|
||||
Updates the context with any error information if applicable.
|
||||
"""
|
||||
name = course_module.url_name
|
||||
name = course_block.url_name
|
||||
export_file = NamedTemporaryFile(prefix=name + '.', suffix=".tar.gz") # lint-amnesty, pylint: disable=consider-using-with
|
||||
root_dir = path(mkdtemp())
|
||||
|
||||
@@ -349,7 +349,7 @@ def create_export_tarball(course_module, course_key, context, status=None):
|
||||
if isinstance(course_key, LibraryLocator):
|
||||
export_library_to_xml(modulestore(), contentstore(), course_key, root_dir, name)
|
||||
else:
|
||||
export_course_to_xml(modulestore(), contentstore(), course_module.id, root_dir, name)
|
||||
export_course_to_xml(modulestore(), contentstore(), course_block.id, root_dir, name)
|
||||
|
||||
if status:
|
||||
status.set_state('Compressing')
|
||||
|
||||
@@ -348,7 +348,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
|
||||
# check for policy.json
|
||||
self.assertTrue(filesystem.exists('policy.json'))
|
||||
|
||||
# compare what's on disk to what we have in the course module
|
||||
# compare what's on disk to what we have in the course block
|
||||
with filesystem.open('policy.json', 'r') as course_policy:
|
||||
on_disk = loads(course_policy.read())
|
||||
self.assertIn('course/2012_Fall', on_disk)
|
||||
@@ -1195,8 +1195,8 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
"""Test new course creation and verify default language"""
|
||||
test_course_data = self.assert_created_course()
|
||||
course_id = _get_course_id(self.store, test_course_data)
|
||||
course_module = self.store.get_course(course_id)
|
||||
self.assertEqual(course_module.language, 'hr')
|
||||
course_block = self.store.get_course(course_id)
|
||||
self.assertEqual(course_block.language, 'hr')
|
||||
|
||||
def test_create_course_with_dots(self):
|
||||
"""Test new course creation with dots in the name"""
|
||||
@@ -1617,12 +1617,12 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
#
|
||||
|
||||
# first check PDF textbooks, to make sure the url paths got updated
|
||||
course_module = self.store.get_course(target_id)
|
||||
course_block = self.store.get_course(target_id)
|
||||
|
||||
self.assertEqual(len(course_module.pdf_textbooks), 1)
|
||||
self.assertEqual(len(course_module.pdf_textbooks[0]["chapters"]), 2)
|
||||
self.assertEqual(course_module.pdf_textbooks[0]["chapters"][0]["url"], '/static/Chapter1.pdf')
|
||||
self.assertEqual(course_module.pdf_textbooks[0]["chapters"][1]["url"], '/static/Chapter2.pdf')
|
||||
self.assertEqual(len(course_block.pdf_textbooks), 1)
|
||||
self.assertEqual(len(course_block.pdf_textbooks[0]["chapters"]), 2)
|
||||
self.assertEqual(course_block.pdf_textbooks[0]["chapters"][0]["url"], '/static/Chapter1.pdf')
|
||||
self.assertEqual(course_block.pdf_textbooks[0]["chapters"][1]["url"], '/static/Chapter2.pdf')
|
||||
|
||||
def test_import_into_new_course_id_wiki_slug_renamespacing(self):
|
||||
# If reimporting into the same course do not change the wiki_slug.
|
||||
@@ -1634,14 +1634,14 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
'run': target_id.run
|
||||
}
|
||||
_create_course(self, target_id, course_data)
|
||||
course_module = self.store.get_course(target_id)
|
||||
course_module.wiki_slug = 'toy'
|
||||
course_module.save()
|
||||
course_block = self.store.get_course(target_id)
|
||||
course_block.wiki_slug = 'toy'
|
||||
course_block.save()
|
||||
|
||||
# Import a course with wiki_slug == location.course
|
||||
import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_id=target_id)
|
||||
course_module = self.store.get_course(target_id)
|
||||
self.assertEqual(course_module.wiki_slug, 'toy')
|
||||
course_block = self.store.get_course(target_id)
|
||||
self.assertEqual(course_block.wiki_slug, 'toy')
|
||||
|
||||
# But change the wiki_slug if it is a different course.
|
||||
target_id = self.store.make_course_key('MITx', '111', '2013_Spring')
|
||||
@@ -1655,13 +1655,13 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
|
||||
# Import a course with wiki_slug == location.course
|
||||
import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], target_id=target_id)
|
||||
course_module = self.store.get_course(target_id)
|
||||
self.assertEqual(course_module.wiki_slug, 'MITx.111.2013_Spring')
|
||||
course_block = self.store.get_course(target_id)
|
||||
self.assertEqual(course_block.wiki_slug, 'MITx.111.2013_Spring')
|
||||
|
||||
# Now try importing a course with wiki_slug == '{0}.{1}.{2}'.format(location.org, location.course, location.run)
|
||||
import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['two_toys'], target_id=target_id)
|
||||
course_module = self.store.get_course(target_id)
|
||||
self.assertEqual(course_module.wiki_slug, 'MITx.111.2013_Spring')
|
||||
course_block = self.store.get_course(target_id)
|
||||
self.assertEqual(course_block.wiki_slug, 'MITx.111.2013_Spring')
|
||||
|
||||
def test_import_metadata_with_attempts_empty_string(self):
|
||||
import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['simple'], create_if_not_present=True)
|
||||
@@ -1797,8 +1797,8 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
|
||||
course_key = _get_course_id(self.store, self.course_data)
|
||||
_create_course(self, course_key, self.course_data)
|
||||
course_module = self.store.get_course(course_key)
|
||||
self.assertEqual(course_module.wiki_slug, 'MITx.111.2013_Spring')
|
||||
course_block = self.store.get_course(course_key)
|
||||
self.assertEqual(course_block.wiki_slug, 'MITx.111.2013_Spring')
|
||||
|
||||
def test_course_handler_with_invalid_course_key_string(self):
|
||||
"""Test viewing the course overview page with invalid course id"""
|
||||
|
||||
@@ -850,7 +850,7 @@ class TestLibrarySearchIndexer(MixedWithOptionsTestCase):
|
||||
|
||||
class GroupConfigurationSearchSplit(CourseTestCase, MixedWithOptionsTestCase):
|
||||
"""
|
||||
Tests indexing of content groups on course modules using split modulestore.
|
||||
Tests indexing of content groups on course blocks using split modulestore.
|
||||
"""
|
||||
CREATE_USER = True
|
||||
INDEX_NAME = CoursewareSearchIndexer.INDEX_NAME
|
||||
|
||||
@@ -33,7 +33,7 @@ class TestExportGit(CourseTestCase):
|
||||
Setup test course, user, and url.
|
||||
"""
|
||||
super().setUp()
|
||||
self.course_module = modulestore().get_course(self.course.id)
|
||||
self.course_block = modulestore().get_course(self.course.id)
|
||||
self.test_url = reverse_course_url('export_git', self.course.id)
|
||||
|
||||
def make_bare_repo_with_course(self, repo_name):
|
||||
@@ -55,8 +55,8 @@ class TestExportGit(CourseTestCase):
|
||||
|
||||
subprocess.check_output(['git', '--bare', 'init', ], cwd=bare_repo_dir)
|
||||
self.populate_course()
|
||||
self.course_module.giturl = f'file://{bare_repo_dir}'
|
||||
modulestore().update_item(self.course_module, self.user.id)
|
||||
self.course_block.giturl = f'file://{bare_repo_dir}'
|
||||
modulestore().update_item(self.course_block, self.user.id)
|
||||
|
||||
def test_giturl_missing(self):
|
||||
"""
|
||||
@@ -81,8 +81,8 @@ class TestExportGit(CourseTestCase):
|
||||
"""
|
||||
Test failed course export response.
|
||||
"""
|
||||
self.course_module.giturl = 'foobar'
|
||||
modulestore().update_item(self.course_module, self.user.id)
|
||||
self.course_block.giturl = 'foobar'
|
||||
modulestore().update_item(self.course_block, self.user.id)
|
||||
|
||||
response = self.client.get(f'{self.test_url}?action=push')
|
||||
self.assertContains(response, 'Export Failed:')
|
||||
@@ -91,8 +91,8 @@ class TestExportGit(CourseTestCase):
|
||||
"""
|
||||
Regression test for making sure errors are properly stringified
|
||||
"""
|
||||
self.course_module.giturl = 'foobar'
|
||||
modulestore().update_item(self.course_module, self.user.id)
|
||||
self.course_block.giturl = 'foobar'
|
||||
modulestore().update_item(self.course_block, self.user.id)
|
||||
|
||||
response = self.client.get(f'{self.test_url}?action=push')
|
||||
self.assertNotContains(response, 'django.utils.functional.__proxy__')
|
||||
@@ -123,11 +123,11 @@ class TestExportGit(CourseTestCase):
|
||||
repo_name = 'dirty_repo1'
|
||||
self.make_bare_repo_with_course(repo_name)
|
||||
git_export_utils.export_to_git(self.course.id,
|
||||
self.course_module.giturl, self.user)
|
||||
self.course_block.giturl, self.user)
|
||||
|
||||
# Make arbitrary change to course to make diff
|
||||
self.course_module.matlab_api_key = 'something'
|
||||
modulestore().update_item(self.course_module, self.user.id)
|
||||
self.course_block.matlab_api_key = 'something'
|
||||
modulestore().update_item(self.course_block, self.user.id)
|
||||
# Touch a file in the directory, export again, and make sure
|
||||
# the test file is gone
|
||||
repo_dir = os.path.join(
|
||||
@@ -138,5 +138,5 @@ class TestExportGit(CourseTestCase):
|
||||
open(test_file, 'a').close()
|
||||
self.assertTrue(os.path.isfile(test_file))
|
||||
git_export_utils.export_to_git(self.course.id,
|
||||
self.course_module.giturl, self.user)
|
||||
self.course_block.giturl, self.user)
|
||||
self.assertFalse(os.path.isfile(test_file))
|
||||
|
||||
@@ -102,11 +102,11 @@ def _asset_index(request, course_key):
|
||||
|
||||
Supports start (0-based index into the list of assets) and max query parameters.
|
||||
'''
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
|
||||
return render_to_response('asset_index.html', {
|
||||
'language_code': request.LANGUAGE_CODE,
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'max_file_size_in_mbs': settings.MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB,
|
||||
'chunk_size_in_mbs': settings.UPLOAD_CHUNK_SIZE_IN_MB,
|
||||
'max_file_size_redirect_url': settings.MAX_ASSET_UPLOAD_FILE_SIZE_URL,
|
||||
|
||||
@@ -63,12 +63,12 @@ LOGGER = logging.getLogger(__name__)
|
||||
def _get_course_and_check_access(course_key, user, depth=0):
|
||||
"""
|
||||
Internal method used to calculate and return the locator and
|
||||
course module for the view functions in this file.
|
||||
course block for the view functions in this file.
|
||||
"""
|
||||
if not has_studio_write_access(user, course_key):
|
||||
raise PermissionDenied()
|
||||
course_module = modulestore().get_course(course_key, depth=depth)
|
||||
return course_module
|
||||
course_block = modulestore().get_course(course_key, depth=depth)
|
||||
return course_block
|
||||
|
||||
|
||||
def _delete_asset(course_key, asset_key_string):
|
||||
|
||||
@@ -27,9 +27,9 @@ def checklists_handler(request, course_key_string=None):
|
||||
if not has_course_author_access(request.user, course_key):
|
||||
raise PermissionDenied()
|
||||
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
return render_to_response('checklists.html', {
|
||||
'language_code': request.LANGUAGE_CODE,
|
||||
'context_course': course_module,
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id),
|
||||
'context_course': course_block,
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
|
||||
})
|
||||
|
||||
@@ -149,13 +149,13 @@ class AccessListFallback(Exception):
|
||||
|
||||
def get_course_and_check_access(course_key, user, depth=0):
|
||||
"""
|
||||
Function used to calculate and return the locator and course module
|
||||
Function used to calculate and return the locator and course block
|
||||
for the view functions in this file.
|
||||
"""
|
||||
if not has_studio_read_access(user, course_key):
|
||||
raise PermissionDenied()
|
||||
course_module = modulestore().get_course(course_key, depth=depth)
|
||||
return course_module
|
||||
course_block = modulestore().get_course(course_key, depth=depth)
|
||||
return course_block
|
||||
|
||||
|
||||
def reindex_course_and_check_access(course_key, user):
|
||||
@@ -285,8 +285,8 @@ def course_handler(request, course_key_string=None):
|
||||
if request.method == 'GET':
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user, depth=None)
|
||||
return JsonResponse(_course_outline_json(request, course_module))
|
||||
course_block = get_course_and_check_access(course_key, request.user, depth=None)
|
||||
return JsonResponse(_course_outline_json(request, course_block))
|
||||
elif request.method == 'POST': # not sure if this is only post. If one will have ids, it goes after access
|
||||
return _create_or_rerun_course(request)
|
||||
elif not has_studio_write_access(request.user, CourseKey.from_string(course_key_string)):
|
||||
@@ -322,11 +322,11 @@ def course_rerun_handler(request, course_key_string):
|
||||
raise PermissionDenied()
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user, depth=3)
|
||||
course_block = get_course_and_check_access(course_key, request.user, depth=3)
|
||||
if request.method == 'GET':
|
||||
return render_to_response('course-create-rerun.html', {
|
||||
'source_course_key': course_key,
|
||||
'display_name': course_module.display_name,
|
||||
'display_name': course_block.display_name,
|
||||
'user': request.user,
|
||||
'course_creator_status': _get_course_creator_status(request.user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False)
|
||||
@@ -362,16 +362,16 @@ def course_search_index_handler(request, course_key_string):
|
||||
}), content_type=content_type, status=200)
|
||||
|
||||
|
||||
def _course_outline_json(request, course_module):
|
||||
def _course_outline_json(request, course_block):
|
||||
"""
|
||||
Returns a JSON representation of the course module and recursively all of its children.
|
||||
Returns a JSON representation of the course block and recursively all of its children.
|
||||
"""
|
||||
is_concise = request.GET.get('format') == 'concise'
|
||||
include_children_predicate = lambda xblock: not xblock.category == 'vertical'
|
||||
if is_concise:
|
||||
include_children_predicate = lambda xblock: xblock.has_children
|
||||
return create_xblock_info(
|
||||
course_module,
|
||||
course_block,
|
||||
include_child_info=True,
|
||||
course_outline=False if is_concise else True, # lint-amnesty, pylint: disable=simplifiable-if-expression
|
||||
include_children_predicate=include_children_predicate,
|
||||
@@ -640,12 +640,12 @@ def _get_rerun_link_for_item(course_key):
|
||||
return reverse_course_url('course_rerun_handler', course_key)
|
||||
|
||||
|
||||
def _deprecated_blocks_info(course_module, deprecated_block_types):
|
||||
def _deprecated_blocks_info(course_block, deprecated_block_types):
|
||||
"""
|
||||
Returns deprecation information about `deprecated_block_types`
|
||||
|
||||
Arguments:
|
||||
course_module (CourseBlock): course object
|
||||
course_block (CourseBlock): course object
|
||||
deprecated_block_types (list): list of deprecated blocks types
|
||||
|
||||
Returns:
|
||||
@@ -656,14 +656,14 @@ def _deprecated_blocks_info(course_module, deprecated_block_types):
|
||||
"""
|
||||
data = {
|
||||
'deprecated_enabled_block_types': [
|
||||
block_type for block_type in course_module.advanced_modules if block_type in deprecated_block_types
|
||||
block_type for block_type in course_block.advanced_modules if block_type in deprecated_block_types
|
||||
],
|
||||
'blocks': [],
|
||||
'advance_settings_url': reverse_course_url('advanced_settings_handler', course_module.id)
|
||||
'advance_settings_url': reverse_course_url('advanced_settings_handler', course_block.id)
|
||||
}
|
||||
|
||||
deprecated_blocks = modulestore().get_items(
|
||||
course_module.id,
|
||||
course_block.id,
|
||||
qualifiers={
|
||||
'category': re.compile('^' + '$|^'.join(deprecated_block_types) + '$')
|
||||
}
|
||||
@@ -689,21 +689,21 @@ def course_index(request, course_key):
|
||||
# A depth of None implies the whole course. The course outline needs this in order to compute has_changes.
|
||||
# A unit may not have a draft version, but one of its components could, and hence the unit itself has changes.
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user, depth=None)
|
||||
if not course_module:
|
||||
course_block = get_course_and_check_access(course_key, request.user, depth=None)
|
||||
if not course_block:
|
||||
raise Http404
|
||||
lms_link = get_lms_link_for_item(course_module.location)
|
||||
lms_link = get_lms_link_for_item(course_block.location)
|
||||
reindex_link = None
|
||||
if settings.FEATURES.get('ENABLE_COURSEWARE_INDEX', False):
|
||||
if GlobalStaff().has_user(request.user):
|
||||
reindex_link = f"/course/{str(course_key)}/search_reindex"
|
||||
sections = course_module.get_children()
|
||||
course_structure = _course_outline_json(request, course_module)
|
||||
sections = course_block.get_children()
|
||||
course_structure = _course_outline_json(request, course_block)
|
||||
locator_to_show = request.GET.get('show', None)
|
||||
|
||||
course_release_date = (
|
||||
get_default_time_display(course_module.start)
|
||||
if course_module.start != DEFAULT_START_DATE
|
||||
get_default_time_display(course_block.start)
|
||||
if course_block.start != DEFAULT_START_DATE
|
||||
else _("Set Date")
|
||||
)
|
||||
|
||||
@@ -715,16 +715,16 @@ def course_index(request, course_key):
|
||||
current_action = None
|
||||
|
||||
deprecated_block_names = [block.name for block in deprecated_xblocks()]
|
||||
deprecated_blocks_info = _deprecated_blocks_info(course_module, deprecated_block_names)
|
||||
deprecated_blocks_info = _deprecated_blocks_info(course_block, deprecated_block_names)
|
||||
|
||||
frontend_app_publisher_url = configuration_helpers.get_value_for_org(
|
||||
course_module.location.org,
|
||||
course_block.location.org,
|
||||
'FRONTEND_APP_PUBLISHER_URL',
|
||||
settings.FEATURES.get('FRONTEND_APP_PUBLISHER_URL', False)
|
||||
)
|
||||
# gather any errors in the currently stored proctoring settings.
|
||||
advanced_dict = CourseMetadata.fetch(course_module)
|
||||
proctoring_errors = CourseMetadata.validate_proctoring_settings(course_module, advanced_dict, request.user)
|
||||
advanced_dict = CourseMetadata.fetch(course_block)
|
||||
proctoring_errors = CourseMetadata.validate_proctoring_settings(course_block, advanced_dict, request.user)
|
||||
|
||||
configuration = DiscussionsConfiguration.get(course_key)
|
||||
provider = configuration.provider_type
|
||||
@@ -733,7 +733,7 @@ def course_index(request, course_key):
|
||||
|
||||
return render_to_response('course_outline.html', {
|
||||
'language_code': request.LANGUAGE_CODE,
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'lms_link': lms_link,
|
||||
'sections': sections,
|
||||
'course_structure': course_structure,
|
||||
@@ -751,8 +751,8 @@ def course_index(request, course_key):
|
||||
},
|
||||
) if current_action else None,
|
||||
'frontend_app_publisher_url': frontend_app_publisher_url,
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id),
|
||||
'advance_settings_url': reverse_course_url('advanced_settings_handler', course_module.id),
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
|
||||
'advance_settings_url': reverse_course_url('advanced_settings_handler', course_block.id),
|
||||
'proctoring_errors': proctoring_errors,
|
||||
})
|
||||
|
||||
@@ -1068,17 +1068,17 @@ def course_info_handler(request, course_key_string):
|
||||
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
||||
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user)
|
||||
if not course_module:
|
||||
course_block = get_course_and_check_access(course_key, request.user)
|
||||
if not course_block:
|
||||
raise Http404
|
||||
if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
|
||||
return render_to_response(
|
||||
'course_info.html',
|
||||
{
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'updates_url': reverse_course_url('course_info_update_handler', course_key),
|
||||
'handouts_locator': course_key.make_usage_key('course_info', 'handouts'),
|
||||
'base_asset_url': StaticContent.get_base_url_path_for_course_assets(course_module.id),
|
||||
'base_asset_url': StaticContent.get_base_url_path_for_course_assets(course_block.id),
|
||||
}
|
||||
)
|
||||
else:
|
||||
@@ -1153,24 +1153,24 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
credit_eligibility_enabled = settings.FEATURES.get('ENABLE_CREDIT_ELIGIBILITY', False)
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user)
|
||||
course_block = get_course_and_check_access(course_key, request.user)
|
||||
if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
|
||||
upload_asset_url = reverse_course_url('assets_handler', course_key)
|
||||
|
||||
# see if the ORG of this course can be attributed to a defined configuration . In that case, the
|
||||
# course about page should be editable in Studio
|
||||
publisher_enabled = configuration_helpers.get_value_for_org(
|
||||
course_module.location.org,
|
||||
course_block.location.org,
|
||||
'ENABLE_PUBLISHER',
|
||||
settings.FEATURES.get('ENABLE_PUBLISHER', False)
|
||||
)
|
||||
marketing_enabled = configuration_helpers.get_value_for_org(
|
||||
course_module.location.org,
|
||||
course_block.location.org,
|
||||
'ENABLE_MKTG_SITE',
|
||||
settings.FEATURES.get('ENABLE_MKTG_SITE', False)
|
||||
)
|
||||
enable_extended_course_details = configuration_helpers.get_value_for_org(
|
||||
course_module.location.org,
|
||||
course_block.location.org,
|
||||
'ENABLE_EXTENDED_COURSE_DETAILS',
|
||||
settings.FEATURES.get('ENABLE_EXTENDED_COURSE_DETAILS', False)
|
||||
)
|
||||
@@ -1178,7 +1178,7 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab
|
||||
about_page_editable = not publisher_enabled
|
||||
enrollment_end_editable = GlobalStaff().has_user(request.user) or not publisher_enabled
|
||||
short_description_editable = configuration_helpers.get_value_for_org(
|
||||
course_module.location.org,
|
||||
course_block.location.org,
|
||||
'EDITABLE_SHORT_DESCRIPTION',
|
||||
settings.FEATURES.get('EDITABLE_SHORT_DESCRIPTION', True)
|
||||
)
|
||||
@@ -1188,12 +1188,12 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab
|
||||
upgrade_deadline = (verified_mode and verified_mode.expiration_datetime and
|
||||
verified_mode.expiration_datetime.isoformat())
|
||||
settings_context = {
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'course_locator': course_key,
|
||||
'lms_link_for_about_page': get_link_for_about_page(course_module),
|
||||
'course_image_url': course_image_url(course_module, 'course_image'),
|
||||
'banner_image_url': course_image_url(course_module, 'banner_image'),
|
||||
'video_thumbnail_image_url': course_image_url(course_module, 'video_thumbnail_image'),
|
||||
'lms_link_for_about_page': get_link_for_about_page(course_block),
|
||||
'course_image_url': course_image_url(course_block, 'course_image'),
|
||||
'banner_image_url': course_image_url(course_block, 'banner_image'),
|
||||
'video_thumbnail_image_url': course_image_url(course_block, 'video_thumbnail_image'),
|
||||
'details_url': reverse_course_url('settings_handler', course_key),
|
||||
'about_page_editable': about_page_editable,
|
||||
'marketing_enabled': marketing_enabled,
|
||||
@@ -1210,7 +1210,7 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab
|
||||
'is_entrance_exams_enabled': core_toggles.ENTRANCE_EXAMS.is_enabled(),
|
||||
'enable_extended_course_details': enable_extended_course_details,
|
||||
'upgrade_deadline': upgrade_deadline,
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id),
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
|
||||
}
|
||||
if is_prerequisite_courses_enabled():
|
||||
courses, in_process_course_actions = get_courses_accessible_to_user(request)
|
||||
@@ -1232,7 +1232,7 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab
|
||||
|
||||
# if 'minimum_grade_credit' of a course is not set or 0 then
|
||||
# show warning message to course author.
|
||||
show_min_grade_warning = False if course_module.minimum_grade_credit > 0 else True # lint-amnesty, pylint: disable=simplifiable-if-expression
|
||||
show_min_grade_warning = False if course_block.minimum_grade_credit > 0 else True # lint-amnesty, pylint: disable=simplifiable-if-expression
|
||||
settings_context.update(
|
||||
{
|
||||
'is_credit_course': True,
|
||||
@@ -1278,7 +1278,7 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab
|
||||
# We have to be careful that we're only executing the following logic if we actually
|
||||
# need to create or delete an entrance exam from the specified course
|
||||
if core_toggles.ENTRANCE_EXAMS.is_enabled():
|
||||
course_entrance_exam_present = course_module.entrance_exam_enabled
|
||||
course_entrance_exam_present = course_block.entrance_exam_enabled
|
||||
entrance_exam_enabled = request.json.get('entrance_exam_enabled', '') == 'true'
|
||||
ee_min_score_pct = request.json.get('entrance_exam_minimum_score_pct', None)
|
||||
# If the entrance exam box on the settings screen has been checked...
|
||||
@@ -1328,17 +1328,17 @@ def grading_handler(request, course_key_string, grader_index=None):
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user)
|
||||
course_block = get_course_and_check_access(course_key, request.user)
|
||||
|
||||
if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
|
||||
course_details = CourseGradingModel.fetch(course_key)
|
||||
return render_to_response('settings_graders.html', {
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'course_locator': course_key,
|
||||
'course_details': course_details,
|
||||
'grading_url': reverse_course_url('grading_handler', course_key),
|
||||
'is_credit_course': is_credit_course(course_key),
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id),
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
|
||||
})
|
||||
elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
|
||||
if request.method == 'GET':
|
||||
@@ -1371,7 +1371,7 @@ def grading_handler(request, course_key_string, grader_index=None):
|
||||
return JsonResponse()
|
||||
|
||||
|
||||
def _refresh_course_tabs(user: User, course_module: CourseBlock):
|
||||
def _refresh_course_tabs(user: User, course_block: CourseBlock):
|
||||
"""
|
||||
Automatically adds/removes tabs if changes to the course require them.
|
||||
|
||||
@@ -1392,19 +1392,19 @@ def _refresh_course_tabs(user: User, course_module: CourseBlock):
|
||||
elif not tab_enabled and has_tab:
|
||||
tabs.remove(tab_panel)
|
||||
|
||||
course_tabs = copy.copy(course_module.tabs)
|
||||
course_tabs = copy.copy(course_block.tabs)
|
||||
|
||||
# Additionally update any tabs that are provided by non-dynamic course views
|
||||
for tab_type in CourseTabPluginManager.get_tab_types():
|
||||
if not tab_type.is_dynamic and tab_type.is_default:
|
||||
tab_enabled = tab_type.is_enabled(course_module, user=user)
|
||||
tab_enabled = tab_type.is_enabled(course_block, user=user)
|
||||
update_tab(course_tabs, tab_type, tab_enabled)
|
||||
|
||||
CourseTabList.validate_tabs(course_tabs)
|
||||
|
||||
# Save the tabs into the course if they have been changed
|
||||
if course_tabs != course_module.tabs:
|
||||
course_module.tabs = course_tabs
|
||||
if course_tabs != course_block.tabs:
|
||||
course_block.tabs = course_tabs
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -1423,42 +1423,42 @@ def advanced_settings_handler(request, course_key_string):
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user)
|
||||
course_block = get_course_and_check_access(course_key, request.user)
|
||||
|
||||
advanced_dict = CourseMetadata.fetch(course_module)
|
||||
advanced_dict = CourseMetadata.fetch(course_block)
|
||||
if settings.FEATURES.get('DISABLE_MOBILE_COURSE_AVAILABLE', False):
|
||||
advanced_dict.get('mobile_available')['deprecated'] = True
|
||||
|
||||
if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
|
||||
publisher_enabled = configuration_helpers.get_value_for_org(
|
||||
course_module.location.org,
|
||||
course_block.location.org,
|
||||
'ENABLE_PUBLISHER',
|
||||
settings.FEATURES.get('ENABLE_PUBLISHER', False)
|
||||
)
|
||||
# gather any errors in the currently stored proctoring settings.
|
||||
proctoring_errors = CourseMetadata.validate_proctoring_settings(course_module, advanced_dict, request.user)
|
||||
proctoring_errors = CourseMetadata.validate_proctoring_settings(course_block, advanced_dict, request.user)
|
||||
|
||||
return render_to_response('settings_advanced.html', {
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'advanced_dict': advanced_dict,
|
||||
'advanced_settings_url': reverse_course_url('advanced_settings_handler', course_key),
|
||||
'publisher_enabled': publisher_enabled,
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_module.id),
|
||||
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
|
||||
'proctoring_errors': proctoring_errors,
|
||||
})
|
||||
elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
|
||||
if request.method == 'GET':
|
||||
return JsonResponse(CourseMetadata.fetch(course_module))
|
||||
return JsonResponse(CourseMetadata.fetch(course_block))
|
||||
else:
|
||||
try:
|
||||
return JsonResponse(
|
||||
update_course_advanced_settings(course_module, request.json, request.user)
|
||||
update_course_advanced_settings(course_block, request.json, request.user)
|
||||
)
|
||||
except ValidationError as err:
|
||||
return JsonResponseBadRequest(err.detail)
|
||||
|
||||
|
||||
def update_course_advanced_settings(course_module: CourseBlock, data: Dict, user: User) -> Dict:
|
||||
def update_course_advanced_settings(course_block: CourseBlock, data: Dict, user: User) -> Dict:
|
||||
"""
|
||||
Helper function to update course advanced settings from API data.
|
||||
|
||||
@@ -1466,7 +1466,7 @@ def update_course_advanced_settings(course_module: CourseBlock, data: Dict, user
|
||||
it to the course advanced settings.
|
||||
|
||||
Args:
|
||||
course_module (CourseBlock): The course run object on which to operate.
|
||||
course_block (CourseBlock): The course run object on which to operate.
|
||||
data (Dict): JSON data as found the ``request.data``
|
||||
user (User): The user performing the operation
|
||||
|
||||
@@ -1474,10 +1474,10 @@ def update_course_advanced_settings(course_module: CourseBlock, data: Dict, user
|
||||
Dict: The updated data after applying changes based on supplied data.
|
||||
"""
|
||||
try:
|
||||
# validate data formats and update the course module.
|
||||
# validate data formats and update the course block.
|
||||
# Note: don't update mongo yet, but wait until after any tabs are changed
|
||||
is_valid, errors, updated_data = CourseMetadata.validate_and_update_from_json(
|
||||
course_module,
|
||||
course_block,
|
||||
data,
|
||||
user=user,
|
||||
)
|
||||
@@ -1487,7 +1487,7 @@ def update_course_advanced_settings(course_module: CourseBlock, data: Dict, user
|
||||
|
||||
try:
|
||||
# update the course tabs if required by any setting changes
|
||||
_refresh_course_tabs(user, course_module)
|
||||
_refresh_course_tabs(user, course_block)
|
||||
except InvalidTabsException as err:
|
||||
log.exception(str(err))
|
||||
response_message = [
|
||||
@@ -1499,7 +1499,7 @@ def update_course_advanced_settings(course_module: CourseBlock, data: Dict, user
|
||||
raise ValidationError(response_message) from err
|
||||
|
||||
# now update mongo
|
||||
modulestore().update_item(course_module, user.id)
|
||||
modulestore().update_item(course_block, user.id)
|
||||
|
||||
return updated_data
|
||||
|
||||
@@ -1665,8 +1665,8 @@ def textbooks_detail_handler(request, course_key_string, textbook_id):
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
store = modulestore()
|
||||
with store.bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user)
|
||||
matching_id = [tb for tb in course_module.pdf_textbooks
|
||||
course_block = get_course_and_check_access(course_key, request.user)
|
||||
matching_id = [tb for tb in course_block.pdf_textbooks
|
||||
if str(tb.get("id")) == str(textbook_id)]
|
||||
if matching_id:
|
||||
textbook = matching_id[0]
|
||||
@@ -1684,23 +1684,23 @@ def textbooks_detail_handler(request, course_key_string, textbook_id):
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
new_textbook["id"] = textbook_id
|
||||
if textbook:
|
||||
i = course_module.pdf_textbooks.index(textbook)
|
||||
new_textbooks = course_module.pdf_textbooks[0:i]
|
||||
i = course_block.pdf_textbooks.index(textbook)
|
||||
new_textbooks = course_block.pdf_textbooks[0:i]
|
||||
new_textbooks.append(new_textbook)
|
||||
new_textbooks.extend(course_module.pdf_textbooks[i + 1:])
|
||||
course_module.pdf_textbooks = new_textbooks
|
||||
new_textbooks.extend(course_block.pdf_textbooks[i + 1:])
|
||||
course_block.pdf_textbooks = new_textbooks
|
||||
else:
|
||||
course_module.pdf_textbooks.append(new_textbook)
|
||||
store.update_item(course_module, request.user.id)
|
||||
course_block.pdf_textbooks.append(new_textbook)
|
||||
store.update_item(course_block, request.user.id)
|
||||
return JsonResponse(new_textbook, status=201)
|
||||
elif request.method == 'DELETE':
|
||||
if not textbook:
|
||||
return JsonResponse(status=404)
|
||||
i = course_module.pdf_textbooks.index(textbook)
|
||||
remaining_textbooks = course_module.pdf_textbooks[0:i]
|
||||
remaining_textbooks.extend(course_module.pdf_textbooks[i + 1:])
|
||||
course_module.pdf_textbooks = remaining_textbooks
|
||||
store.update_item(course_module, request.user.id)
|
||||
i = course_block.pdf_textbooks.index(textbook)
|
||||
remaining_textbooks = course_block.pdf_textbooks[0:i]
|
||||
remaining_textbooks.extend(course_block.pdf_textbooks[i + 1:])
|
||||
course_block.pdf_textbooks = remaining_textbooks
|
||||
store.update_item(course_block, request.user.id)
|
||||
return JsonResponse()
|
||||
|
||||
|
||||
|
||||
@@ -30,18 +30,18 @@ def export_git(request, course_key_string):
|
||||
if not has_course_author_access(request.user, course_key):
|
||||
raise PermissionDenied()
|
||||
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
failed = False
|
||||
|
||||
log.debug('export_git course_module=%s', course_module)
|
||||
log.debug('export_git course_block=%s', course_block)
|
||||
|
||||
msg = ""
|
||||
if 'action' in request.GET and course_module.giturl:
|
||||
if 'action' in request.GET and course_block.giturl:
|
||||
if request.GET['action'] == 'push':
|
||||
try:
|
||||
git_export_utils.export_to_git(
|
||||
course_module.id,
|
||||
course_module.giturl,
|
||||
course_block.id,
|
||||
course_block.giturl,
|
||||
request.user,
|
||||
)
|
||||
msg = _('Course successfully exported to git repository')
|
||||
@@ -50,7 +50,7 @@ def export_git(request, course_key_string):
|
||||
msg = str(ex)
|
||||
|
||||
return render_to_response('export_git.html', {
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'msg': msg,
|
||||
'failed': failed,
|
||||
})
|
||||
|
||||
@@ -254,7 +254,7 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None
|
||||
user
|
||||
)
|
||||
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course block, so
|
||||
# if we add one then we need to also add it to the policy information (i.e. metadata)
|
||||
# we should remove this once we can break this reference from the course to static tabs
|
||||
if category == 'static_tab':
|
||||
|
||||
@@ -979,7 +979,7 @@ def _delete_item(usage_key, user):
|
||||
store = modulestore()
|
||||
|
||||
with store.bulk_operations(usage_key.course_key):
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course block, so
|
||||
# if we add one then we need to also add it to the policy information (i.e. metadata)
|
||||
# we should remove this once we can break this reference from the course to static tabs
|
||||
if usage_key.block_type == 'static_tab':
|
||||
@@ -1105,7 +1105,7 @@ def _get_gating_info(course, xblock):
|
||||
info = {}
|
||||
if xblock.category == 'sequential' and course.enable_subsection_gating:
|
||||
if not hasattr(course, 'gating_prerequisites'):
|
||||
# Cache gating prerequisites on course module so that we are not
|
||||
# Cache gating prerequisites on course block so that we are not
|
||||
# hitting the database for every xblock in the course
|
||||
course.gating_prerequisites = gating_api.get_prerequisites(course.id)
|
||||
info["is_prereq"] = gating_api.is_prerequisite(course.id, xblock.location)
|
||||
|
||||
@@ -106,7 +106,7 @@ def update_tabs_handler(course_item: CourseBlock, tabs_data: Dict, user: User) -
|
||||
Helper to handle updates to course tabs based on API data.
|
||||
|
||||
Args:
|
||||
course_item (CourseBlock): Course module whose tabs need to be updated
|
||||
course_item (CourseBlock): Course block whose tabs need to be updated
|
||||
tabs_data (Dict): JSON formatted data for updating or reordering tabs.
|
||||
user (User): The user performing the operation.
|
||||
"""
|
||||
|
||||
@@ -525,9 +525,9 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assert_correct_json_response(child_response, is_concise)
|
||||
|
||||
def test_course_outline_initial_state(self):
|
||||
course_module = modulestore().get_item(self.course.location)
|
||||
course_block = modulestore().get_item(self.course.location)
|
||||
course_structure = create_xblock_info(
|
||||
course_module,
|
||||
course_block,
|
||||
include_child_info=True,
|
||||
include_children_predicate=lambda xblock: not xblock.category == 'vertical'
|
||||
)
|
||||
@@ -543,7 +543,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assertIn(str(self.sequential.location), expanded_locators)
|
||||
self.assertIn(str(self.vertical.location), expanded_locators)
|
||||
|
||||
def _create_test_data(self, course_module, create_blocks=False, publish=True, block_types=None):
|
||||
def _create_test_data(self, course_block, create_blocks=False, publish=True, block_types=None):
|
||||
"""
|
||||
Create data for test.
|
||||
"""
|
||||
@@ -558,7 +558,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
if not publish:
|
||||
self.store.unpublish(self.vertical.location, self.user.id)
|
||||
|
||||
course_module.advanced_modules.extend(block_types)
|
||||
course_block.advanced_modules.extend(block_types)
|
||||
|
||||
def _verify_deprecated_info(self, course_id, advanced_modules, info, deprecated_block_types):
|
||||
"""
|
||||
@@ -594,12 +594,12 @@ class TestCourseOutline(CourseTestCase):
|
||||
"""
|
||||
Verify deprecated warning info.
|
||||
"""
|
||||
course_module = modulestore().get_item(self.course.location)
|
||||
self._create_test_data(course_module, create_blocks=True, block_types=block_types, publish=publish)
|
||||
info = _deprecated_blocks_info(course_module, block_types)
|
||||
course_block = modulestore().get_item(self.course.location)
|
||||
self._create_test_data(course_block, create_blocks=True, block_types=block_types, publish=publish)
|
||||
info = _deprecated_blocks_info(course_block, block_types)
|
||||
self._verify_deprecated_info(
|
||||
course_module.id,
|
||||
course_module.advanced_modules,
|
||||
course_block.id,
|
||||
course_block.advanced_modules,
|
||||
info,
|
||||
block_types
|
||||
)
|
||||
@@ -611,17 +611,17 @@ class TestCourseOutline(CourseTestCase):
|
||||
(["a", "b", "c"], ["d", "e", "f"])
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_verify_warn_only_on_enabled_modules(self, enabled_block_types, deprecated_block_types):
|
||||
def test_verify_warn_only_on_enabled_blocks(self, enabled_block_types, deprecated_block_types):
|
||||
"""
|
||||
Verify that we only warn about block_types that are both deprecated and enabled.
|
||||
"""
|
||||
expected_block_types = list(set(enabled_block_types) & set(deprecated_block_types))
|
||||
course_module = modulestore().get_item(self.course.location)
|
||||
self._create_test_data(course_module, create_blocks=True, block_types=enabled_block_types)
|
||||
info = _deprecated_blocks_info(course_module, deprecated_block_types)
|
||||
course_block = modulestore().get_item(self.course.location)
|
||||
self._create_test_data(course_block, create_blocks=True, block_types=enabled_block_types)
|
||||
info = _deprecated_blocks_info(course_block, deprecated_block_types)
|
||||
self._verify_deprecated_info(
|
||||
course_module.id,
|
||||
course_module.advanced_modules,
|
||||
course_block.id,
|
||||
course_block.advanced_modules,
|
||||
info,
|
||||
expected_block_types
|
||||
)
|
||||
|
||||
@@ -79,7 +79,7 @@ def _manage_users(request, course_key):
|
||||
if not user_perms & STUDIO_VIEW_USERS:
|
||||
raise PermissionDenied()
|
||||
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
instructors = set(CourseInstructorRole(course_key).users_with_role())
|
||||
# the page only lists staff and assumes they're a superset of instructors. Do a union to ensure.
|
||||
staff = set(CourseStaffRole(course_key).users_with_role()).union(instructors)
|
||||
@@ -91,7 +91,7 @@ def _manage_users(request, course_key):
|
||||
formatted_users.append(user_with_role(user, 'staff'))
|
||||
|
||||
return render_to_response('manage_users.html', {
|
||||
'context_course': course_module,
|
||||
'context_course': course_block,
|
||||
'show_transfer_ownership_hint': request.user in instructors and len(instructors) == 1,
|
||||
'users': formatted_users,
|
||||
'allow_actions': bool(user_perms & STUDIO_EDIT_ROLES),
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestDumpToNeo4jCommandBase(SharedModuleStoreTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
r"""
|
||||
Creates two courses; one that's just a course module, and one that
|
||||
Creates two courses; one that's just a course block, and one that
|
||||
looks like:
|
||||
course
|
||||
|
|
||||
|
||||
@@ -78,7 +78,7 @@ def replace_course_urls(text, course_key):
|
||||
Replace /course/$stuff urls with /courses/$course_id/$stuff urls
|
||||
|
||||
text: The text to replace
|
||||
course_module: A CourseBlock
|
||||
course_key: A CourseBlock
|
||||
|
||||
returns: text with the links replaced
|
||||
"""
|
||||
|
||||
@@ -51,11 +51,11 @@ def generate_proctoring_requirements_email_context(user, course_id):
|
||||
user: Currently logged-in user
|
||||
course_id: ID of the proctoring-enabled course the user is enrolled in
|
||||
"""
|
||||
course_module = modulestore().get_course(course_id)
|
||||
course_block = modulestore().get_course(course_id)
|
||||
return {
|
||||
'user': user,
|
||||
'course_name': course_module.display_name,
|
||||
'proctoring_provider': capwords(course_module.proctoring_provider.replace('_', ' ')),
|
||||
'course_name': course_block.display_name,
|
||||
'proctoring_provider': capwords(course_block.proctoring_provider.replace('_', ' ')),
|
||||
'proctoring_requirements_url': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('faq', ''),
|
||||
'idv_required': not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'),
|
||||
'id_verification_url': IDVerificationService.get_verify_location(),
|
||||
|
||||
@@ -284,8 +284,8 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas
|
||||
"""
|
||||
Provide a tuple of string[]s that should be (in, not_in) the email
|
||||
"""
|
||||
course_module = modulestore().get_course(self.course.id)
|
||||
proctoring_provider = capwords(course_module.proctoring_provider.replace('_', ' '))
|
||||
course_block = modulestore().get_course(self.course.id)
|
||||
proctoring_provider = capwords(course_block.proctoring_provider.replace('_', ' '))
|
||||
fragments = [
|
||||
(
|
||||
"You are enrolled in {} at {}. This course contains proctored exams.".format(
|
||||
|
||||
@@ -49,6 +49,6 @@ class CCXCourseSerializer(serializers.ModelSerializer):
|
||||
@staticmethod
|
||||
def get_course_modules(obj):
|
||||
"""
|
||||
Getter for the Course Modules. The list is stored in a compressed field.
|
||||
Getter for the Course Blocks. The list is stored in a compressed field.
|
||||
"""
|
||||
return obj.structure or []
|
||||
|
||||
@@ -108,7 +108,7 @@ def has_access(user, action, obj, course_key=None):
|
||||
- visible_to_staff_only for modules
|
||||
- DISABLE_START_DATES
|
||||
- different access for instructor, staff, course staff, and students.
|
||||
- mobile_available flag for course modules
|
||||
- mobile_available flag for course blocks
|
||||
|
||||
user: a Django user object. May be anonymous. If none is passed,
|
||||
anonymous is assumed
|
||||
|
||||
@@ -152,17 +152,17 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_
|
||||
NOTE: assumes that if we got this far, user has access to course. Returns
|
||||
None if this is not the case.
|
||||
|
||||
field_data_cache must include data from the course module and 2 levels of its descendants
|
||||
field_data_cache must include data from the course blocks and 2 levels of its descendants
|
||||
'''
|
||||
with modulestore().bulk_operations(course.id):
|
||||
course_module = get_module_for_descriptor(
|
||||
course_block = get_module_for_descriptor(
|
||||
user, request, course, field_data_cache, course.id, course=course
|
||||
)
|
||||
if course_module is None:
|
||||
if course_block is None:
|
||||
return None, None, None
|
||||
|
||||
toc_chapters = []
|
||||
chapters = course_module.get_display_items()
|
||||
chapters = course_block.get_display_items()
|
||||
|
||||
# Check for content which needs to be completed
|
||||
# before the rest of the content is made available
|
||||
|
||||
@@ -42,14 +42,14 @@ class ProgressCourseApp(CourseApp):
|
||||
@classmethod
|
||||
def is_enabled(cls, course_key: CourseKey) -> bool:
|
||||
"""
|
||||
The progress course status is stored in the course module.
|
||||
The progress course status is stored in the course block.
|
||||
"""
|
||||
return not CourseOverview.get_from_id(course_key).hide_progress_tab
|
||||
|
||||
@classmethod
|
||||
def set_enabled(cls, course_key: CourseKey, enabled: bool, user: 'User') -> bool:
|
||||
"""
|
||||
The progress course enabled/disabled status is stored in the course module.
|
||||
The progress course enabled/disabled status is stored in the course block.
|
||||
"""
|
||||
course = get_course_by_id(course_key)
|
||||
course.hide_progress_tab = not enabled
|
||||
|
||||
@@ -103,7 +103,7 @@ class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTra
|
||||
@override_settings(COURSE_ABOUT_VISIBILITY_PERMISSION="see_about_page")
|
||||
def test_visible_about_page_settings(self):
|
||||
"""
|
||||
Verify that the About Page honors the permission settings in the course module
|
||||
Verify that the About Page honors the permission settings in the course block
|
||||
"""
|
||||
url = reverse('about_course', args=[str(self.course_with_about.id)])
|
||||
resp = self.client.get(url)
|
||||
|
||||
@@ -367,7 +367,7 @@ class CourseInstantiationTests(ModuleStoreTestCase):
|
||||
|
||||
@ddt.data(*itertools.product(range(5), [None, 0, 5]))
|
||||
@ddt.unpack
|
||||
def test_repeated_course_module_instantiation(self, loops, course_depth):
|
||||
def test_repeated_course_block_instantiation(self, loops, course_depth):
|
||||
|
||||
with modulestore().default_store(ModuleStoreEnum.Type.split):
|
||||
course = CourseFactory.create()
|
||||
@@ -385,7 +385,7 @@ class CourseInstantiationTests(ModuleStoreTestCase):
|
||||
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
|
||||
course.id, self.user, course, depth=course_depth
|
||||
)
|
||||
course_module = get_module_for_descriptor(
|
||||
course_block = get_module_for_descriptor(
|
||||
self.user,
|
||||
fake_request,
|
||||
course,
|
||||
@@ -393,7 +393,7 @@ class CourseInstantiationTests(ModuleStoreTestCase):
|
||||
course.id,
|
||||
course=course
|
||||
)
|
||||
for chapter in course_module.get_children():
|
||||
for chapter in course_block.get_children():
|
||||
for section in chapter.get_children():
|
||||
for item in section.get_children():
|
||||
assert item.graded
|
||||
|
||||
@@ -312,7 +312,7 @@ class SplitTestPosition(SharedModuleStoreTestCase):
|
||||
self.client.login(username=self.student.username, password='test')
|
||||
|
||||
def test_changing_position_works(self):
|
||||
# Make a mock FieldDataCache for this course, so we can get the course module
|
||||
# Make a mock FieldDataCache for this course, so we can get the course block
|
||||
mock_field_data_cache = FieldDataCache([self.course], self.course.id, self.student)
|
||||
course = get_module_for_descriptor(
|
||||
self.student,
|
||||
|
||||
@@ -1109,7 +1109,7 @@ class BaseDueDateTests(ModuleStoreTestCase):
|
||||
def test_backwards_compatibility(self):
|
||||
# The test course being used has show_timezone = False in the policy file
|
||||
# (and no due_date_display_format set). This is to test our backwards compatibility--
|
||||
# in course_module's init method, the date_display_format will be set accordingly to
|
||||
# in course_block's init method, the date_display_format will be set accordingly to
|
||||
# remove the timezone.
|
||||
course = self.set_up_course(due_date_display_format=None, show_timezone=False)
|
||||
response = self.get_response(course)
|
||||
|
||||
@@ -1262,7 +1262,7 @@ def get_static_tab_fragment(request, course, tab):
|
||||
request.user, request, loc, field_data_cache, static_asset_path=course.static_asset_path, course=course
|
||||
)
|
||||
|
||||
logging.debug('course_module = %s', tab_module)
|
||||
logging.debug('course_block = %s', tab_module)
|
||||
|
||||
fragment = Fragment()
|
||||
if tab_module is not None:
|
||||
|
||||
@@ -408,7 +408,7 @@ def get_internal_endpoint(path=""):
|
||||
return get_endpoint(settings.EDXNOTES_INTERNAL_API, path)
|
||||
|
||||
|
||||
def get_course_position(course_module):
|
||||
def get_course_position(course_block):
|
||||
"""
|
||||
Return the user's current place in the course.
|
||||
|
||||
@@ -418,14 +418,14 @@ def get_course_position(course_module):
|
||||
If there is no current position in the course or chapter, then selects
|
||||
the first child.
|
||||
"""
|
||||
urlargs = {'course_id': str(course_module.id)}
|
||||
chapter = get_current_child(course_module, min_depth=1)
|
||||
urlargs = {'course_id': str(course_block.id)}
|
||||
chapter = get_current_child(course_block, min_depth=1)
|
||||
if chapter is None:
|
||||
log.debug("No chapter found when loading current position in course")
|
||||
return None
|
||||
|
||||
urlargs['chapter'] = chapter.url_name
|
||||
if course_module.position is not None:
|
||||
if course_block.position is not None:
|
||||
return {
|
||||
'display_name': Text(chapter.display_name_with_default),
|
||||
'url': reverse('courseware_chapter', kwargs=urlargs),
|
||||
|
||||
@@ -816,25 +816,25 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Returns `None` if no chapter found.
|
||||
"""
|
||||
mock_course_module = MagicMock()
|
||||
mock_course_module.position = 3
|
||||
mock_course_module.get_display_items.return_value = []
|
||||
assert helpers.get_course_position(mock_course_module) is None
|
||||
mock_course_block = MagicMock()
|
||||
mock_course_block.position = 3
|
||||
mock_course_block.get_display_items.return_value = []
|
||||
assert helpers.get_course_position(mock_course_block) is None
|
||||
|
||||
def test_get_course_position_to_chapter(self):
|
||||
"""
|
||||
Returns a position that leads to COURSE/CHAPTER if this isn't the users's
|
||||
first time.
|
||||
"""
|
||||
mock_course_module = MagicMock(id=self.course.id, position=3)
|
||||
mock_course_block = MagicMock(id=self.course.id, position=3)
|
||||
|
||||
mock_chapter = MagicMock()
|
||||
mock_chapter.url_name = 'chapter_url_name'
|
||||
mock_chapter.display_name_with_default = 'Test Chapter Display Name'
|
||||
|
||||
mock_course_module.get_display_items.return_value = [mock_chapter]
|
||||
mock_course_block.get_display_items.return_value = [mock_chapter]
|
||||
|
||||
assert helpers.get_course_position(mock_course_module) == {
|
||||
assert helpers.get_course_position(mock_course_block) == {
|
||||
'display_name': 'Test Chapter Display Name',
|
||||
'url': f'/courses/{self.course.id}/courseware/chapter_url_name/',
|
||||
}
|
||||
@@ -843,20 +843,20 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Returns `None` if no section found.
|
||||
"""
|
||||
mock_course_module = MagicMock(id=self.course.id, position=None)
|
||||
mock_course_module.get_display_items.return_value = [MagicMock()]
|
||||
assert helpers.get_course_position(mock_course_module) is None
|
||||
mock_course_block = MagicMock(id=self.course.id, position=None)
|
||||
mock_course_block.get_display_items.return_value = [MagicMock()]
|
||||
assert helpers.get_course_position(mock_course_block) is None
|
||||
|
||||
def test_get_course_position_to_section(self):
|
||||
"""
|
||||
Returns a position that leads to COURSE/CHAPTER/SECTION if this is the
|
||||
user's first time.
|
||||
"""
|
||||
mock_course_module = MagicMock(id=self.course.id, position=None)
|
||||
mock_course_block = MagicMock(id=self.course.id, position=None)
|
||||
|
||||
mock_chapter = MagicMock()
|
||||
mock_chapter.url_name = 'chapter_url_name'
|
||||
mock_course_module.get_display_items.return_value = [mock_chapter]
|
||||
mock_course_block.get_display_items.return_value = [mock_chapter]
|
||||
|
||||
mock_section = MagicMock()
|
||||
mock_section.url_name = 'section_url_name'
|
||||
@@ -865,7 +865,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
|
||||
mock_chapter.get_display_items.return_value = [mock_section]
|
||||
mock_section.get_display_items.return_value = [MagicMock()]
|
||||
|
||||
assert helpers.get_course_position(mock_course_module) == {
|
||||
assert helpers.get_course_position(mock_course_block) == {
|
||||
'display_name': 'Test Section Display Name',
|
||||
'url': f'/courses/{self.course.id}/courseware/chapter_url_name/section_url_name/',
|
||||
}
|
||||
@@ -956,9 +956,9 @@ class EdxNotesViewsTest(ModuleStoreTestCase):
|
||||
self.get_token_url = reverse("get_token", args=[str(self.course.id)]) # lint-amnesty, pylint: disable=no-member
|
||||
self.visibility_url = reverse("edxnotes_visibility", args=[str(self.course.id)]) # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def _get_course_module(self):
|
||||
def _get_course_block(self):
|
||||
"""
|
||||
Returns the course module.
|
||||
Returns the course block.
|
||||
"""
|
||||
field_data_cache = FieldDataCache([self.course], self.course.id, self.user) # lint-amnesty, pylint: disable=no-member
|
||||
return get_module_for_descriptor(
|
||||
@@ -1099,8 +1099,8 @@ class EdxNotesViewsTest(ModuleStoreTestCase):
|
||||
content_type="application/json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
course_module = self._get_course_module()
|
||||
assert not course_module.edxnotes_visibility
|
||||
course_block = self._get_course_block()
|
||||
assert not course_block.edxnotes_visibility
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_EDXNOTES": False})
|
||||
def test_edxnotes_visibility_if_feature_is_disabled(self):
|
||||
|
||||
@@ -74,10 +74,10 @@ def edxnotes(request, course_id):
|
||||
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
|
||||
course.id, request.user, course, depth=2
|
||||
)
|
||||
course_module = get_module_for_descriptor(
|
||||
course_block = get_module_for_descriptor(
|
||||
request.user, request, course, field_data_cache, course_key, course=course
|
||||
)
|
||||
position = get_course_position(course_module)
|
||||
position = get_course_position(course_block)
|
||||
if position:
|
||||
context.update({
|
||||
'position': position,
|
||||
@@ -195,7 +195,7 @@ def edxnotes_visibility(request, course_id):
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(request.user, "load", course_key)
|
||||
field_data_cache = FieldDataCache([course], course_key, request.user)
|
||||
course_module = get_module_for_descriptor(
|
||||
course_block = get_module_for_descriptor(
|
||||
request.user, request, course, field_data_cache, course_key, course=course
|
||||
)
|
||||
|
||||
@@ -204,8 +204,8 @@ def edxnotes_visibility(request, course_id):
|
||||
|
||||
try:
|
||||
visibility = json.loads(request.body.decode('utf8'))["visibility"]
|
||||
course_module.edxnotes_visibility = visibility
|
||||
course_module.save()
|
||||
course_block.edxnotes_visibility = visibility
|
||||
course_block.save()
|
||||
return JsonResponse(status=200)
|
||||
except (ValueError, KeyError):
|
||||
log.warning(
|
||||
|
||||
@@ -57,7 +57,7 @@ def update_exam_completion_task(user_identifier: str, content_id: str, completio
|
||||
return
|
||||
|
||||
# This logic has been copied over from openedx/core/djangoapps/schedules/content_highlights.py
|
||||
# in the _get_course_module function.
|
||||
# in the _get_course_block function.
|
||||
# I'm not sure if this is an anti-pattern or not, so if you can avoid re-copying this, please do.
|
||||
# We are using it here because we ran into issues with the User service being undefined when we
|
||||
# encountered a split_test xblock.
|
||||
|
||||
@@ -117,7 +117,7 @@ class UserCourseStatus(views.APIView):
|
||||
* last_visited_module_id: The ID of the last module that the user
|
||||
visited in the course.
|
||||
* last_visited_module_path: The ID of the modules in the path from the
|
||||
last visited module to the course module.
|
||||
last visited module to the course block.
|
||||
|
||||
For version v1 GET request response includes the following values.
|
||||
|
||||
@@ -137,18 +137,18 @@ class UserCourseStatus(views.APIView):
|
||||
def _last_visited_module_path(self, request, course):
|
||||
"""
|
||||
Returns the path from the last module visited by the current user in the given course up to
|
||||
the course module. If there is no such visit, the first item deep enough down the course
|
||||
the course block. If there is no such visit, the first item deep enough down the course
|
||||
tree is used.
|
||||
"""
|
||||
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
|
||||
course.id, request.user, course, depth=2)
|
||||
|
||||
course_module = get_module_for_descriptor(
|
||||
course_block = get_module_for_descriptor(
|
||||
request.user, request, course, field_data_cache, course.id, course=course
|
||||
)
|
||||
|
||||
path = [course_module] if course_module else []
|
||||
chapter = get_current_child(course_module, min_depth=2)
|
||||
path = [course_block] if course_block else []
|
||||
chapter = get_current_child(course_block, min_depth=2)
|
||||
if chapter is not None:
|
||||
path.append(chapter)
|
||||
section = get_current_child(chapter, min_depth=1)
|
||||
|
||||
@@ -205,31 +205,31 @@ def has_specific_team_access(user, team):
|
||||
)
|
||||
|
||||
|
||||
def has_specific_teamset_access(user, course_module, teamset_id):
|
||||
def has_specific_teamset_access(user, course_block, teamset_id):
|
||||
"""
|
||||
Staff have access to all teamsets.
|
||||
All non-staff users have access to open and public_managed teamsets.
|
||||
Non-staff users only have access to a private_managed teamset if they are in a team in that teamset
|
||||
"""
|
||||
return has_course_staff_privileges(user, course_module.id) or \
|
||||
teamset_is_public_or_user_is_on_team_in_teamset(user, course_module, teamset_id)
|
||||
return has_course_staff_privileges(user, course_block.id) or \
|
||||
teamset_is_public_or_user_is_on_team_in_teamset(user, course_block, teamset_id)
|
||||
|
||||
|
||||
def teamset_is_public_or_user_is_on_team_in_teamset(user, course_module, teamset_id):
|
||||
def teamset_is_public_or_user_is_on_team_in_teamset(user, course_block, teamset_id):
|
||||
"""
|
||||
The only users who should be able to see private_managed teamsets
|
||||
or recieve any information about them at all from the API are:
|
||||
- Course staff
|
||||
- Users who are enrolled in a team in a private_managed teamset
|
||||
|
||||
course_module is passed in because almost universally where we'll be calling this, we will already
|
||||
course_block is passed in because almost universally where we'll be calling this, we will already
|
||||
need to have looked up the course from modulestore to make sure that the topic we're interested in
|
||||
exists in the course.
|
||||
"""
|
||||
teamset = course_module.teams_configuration.teamsets_by_id[teamset_id]
|
||||
teamset = course_block.teams_configuration.teamsets_by_id[teamset_id]
|
||||
if teamset.teamset_type != TeamsetType.private_managed:
|
||||
return True
|
||||
return CourseTeamMembership.user_in_team_for_teamset(user, course_module.id, topic_id=teamset_id)
|
||||
return CourseTeamMembership.user_in_team_for_teamset(user, course_block.id, topic_id=teamset_id)
|
||||
|
||||
|
||||
def user_on_team_or_team_is_public(user, team):
|
||||
@@ -242,8 +242,8 @@ def user_on_team_or_team_is_public(user, team):
|
||||
"""
|
||||
if CourseTeamMembership.is_user_on_team(user, team):
|
||||
return True
|
||||
course_module = modulestore().get_course(team.course_id)
|
||||
teamset = course_module.teams_configuration.teamsets_by_id[team.topic_id]
|
||||
course_block = modulestore().get_course(team.course_id)
|
||||
teamset = course_block.teams_configuration.teamsets_by_id[team.topic_id]
|
||||
return teamset.teamset_type != TeamsetType.private_managed
|
||||
|
||||
|
||||
@@ -289,8 +289,8 @@ def get_teams_accessible_by_user(user, topic_id_set, course_id, organization_pro
|
||||
return CourseTeam.objects.filter(**filter_query)
|
||||
|
||||
# Private teams should be hidden unless the student is a member
|
||||
course_module = modulestore().get_course(course_id)
|
||||
private_teamset_ids = [ts.teamset_id for ts in course_module.teamsets if ts.is_private_managed]
|
||||
course_block = modulestore().get_course(course_id)
|
||||
private_teamset_ids = [ts.teamset_id for ts in course_block.teamsets if ts.is_private_managed]
|
||||
return CourseTeam.objects.filter(**filter_query).exclude(
|
||||
Q(topic_id__in=private_teamset_ids), ~Q(membership__user=user)
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ def load_team_membership_csv(course, response):
|
||||
Load a CSV detailing course membership.
|
||||
|
||||
Arguments:
|
||||
course (CourseBlock): Course module for which CSV
|
||||
course (CourseBlock): Course block for which CSV
|
||||
download has been requested.
|
||||
response (HttpResponse): Django response object to which
|
||||
the CSV content will be written.
|
||||
|
||||
@@ -418,7 +418,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
course_id_string = request.query_params['course_id']
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_id_string)
|
||||
course_module = modulestore().get_course(course_key)
|
||||
course_block = modulestore().get_course(course_key)
|
||||
except InvalidKeyError:
|
||||
error = build_api_error(
|
||||
gettext_noop("The supplied course id {course_id} is not valid."),
|
||||
@@ -427,7 +427,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
return Response(error, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Ensure the course exists
|
||||
if course_module is None:
|
||||
if course_block is None:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
result_filter.update({'course_id': course_key})
|
||||
|
||||
@@ -446,7 +446,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
result_filter.update({'membership__user__username': username})
|
||||
topic_id = request.query_params.get('topic_id', None)
|
||||
if topic_id is not None:
|
||||
if topic_id not in course_module.teamsets_by_id:
|
||||
if topic_id not in course_block.teamsets_by_id:
|
||||
error = build_api_error(
|
||||
gettext_noop('The supplied topic id {topic_id} is not valid'),
|
||||
topic_id=topic_id
|
||||
@@ -485,7 +485,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
|
||||
# Non-staff users should not be able to see private_managed teams that they are not on.
|
||||
# Staff shouldn't have any excluded teams.
|
||||
excluded_private_team_ids = self._get_private_team_ids_to_exclude(course_module)
|
||||
excluded_private_team_ids = self._get_private_team_ids_to_exclude(course_block)
|
||||
|
||||
search_results['results'] = [
|
||||
result for result in search_results['results']
|
||||
@@ -516,7 +516,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
}
|
||||
|
||||
# hide private_managed courses from non-staff users that aren't members of those teams
|
||||
excluded_private_team_ids = self._get_private_team_ids_to_exclude(course_module)
|
||||
excluded_private_team_ids = self._get_private_team_ids_to_exclude(course_block)
|
||||
|
||||
queryset = CourseTeam.objects.filter(**result_filter).exclude(team_id__in=excluded_private_team_ids)
|
||||
order_by_input = request.query_params.get('order_by', 'name')
|
||||
@@ -552,8 +552,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
# Ensure the course exists
|
||||
course_module = modulestore().get_course(course_key)
|
||||
if not course_module:
|
||||
course_block = modulestore().get_course(course_key)
|
||||
if not course_block:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
except InvalidKeyError:
|
||||
field_errors['course_id'] = build_api_error(
|
||||
@@ -577,8 +577,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
if course_key and not has_team_api_access(request.user, course_key):
|
||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
if topic_id not in course_module.teams_configuration.teamsets_by_id or (
|
||||
not has_specific_teamset_access(request.user, course_module, topic_id)
|
||||
if topic_id not in course_block.teams_configuration.teamsets_by_id or (
|
||||
not has_specific_teamset_access(request.user, course_block, topic_id)
|
||||
):
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@@ -647,18 +647,18 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
page_query_param = self.request.query_params.get(self.paginator.page_query_param)
|
||||
return page_kwarg or page_query_param or 1
|
||||
|
||||
def _get_private_team_ids_to_exclude(self, course_module):
|
||||
def _get_private_team_ids_to_exclude(self, course_block):
|
||||
"""
|
||||
Get the list of team ids that should be excluded from the response.
|
||||
Staff can see all private teams.
|
||||
Users should not be able to see teams in private teamsets that they are not a member of.
|
||||
"""
|
||||
if has_access(self.request.user, 'staff', course_module.id):
|
||||
if has_access(self.request.user, 'staff', course_block.id):
|
||||
return set()
|
||||
|
||||
private_teamset_ids = [ts.teamset_id for ts in course_module.teamsets if ts.is_private_managed]
|
||||
private_teamset_ids = [ts.teamset_id for ts in course_block.teamsets if ts.is_private_managed]
|
||||
excluded_team_ids = CourseTeam.objects.filter(
|
||||
course_id=course_module.id,
|
||||
course_id=course_block.id,
|
||||
topic_id__in=private_teamset_ids
|
||||
).exclude(
|
||||
membership__user=self.request.user
|
||||
@@ -1013,8 +1013,8 @@ class TopicListView(GenericAPIView):
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Ensure the course exists
|
||||
course_module = modulestore().get_course(course_id)
|
||||
if course_module is None: # course is None if not found
|
||||
course_block = modulestore().get_course(course_id)
|
||||
if course_block is None: # course is None if not found
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if not has_team_api_access(request.user, course_id):
|
||||
@@ -1034,8 +1034,8 @@ class TopicListView(GenericAPIView):
|
||||
# Always sort alphabetically, as it will be used as secondary sort
|
||||
# in the case of "team_count".
|
||||
organization_protection_status = user_organization_protection_status(request.user, course_id)
|
||||
topics = get_alphabetical_topics(course_module)
|
||||
topics = _filter_hidden_private_teamsets(request.user, topics, course_module)
|
||||
topics = get_alphabetical_topics(course_block)
|
||||
topics = _filter_hidden_private_teamsets(request.user, topics, course_block)
|
||||
|
||||
if ordering == 'team_count':
|
||||
add_team_count(request.user, topics, course_id, organization_protection_status)
|
||||
@@ -1065,17 +1065,17 @@ class TopicListView(GenericAPIView):
|
||||
return response
|
||||
|
||||
|
||||
def _filter_hidden_private_teamsets(user, teamsets, course_module):
|
||||
def _filter_hidden_private_teamsets(user, teamsets, course_block):
|
||||
"""
|
||||
Return a filtered list of teamsets, removing any private teamsets that a user doesn't have access to.
|
||||
Follows the same logic as `has_specific_teamset_access` but in bulk rather than for one teamset at a time
|
||||
"""
|
||||
if has_course_staff_privileges(user, course_module.id):
|
||||
if has_course_staff_privileges(user, course_block.id):
|
||||
return teamsets
|
||||
private_teamset_ids = [teamset.teamset_id for teamset in course_module.teamsets if teamset.is_private_managed]
|
||||
private_teamset_ids = [teamset.teamset_id for teamset in course_block.teamsets if teamset.is_private_managed]
|
||||
teamset_ids_user_has_access_to = set(
|
||||
CourseTeam.objects.filter(
|
||||
course_id=course_module.id,
|
||||
course_id=course_block.id,
|
||||
topic_id__in=private_teamset_ids,
|
||||
membership__user=user
|
||||
).values_list('topic_id', flat=True)
|
||||
@@ -1086,17 +1086,17 @@ def _filter_hidden_private_teamsets(user, teamsets, course_module):
|
||||
]
|
||||
|
||||
|
||||
def get_alphabetical_topics(course_module):
|
||||
def get_alphabetical_topics(course_block):
|
||||
"""Return a list of team topics sorted alphabetically.
|
||||
|
||||
Arguments:
|
||||
course_module (xmodule): the course which owns the team topics
|
||||
course_block (xblock): the course which owns the team topics
|
||||
|
||||
Returns:
|
||||
list: a list of sorted team topics
|
||||
"""
|
||||
return sorted(
|
||||
course_module.teams_configuration.cleaned_data['team_sets'],
|
||||
course_block.teams_configuration.cleaned_data['team_sets'],
|
||||
key=lambda t: t['name'].lower(),
|
||||
)
|
||||
|
||||
@@ -1159,19 +1159,19 @@ class TopicDetailView(APIView):
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Ensure the course exists
|
||||
course_module = modulestore().get_course(course_id)
|
||||
if course_module is None:
|
||||
course_block = modulestore().get_course(course_id)
|
||||
if course_block is None:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if not has_team_api_access(request.user, course_id):
|
||||
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
try:
|
||||
topic = course_module.teamsets_by_id[topic_id]
|
||||
topic = course_block.teamsets_by_id[topic_id]
|
||||
except KeyError:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if not has_specific_teamset_access(request.user, course_module, topic_id):
|
||||
if not has_specific_teamset_access(request.user, course_block, topic_id):
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
organization_protection_status = user_organization_protection_status(request.user, course_id)
|
||||
@@ -1371,11 +1371,11 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
if not has_team_api_access(request.user, requested_course_key):
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
course_module = modulestore().get_course(requested_course_key)
|
||||
if not course_module:
|
||||
course_block = modulestore().get_course(requested_course_key)
|
||||
if not course_block:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
specified_username_or_team = True
|
||||
teamsets = course_module.teams_configuration.teamsets_by_id
|
||||
teamsets = course_block.teams_configuration.teamsets_by_id
|
||||
teamset_id = request.query_params['teamset_id']
|
||||
teamset = teamsets.get(teamset_id, None)
|
||||
if not teamset:
|
||||
@@ -1462,9 +1462,9 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
|
||||
except User.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
course_module = modulestore().get_course(team.course_id)
|
||||
course_block = modulestore().get_course(team.course_id)
|
||||
# This should use `calc_max_team_size` instead of `default_max_team_size` (TODO MST-32).
|
||||
max_team_size = course_module.teams_configuration.default_max_team_size
|
||||
max_team_size = course_block.teams_configuration.default_max_team_size
|
||||
if max_team_size is not None and team.users.count() >= max_team_size:
|
||||
return Response(
|
||||
build_api_error(gettext_noop("This team is already full.")),
|
||||
@@ -1709,7 +1709,7 @@ class MembershipBulkManagementView(GenericAPIView):
|
||||
course_id = CourseKey.from_string(course_id_string)
|
||||
except InvalidKeyError:
|
||||
raise Http404(f'Invalid course key: {course_id_string}') # lint-amnesty, pylint: disable=raise-missing-from
|
||||
course_module = modulestore().get_course(course_id)
|
||||
if not course_module:
|
||||
course_block = modulestore().get_course(course_id)
|
||||
if not course_block:
|
||||
raise Http404(f'Course not found: {course_id}')
|
||||
return course_module
|
||||
return course_block
|
||||
|
||||
@@ -94,7 +94,7 @@ FEATURES = {
|
||||
# .. toggle_name: FEATURES['DISPLAY_DEBUG_INFO_TO_STAFF']
|
||||
# .. toggle_implementation: DjangoSetting
|
||||
# .. toggle_default: True
|
||||
# .. toggle_description: Add a "Staff Debug" button to course modules for debugging
|
||||
# .. toggle_description: Add a "Staff Debug" button to course blocks for debugging
|
||||
# by course staff.
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2015-09-04
|
||||
|
||||
@@ -161,7 +161,7 @@ class CourseOverviewTestCase(CatalogIntegrationMixin, ModuleStoreTestCase, Cache
|
||||
time_field_accessor = lambda object, field_name: get_seconds_since_epoch(getattr(object, field_name))
|
||||
|
||||
# The course about fields are accessed through the CourseDetail
|
||||
# class for the course module, and stored as attributes on the
|
||||
# class for the course block, and stored as attributes on the
|
||||
# CourseOverview objects.
|
||||
course_about_accessor = lambda object, field_name: CourseDetails.fetch_about_attribute(object.id, field_name)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ def get_expected_duration(course_id):
|
||||
|
||||
def spaced_out_sections(course):
|
||||
"""
|
||||
Generator that returns sections of the course module with a suggested time to complete for each
|
||||
Generator that returns sections of the course block with a suggested time to complete for each
|
||||
|
||||
Returns:
|
||||
index (int): index of section
|
||||
|
||||
@@ -238,7 +238,7 @@ class CourseCohortsSettings(models.Model):
|
||||
|
||||
# Note that although a default value is specified here for always_cohort_inline_discussions (False),
|
||||
# in reality the default value at the time that cohorting is enabled for a course comes from
|
||||
# course_module.always_cohort_inline_discussions (via `migrate_cohort_settings`).
|
||||
# course_block.always_cohort_inline_discussions (via `migrate_cohort_settings`).
|
||||
# DEPRECATED-- DO NOT USE: Instead use `CourseDiscussionSettings.always_divide_inline_discussions`
|
||||
# via `CourseDiscussionSettings.get` or `CourseDiscussionSettings.update`.
|
||||
always_cohort_inline_discussions = models.BooleanField(default=False)
|
||||
|
||||
@@ -149,7 +149,7 @@ def permission_blacked_out(course, role_names, permission_name):
|
||||
Returns true if a user in course with the given roles would have permission_name blacked out.
|
||||
|
||||
This will return true if it is a permission that the user might have normally had for the course, but does not have
|
||||
right this moment because we are in a discussion blackout period (as defined by the settings on the course module).
|
||||
right this moment because we are in a discussion blackout period (as defined by the settings on the course block).
|
||||
Namely, they can still view, but they can't edit, update, or create anything. This only applies to students, as
|
||||
moderators of any kind still have posting privileges during discussion blackouts.
|
||||
"""
|
||||
|
||||
@@ -582,8 +582,8 @@ def is_enrollment_valid_for_proctoring(username, course_id):
|
||||
return False
|
||||
|
||||
# Check that the course has proctored exams enabled
|
||||
course_module = modulestore().get_course(course_id)
|
||||
if not course_module or not course_module.enable_proctored_exams:
|
||||
course_block = modulestore().get_course(course_id)
|
||||
if not course_block or not course_block.enable_proctored_exams:
|
||||
return False
|
||||
|
||||
# Only allow verified modes
|
||||
@@ -592,7 +592,7 @@ def is_enrollment_valid_for_proctoring(username, course_id):
|
||||
]
|
||||
|
||||
# If the proctoring provider allows learners in honor mode to take exams, include it
|
||||
if settings.PROCTORING_BACKENDS.get(course_module.proctoring_provider, {}).get('allow_honor_mode'):
|
||||
if settings.PROCTORING_BACKENDS.get(course_block.proctoring_provider, {}).get('allow_honor_mode'):
|
||||
appropriate_modes.append(CourseMode.HONOR)
|
||||
|
||||
if enrollment['mode'] not in appropriate_modes:
|
||||
|
||||
@@ -83,8 +83,8 @@ def get_week_highlights(user, course_key, week_num):
|
||||
the requested week_num.
|
||||
"""
|
||||
course_descriptor = _get_course_with_highlights(course_key)
|
||||
course_module = _get_course_module(course_descriptor, user)
|
||||
sections_with_highlights = _get_sections_with_highlights(course_module)
|
||||
course_block = _get_course_block(course_descriptor, user)
|
||||
sections_with_highlights = _get_sections_with_highlights(course_block)
|
||||
highlights = _get_highlights_for_week(
|
||||
sections_with_highlights,
|
||||
week_num,
|
||||
@@ -101,8 +101,8 @@ def get_next_section_highlights(user, course_key, start_date, target_date):
|
||||
CourseUpdateDoeNotExist: if highlights do not exist for the requested date
|
||||
"""
|
||||
course_descriptor = _get_course_with_highlights(course_key)
|
||||
course_module = _get_course_module(course_descriptor, user)
|
||||
return _get_highlights_for_next_section(course_module, start_date, target_date)
|
||||
course_block = _get_course_block(course_descriptor, user)
|
||||
return _get_highlights_for_next_section(course_block, start_date, target_date)
|
||||
|
||||
|
||||
def _get_course_with_highlights(course_key):
|
||||
@@ -126,8 +126,8 @@ def _get_course_descriptor(course_key):
|
||||
return course_descriptor
|
||||
|
||||
|
||||
def _get_course_module(course_descriptor, user):
|
||||
""" Gets course module that takes into account user state and permissions """
|
||||
def _get_course_block(course_descriptor, user):
|
||||
""" Gets course block that takes into account user state and permissions """
|
||||
# Adding courseware imports here to insulate other apps (e.g. schedules) to
|
||||
# avoid import errors.
|
||||
from lms.djangoapps.courseware.model_data import FieldDataCache
|
||||
@@ -142,12 +142,12 @@ def _get_course_module(course_descriptor, user):
|
||||
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
|
||||
course_descriptor.id, user, course_descriptor, depth=1, read_only=True,
|
||||
)
|
||||
course_module = get_module_for_descriptor(
|
||||
course_block = get_module_for_descriptor(
|
||||
user, request, course_descriptor, field_data_cache, course_descriptor.id, course=course_descriptor,
|
||||
)
|
||||
if not course_module:
|
||||
raise CourseUpdateDoesNotExist(f'Course module {course_descriptor.id} not found')
|
||||
return course_module
|
||||
if not course_block:
|
||||
raise CourseUpdateDoesNotExist(f'Course block {course_descriptor.id} not found')
|
||||
return course_block
|
||||
|
||||
|
||||
def _section_has_highlights(section):
|
||||
@@ -155,9 +155,9 @@ def _section_has_highlights(section):
|
||||
return section.highlights and not section.hide_from_toc
|
||||
|
||||
|
||||
def _get_sections_with_highlights(course_module):
|
||||
def _get_sections_with_highlights(course_block):
|
||||
""" Returns all sections that have highlights in a course """
|
||||
return list(filter(_section_has_highlights, course_module.get_children()))
|
||||
return list(filter(_section_has_highlights, course_block.get_children()))
|
||||
|
||||
|
||||
def _get_highlights_for_week(sections, week_num, course_key):
|
||||
|
||||
@@ -168,10 +168,10 @@ class TestContentHighlights(ModuleStoreTestCase): # lint-amnesty, pylint: disab
|
||||
with self.store.bulk_operations(self.course_key):
|
||||
self._create_chapter(highlights=['Test highlight'])
|
||||
|
||||
with self.assertRaisesRegex(CourseUpdateDoesNotExist, 'Course module .* not found'):
|
||||
with self.assertRaisesRegex(CourseUpdateDoesNotExist, 'Course block .* not found'):
|
||||
get_week_highlights(self.user, self.course_key, 1)
|
||||
|
||||
yesterday = datetime.datetime.utcnow() - datetime.timedelta(days=1)
|
||||
today = datetime.datetime.utcnow()
|
||||
with self.assertRaisesRegex(CourseUpdateDoesNotExist, 'Course module .* not found'):
|
||||
with self.assertRaisesRegex(CourseUpdateDoesNotExist, 'Course block .* not found'):
|
||||
get_next_section_highlights(self.user, self.course_key, yesterday, today.date())
|
||||
|
||||
@@ -30,7 +30,7 @@ class ShowAnswerFieldOverrideTest(ModuleStoreTestCase):
|
||||
inject_field_overrides((course,), course, self.user)
|
||||
return course
|
||||
|
||||
def get_course_module(self, course):
|
||||
def get_course_block(self, course):
|
||||
request = RequestFactory().request()
|
||||
field_data_cache = FieldDataCache([], course.id, self.user)
|
||||
return get_module(self.user, request, course.location, field_data_cache, course=course)
|
||||
@@ -40,18 +40,18 @@ class ShowAnswerFieldOverrideTest(ModuleStoreTestCase):
|
||||
with override_waffle_flag(RELATIVE_DATES_FLAG, active=active):
|
||||
# Instructor paced course will just have the default value
|
||||
ip_course = self.setup_course()
|
||||
course_module = self.get_course_module(ip_course)
|
||||
assert course_module.showanswer == SHOWANSWER.FINISHED
|
||||
course_block = self.get_course_block(ip_course)
|
||||
assert course_block.showanswer == SHOWANSWER.FINISHED
|
||||
|
||||
# This should be updated to not explicitly add in the showanswer so it can test the
|
||||
# default case of never touching showanswer. Reference ticket AA-307 (if that's closed,
|
||||
# this can be updated!)
|
||||
sp_course = self.setup_course(self_paced=True, showanswer=SHOWANSWER.FINISHED)
|
||||
course_module = self.get_course_module(sp_course)
|
||||
course_block = self.get_course_block(sp_course)
|
||||
if active:
|
||||
assert course_module.showanswer == SHOWANSWER.AFTER_ALL_ATTEMPTS_OR_CORRECT
|
||||
assert course_block.showanswer == SHOWANSWER.AFTER_ALL_ATTEMPTS_OR_CORRECT
|
||||
else:
|
||||
assert course_module.showanswer == SHOWANSWER.FINISHED
|
||||
assert course_block.showanswer == SHOWANSWER.FINISHED
|
||||
|
||||
@ddt.data(
|
||||
(SHOWANSWER.ATTEMPTED, SHOWANSWER.ATTEMPTED_NO_PAST_DUE),
|
||||
@@ -67,5 +67,5 @@ class ShowAnswerFieldOverrideTest(ModuleStoreTestCase):
|
||||
@override_waffle_flag(RELATIVE_DATES_FLAG, active=True)
|
||||
def test_get(self, initial_value, expected_final_value):
|
||||
course = self.setup_course(self_paced=True, showanswer=initial_value)
|
||||
course_module = self.get_course_module(course)
|
||||
assert course_module.showanswer == expected_final_value
|
||||
course_block = self.get_course_block(course)
|
||||
assert course_block.showanswer == expected_final_value
|
||||
|
||||
@@ -117,7 +117,7 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
completion=0.75,
|
||||
)
|
||||
|
||||
def _bind_course_module(self, module):
|
||||
def _bind_course_block(self, module):
|
||||
"""
|
||||
Bind a module (part of self.course) so we can access student-specific data.
|
||||
"""
|
||||
@@ -222,7 +222,7 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
|
||||
library_content_block.refresh_children()
|
||||
lib_vertical = self.store.get_item(lib_vertical.location)
|
||||
self._bind_course_module(lib_vertical)
|
||||
self._bind_course_block(lib_vertical)
|
||||
# We need to refetch the library_content_block to retrieve the
|
||||
# fresh version from the call to get_item for lib_vertical
|
||||
library_content_block = [child for child in lib_vertical.get_children()
|
||||
@@ -230,7 +230,7 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
|
||||
## Ensure the library_content_block is properly set up
|
||||
# This is needed so we can call get_child_descriptors
|
||||
self._bind_course_module(library_content_block)
|
||||
self._bind_course_block(library_content_block)
|
||||
# Make sure the runtime knows that the block's children vary per-user:
|
||||
assert library_content_block.has_dynamic_children()
|
||||
assert len(library_content_block.children) == 3
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Django module container for classes and operations related to the "Course Module" content type
|
||||
Django module container for classes and operations related to the "Course Block" content type
|
||||
"""
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ class Textbook: # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
toc_url = self.book_url + 'toc.xml'
|
||||
|
||||
# cdodge: I've added this caching of TOC because in Mongo-backed instances (but not Filesystem stores)
|
||||
# course modules have a very short lifespan and are constantly being created and torn down.
|
||||
# course blocks have a very short lifespan and are constantly being created and torn down.
|
||||
# Since this module in the __init__() method does a synchronous call to AWS to get the TOC
|
||||
# this is causing a big performance problem. So let's be a bit smarter about this and cache
|
||||
# each fetch and store in-mem for 10 minutes.
|
||||
|
||||
@@ -392,7 +392,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
@strip_key
|
||||
def get_course(self, course_key, depth=0, **kwargs): # lint-amnesty, pylint: disable=arguments-differ
|
||||
"""
|
||||
returns the course module associated with the course_id. If no such course exists,
|
||||
returns the course block associated with the course_id. If no such course exists,
|
||||
it returns None
|
||||
|
||||
:param course_key: must be a CourseKey
|
||||
|
||||
@@ -419,7 +419,7 @@ class ItemFactory(XModuleFactory):
|
||||
module.submission_end = submission_end
|
||||
store.update_item(module, user_id)
|
||||
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
|
||||
# VS[compat] cdodge: This is a hack because static_tabs also have references from the course block, so
|
||||
# if we add one then we need to also add it to the policy information (i.e. metadata)
|
||||
# we should remove this once we can break this reference from the course to static tabs
|
||||
if category == 'static_tab':
|
||||
|
||||
@@ -1308,9 +1308,9 @@ class TestItemCrud(SplitModuleTest):
|
||||
)
|
||||
|
||||
# add new child to old parent in continued (leave off version_guid)
|
||||
course_module_locator = new_course.location.version_agnostic()
|
||||
course_block_locator = new_course.location.version_agnostic()
|
||||
new_ele = modulestore().create_child(
|
||||
user, course_module_locator, 'chapter',
|
||||
user, course_block_locator, 'chapter',
|
||||
fields={'display_name': 'chapter 4'},
|
||||
)
|
||||
assert new_ele.update_version != course_block_update_version
|
||||
|
||||
@@ -325,7 +325,7 @@ class LibraryExportManager(ExportManager):
|
||||
|
||||
def post_process(self, root, export_fs):
|
||||
"""
|
||||
Because Libraries are XBlocks, they aren't exported in the same way Course Modules
|
||||
Because Libraries are XBlocks, they aren't exported in the same way Course Blocks
|
||||
are, but instead use the standard XBlock serializers. Accordingly, we need to
|
||||
create our own index file to act as the equivalent to the root course.xml file,
|
||||
called library.xml.
|
||||
|
||||
@@ -342,7 +342,7 @@ class ImportManager:
|
||||
# No matter what do_import_static is, import "static_import" directory.
|
||||
# This is needed because the "about" pages (eg "overview") are
|
||||
# loaded via load_extra_content, and do not inherit the lms
|
||||
# metadata from the course module, and thus do not get
|
||||
# metadata from the course block, and thus do not get
|
||||
# "static_content_store" properly defined. Static content
|
||||
# referenced in those extra pages thus need to come through the
|
||||
# c4x:// contentstore, unfortunately. Tell users to copy that
|
||||
@@ -411,8 +411,8 @@ class ImportManager:
|
||||
if self.verbose:
|
||||
log.debug("Scanning %s for courselike module...", courselike_key)
|
||||
|
||||
# Quick scan to get course module as we need some info from there.
|
||||
# Also we need to make sure that the course module is committed
|
||||
# Quick scan to get course block as we need some info from there.
|
||||
# Also we need to make sure that the course block is committed
|
||||
# first into the store
|
||||
course_data_path = path(self.data_dir) / source_courselike.data_dir
|
||||
|
||||
@@ -589,7 +589,7 @@ class CourseImportManager(ImportManager):
|
||||
from the temporary modulestore.
|
||||
"""
|
||||
source_course = self.xml_module_store.get_course(courselike_key)
|
||||
# STEP 1: find and import course module
|
||||
# STEP 1: find and import course block
|
||||
course, course_data_path = self.import_courselike(
|
||||
runtime, courselike_key, dest_id, source_course,
|
||||
)
|
||||
|
||||
@@ -104,7 +104,7 @@ class SequenceFields: # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
is_entrance_exam = Boolean(
|
||||
display_name=_("Is Entrance Exam"),
|
||||
help=_(
|
||||
"Tag this course module as an Entrance Exam. "
|
||||
"Tag this course block as an Entrance Exam. "
|
||||
"Note, you must enable Entrance Exams for this course setting to take effect."
|
||||
),
|
||||
default=False,
|
||||
|
||||
@@ -712,9 +712,9 @@ class SplitTestBlock( # lint-amnesty, pylint: disable=abstract-method
|
||||
assert hasattr(self.system, 'modulestore') and hasattr(self.system.modulestore, 'get_course'), \
|
||||
"modulestore has to be available"
|
||||
|
||||
course_module = self.system.modulestore.get_course(self.location.course_key)
|
||||
course_block = self.system.modulestore.get_course(self.location.course_key)
|
||||
group_configuration_url = None
|
||||
if 'split_test' in course_module.advanced_modules:
|
||||
if 'split_test' in course_block.advanced_modules:
|
||||
user_partition = self.get_selected_partition()
|
||||
if user_partition:
|
||||
group_configuration_url = "{url}#{configuration_id}".format(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests the course modules and their functions"""
|
||||
"""Tests the course blocks and their functions"""
|
||||
|
||||
|
||||
import itertools
|
||||
@@ -288,7 +288,7 @@ class ImportTestCase(BaseCourseTestCase): # lint-amnesty, pylint: disable=missi
|
||||
# pylint: disable=protected-access
|
||||
assert original_unwrapped is not descriptor._unwrapped_field_data
|
||||
compute_inherited_metadata(descriptor)
|
||||
# Check the course module, since it has inheritance
|
||||
# Check the course block, since it has inheritance
|
||||
descriptor = descriptor.get_children()[0]
|
||||
self.course_descriptor_inheritance_check(descriptor, from_date_string, unicorn_color)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from xmodule.validation import StudioValidationMessage
|
||||
from xmodule.x_module import AUTHOR_VIEW
|
||||
from xmodule.capa_block import ProblemBlock
|
||||
|
||||
from .test_course_module import DummySystem as TestImportSystem
|
||||
from .test_course_block import DummySystem as TestImportSystem
|
||||
|
||||
dummy_render = lambda block, _: Fragment(block.data) # pylint: disable=invalid-name
|
||||
|
||||
@@ -54,7 +54,7 @@ class LibraryContentTest(MixedSplitTestCase):
|
||||
source_library_id=str(self.library.location.library_key)
|
||||
)
|
||||
|
||||
def _bind_course_module(self, module):
|
||||
def _bind_course_block(self, module):
|
||||
"""
|
||||
Bind a module (part of self.course) so we can access student-specific data.
|
||||
"""
|
||||
@@ -219,7 +219,7 @@ class LibraryContentBlockTestMixin:
|
||||
"""
|
||||
self.lc_block.refresh_children()
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_module(self.lc_block)
|
||||
self._bind_course_block(self.lc_block)
|
||||
# Make sure the runtime knows that the block's children vary per-user:
|
||||
assert self.lc_block.has_dynamic_children()
|
||||
|
||||
@@ -360,7 +360,7 @@ class LibraryContentBlockTestMixin:
|
||||
|
||||
self.lc_block.refresh_children()
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_module(self.lc_block)
|
||||
self._bind_course_block(self.lc_block)
|
||||
|
||||
# Eventually, we should see every child block selected
|
||||
while len(blocks_seen) != len(self.lib_blocks):
|
||||
@@ -475,7 +475,7 @@ class TestLibraryContentRender(LibraryContentTest):
|
||||
self.lc_block.refresh_children()
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
assert len(self.lc_block.children) == len(self.lib_blocks)
|
||||
self._bind_course_module(self.lc_block)
|
||||
self._bind_course_block(self.lc_block)
|
||||
rendered = self.lc_block.render(AUTHOR_VIEW, {'root_xblock': self.lc_block})
|
||||
assert 'Hello world from block 1' in rendered.content
|
||||
|
||||
@@ -484,7 +484,7 @@ class TestLibraryContentRender(LibraryContentTest):
|
||||
self.lc_block.refresh_children()
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
assert len(self.lc_block.children) == len(self.lib_blocks)
|
||||
self._bind_course_module(self.lc_block)
|
||||
self._bind_course_block(self.lc_block)
|
||||
rendered = self.lc_block.render(AUTHOR_VIEW, {})
|
||||
assert '' == rendered.content
|
||||
# content should be empty
|
||||
@@ -502,7 +502,7 @@ class TestLibraryContentAnalytics(LibraryContentTest):
|
||||
self.publisher = Mock()
|
||||
self.lc_block.refresh_children()
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_module(self.lc_block)
|
||||
self._bind_course_block(self.lc_block)
|
||||
self.lc_block.xmodule_runtime.publish = self.publisher
|
||||
|
||||
def _assert_event_was_published(self, event_type):
|
||||
@@ -556,7 +556,7 @@ class TestLibraryContentAnalytics(LibraryContentTest):
|
||||
self.store.publish(self.course.location, self.user_id)
|
||||
with self.store.branch_setting(ModuleStoreEnum.Branch.published_only):
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_module(self.lc_block)
|
||||
self._bind_course_block(self.lc_block)
|
||||
self.lc_block.xmodule_runtime.publish = self.publisher
|
||||
self.test_assigned_event()
|
||||
|
||||
@@ -575,7 +575,7 @@ class TestLibraryContentAnalytics(LibraryContentTest):
|
||||
|
||||
# Reload lc_block and set it up for a student:
|
||||
self.lc_block = self.store.get_item(self.lc_block.location)
|
||||
self._bind_course_module(self.lc_block)
|
||||
self._bind_course_block(self.lc_block)
|
||||
self.lc_block.xmodule_runtime.publish = self.publisher
|
||||
|
||||
# Get the keys of each of our blocks, as they appear in the course:
|
||||
|
||||
@@ -11,7 +11,7 @@ from xmodule.modulestore.tests.utils import MixedSplitTestCase
|
||||
from xmodule.randomize_block import RandomizeBlock
|
||||
from xmodule.tests import get_test_system
|
||||
|
||||
from .test_course_module import DummySystem as TestImportSystem
|
||||
from .test_course_block import DummySystem as TestImportSystem
|
||||
|
||||
|
||||
class RandomizeBlockTest(MixedSplitTestCase):
|
||||
|
||||
@@ -19,7 +19,7 @@ from xmodule.split_test_block import (
|
||||
user_partition_values,
|
||||
)
|
||||
from xmodule.tests import get_test_system
|
||||
from xmodule.tests.test_course_module import DummySystem as TestImportSystem
|
||||
from xmodule.tests.test_course_block import DummySystem as TestImportSystem
|
||||
from xmodule.tests.xml import XModuleXmlImportTest
|
||||
from xmodule.tests.xml import factories as xml
|
||||
from xmodule.validation import StudioValidationMessage
|
||||
|
||||
Reference in New Issue
Block a user