Make Studio able to handle deprecated key formats in urls
This commit is contained in:
@@ -131,7 +131,6 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
descriptor = store.get_items(course.id, category='vertical',)
|
||||
resp = self.client.get_html(get_url('unit_handler', descriptor[0].location))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
_test_no_locations(self, resp)
|
||||
|
||||
for expected in expected_types:
|
||||
self.assertIn(expected, resp.content)
|
||||
@@ -157,7 +156,6 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
|
||||
resp = self.client.get_html(get_url('unit_handler', usage_key))
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
_test_no_locations(self, resp, status_code=400)
|
||||
|
||||
def check_edit_unit(self, test_course_name):
|
||||
_, course_items = import_from_xml(modulestore(), self.user.id, 'common/test/data/', [test_course_name])
|
||||
@@ -364,8 +362,6 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
get_url('xblock_view_handler', usage_key, kwargs={'view_name': 'container_preview'})
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# TODO: uncomment when preview no longer has locations being returned.
|
||||
# _test_no_locations(self, resp)
|
||||
|
||||
# These are the data-ids of the xblocks contained in the vertical.
|
||||
self.assertContains(resp, 'edX+toy+2012_Fall+video+sample_video')
|
||||
@@ -534,7 +530,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
url = reverse_course_url(
|
||||
'assets_handler',
|
||||
course.id,
|
||||
kwargs={'asset_key_string': course.id.make_asset_key('asset', 'sample_static.txt')}
|
||||
kwargs={'asset_key_string': unicode(course.id.make_asset_key('asset', 'sample_static.txt'))}
|
||||
)
|
||||
resp = self.client.delete(url)
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
@@ -761,7 +757,6 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
def test_bad_contentstore_request(self):
|
||||
resp = self.client.get_html('http://localhost:8001/c4x/CDX/123123/asset/&images_circuits_Lab7Solution2.png')
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
_test_no_locations(self, resp, 400)
|
||||
|
||||
def test_rewrite_nonportable_links_on_import(self):
|
||||
module_store = modulestore()
|
||||
@@ -1196,7 +1191,6 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
|
||||
for descriptor in items:
|
||||
resp = self.client.get_html(get_url('unit_handler', descriptor.location))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
_test_no_locations(self, resp)
|
||||
|
||||
|
||||
class ContentStoreTest(ContentStoreTestCase):
|
||||
@@ -1475,7 +1469,6 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
status_code=200,
|
||||
html=True
|
||||
)
|
||||
_test_no_locations(self, resp)
|
||||
|
||||
def test_course_factory(self):
|
||||
"""Test that the course factory works correctly."""
|
||||
@@ -1498,7 +1491,6 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
status_code=200,
|
||||
html=True
|
||||
)
|
||||
_test_no_locations(self, resp)
|
||||
|
||||
def test_course_overview_view_with_course(self):
|
||||
"""Test viewing the course overview page with an existing course"""
|
||||
@@ -1522,7 +1514,6 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
}
|
||||
|
||||
resp = self.client.ajax_post(reverse_url('xblock_handler'), section_data)
|
||||
_test_no_locations(self, resp, html=False)
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
@@ -1563,7 +1554,6 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
get_url(handler, course_key, 'course_key_string')
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
_test_no_locations(self, resp)
|
||||
|
||||
_, course_items = import_from_xml(modulestore(), self.user.id, 'common/test/data/', ['simple'])
|
||||
course_key = course_items[0].id
|
||||
@@ -1589,20 +1579,17 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
subsection_key = course_key.make_usage_key('sequential', 'test_sequence')
|
||||
resp = self.client.get_html(get_url('subsection_handler', subsection_key))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
_test_no_locations(self, resp)
|
||||
|
||||
# go look at the Edit page
|
||||
unit_key = course_key.make_usage_key('vertical', 'test_vertical')
|
||||
resp = self.client.get_html(get_url('unit_handler', unit_key))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
_test_no_locations(self, resp)
|
||||
|
||||
def delete_item(category, name):
|
||||
""" Helper method for testing the deletion of an xblock item. """
|
||||
item_key = course_key.make_usage_key(category, name)
|
||||
resp = self.client.delete(get_url('xblock_handler', item_key))
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
_test_no_locations(self, resp, status_code=204, html=False)
|
||||
|
||||
# delete a component
|
||||
delete_item(category='html', name='test_html')
|
||||
@@ -1805,7 +1792,6 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
Show the course overview page.
|
||||
"""
|
||||
resp = self.client.get_html(get_url('course_handler', course_key, 'course_key_string'))
|
||||
_test_no_locations(self, resp)
|
||||
return resp
|
||||
|
||||
def test_wiki_slug(self):
|
||||
@@ -1887,7 +1873,6 @@ class EntryPageTestCase(TestCase):
|
||||
def _test_page(self, page, status_code=200):
|
||||
resp = self.client.get_html(page)
|
||||
self.assertEqual(resp.status_code, status_code)
|
||||
_test_no_locations(self, resp, status_code)
|
||||
|
||||
def test_how_it_works(self):
|
||||
self._test_page("/howitworks")
|
||||
@@ -1925,19 +1910,3 @@ def _course_factory_create_course():
|
||||
def _get_course_id(course_data):
|
||||
"""Returns the course ID (org/number/run)."""
|
||||
return SlashSeparatedCourseKey(course_data['org'], course_data['number'], course_data['run'])
|
||||
|
||||
|
||||
def _test_no_locations(test, resp, status_code=200, html=True):
|
||||
"""
|
||||
Verifies that "i4x", which appears in old locations, but not
|
||||
new locators, does not appear in the HTML response output.
|
||||
Used to verify that database refactoring is complete.
|
||||
"""
|
||||
test.assertNotContains(resp, 'i4x', status_code=status_code, html=html)
|
||||
if html:
|
||||
# For HTML pages, it is nice to call the method with html=True because
|
||||
# it checks that the HTML properly parses. However, it won't find i4x usages
|
||||
# in JavaScript blocks.
|
||||
content = resp.content
|
||||
hits = len(re.findall(r"(?<!jump_to/)i4x://", content))
|
||||
test.assertEqual(hits, 0, "i4x found outside of LMS jump-to links")
|
||||
|
||||
@@ -189,7 +189,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
target_course_id.make_usage_key('conditional', 'condone')
|
||||
)
|
||||
self.assertIsNotNone(conditional_module)
|
||||
different_course_id = SlashSeparatedCourseKey('edX', 'different_course', 'copy_run')
|
||||
different_course_id = SlashSeparatedCourseKey('edX', 'different_course', None)
|
||||
self.assertListEqual(
|
||||
[
|
||||
target_course_id.make_usage_key('problem', 'choiceprob'),
|
||||
|
||||
@@ -61,7 +61,7 @@ else:
|
||||
# XBlocks from pmitros repos are prototypes. They should not be used
|
||||
# except for edX Learning Sciences experiments on edge.edx.org without
|
||||
# further work to make them robust, maintainable, finalize data formats,
|
||||
# etc.
|
||||
# etc.
|
||||
'concept', # Concept mapper. See https://github.com/pmitros/ConceptXBlock
|
||||
'done', # Lets students mark things as done. See https://github.com/pmitros/DoneXBlock
|
||||
'audio', # Embed an audio file. See https://github.com/pmitros/AudioXBlock
|
||||
@@ -97,7 +97,7 @@ def subsection_handler(request, usage_key_string):
|
||||
except ItemNotFoundError:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
preview_link = get_lms_link_for_item(usage_key, preview=True)
|
||||
preview_link = get_lms_link_for_item(item.location, preview=True)
|
||||
|
||||
# make sure that location references a 'sequential', otherwise return
|
||||
# BadRequest
|
||||
@@ -134,9 +134,9 @@ def subsection_handler(request, usage_key_string):
|
||||
'new_unit_category': 'vertical',
|
||||
'lms_link': lms_link,
|
||||
'preview_link': preview_link,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(usage_key.course_key).graders),
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(item.location.course_key).graders),
|
||||
'parent_item': parent,
|
||||
'locator': usage_key,
|
||||
'locator': item.location,
|
||||
'policy_metadata': policy_metadata,
|
||||
'subsection_units': subsection_units,
|
||||
'can_view_live': can_view_live
|
||||
@@ -211,7 +211,7 @@ def unit_handler(request, usage_key_string):
|
||||
return render_to_response('unit.html', {
|
||||
'context_course': course,
|
||||
'unit': item,
|
||||
'unit_usage_key': usage_key,
|
||||
'unit_usage_key': item.location,
|
||||
'child_usage_keys': [block.scope_ids.usage_id for block in xblocks],
|
||||
'component_templates': json.dumps(component_templates),
|
||||
'draft_preview_link': preview_lms_link,
|
||||
@@ -267,7 +267,7 @@ def container_handler(request, usage_key_string):
|
||||
'context_course': course, # Needed only for display of menus at top of page.
|
||||
'xblock': xblock,
|
||||
'unit_publish_state': unit_publish_state,
|
||||
'xblock_locator': usage_key,
|
||||
'xblock_locator': xblock.location,
|
||||
'unit': None if not ancestor_xblocks else ancestor_xblocks[0],
|
||||
'ancestor_xblocks': ancestor_xblocks,
|
||||
'component_templates': json.dumps(component_templates),
|
||||
@@ -415,7 +415,7 @@ def _get_item_in_course(request, usage_key):
|
||||
|
||||
course = modulestore().get_course(course_key)
|
||||
item = modulestore().get_item(usage_key, depth=1)
|
||||
lms_link = get_lms_link_for_item(usage_key)
|
||||
lms_link = get_lms_link_for_item(item.location)
|
||||
|
||||
return course, item, lms_link
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ def xblock_handler(request, usage_key_string):
|
||||
request.user,
|
||||
)
|
||||
|
||||
return JsonResponse({"locator": unicode(dest_usage_key)})
|
||||
return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)})
|
||||
else:
|
||||
return _create_item(request)
|
||||
else:
|
||||
@@ -403,7 +403,7 @@ def _create_item(request):
|
||||
if display_name is not None:
|
||||
metadata['display_name'] = display_name
|
||||
|
||||
store.create_and_save_xmodule(
|
||||
created_block = store.create_and_save_xmodule(
|
||||
dest_usage_key,
|
||||
request.user.id,
|
||||
definition_data=data,
|
||||
@@ -426,10 +426,10 @@ def _create_item(request):
|
||||
|
||||
# TODO replace w/ nicer accessor
|
||||
if not 'detached' in parent.runtime.load_block_type(category)._class_tags:
|
||||
parent.children.append(dest_usage_key)
|
||||
parent.children.append(created_block.location)
|
||||
store.update_item(parent, request.user.id)
|
||||
|
||||
return JsonResponse({"locator": unicode(dest_usage_key), "courseKey": unicode(dest_usage_key.course_key)})
|
||||
return JsonResponse({"locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key)})
|
||||
|
||||
|
||||
def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=None, user=None):
|
||||
@@ -439,8 +439,8 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N
|
||||
store = modulestore()
|
||||
source_item = store.get_item(duplicate_source_usage_key)
|
||||
# Change the blockID to be unique.
|
||||
dest_usage_key = duplicate_source_usage_key.replace(name=uuid4().hex)
|
||||
category = dest_usage_key.category
|
||||
dest_usage_key = source_item.location.replace(name=uuid4().hex)
|
||||
category = dest_usage_key.block_type
|
||||
|
||||
# Update the display name to indicate this is a duplicate (unless display name provided).
|
||||
duplicate_metadata = own_metadata(source_item)
|
||||
@@ -465,7 +465,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N
|
||||
if source_item.has_children:
|
||||
dest_module.children = []
|
||||
for child in source_item.children:
|
||||
dupe = _duplicate_item(dest_usage_key, child, user=user)
|
||||
dupe = _duplicate_item(dest_module.location, child, user=user)
|
||||
dest_module.children.append(dupe)
|
||||
store.update_item(dest_module, user.id if user else None)
|
||||
|
||||
@@ -473,14 +473,14 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, display_name=N
|
||||
parent = store.get_item(parent_usage_key)
|
||||
# If source was already a child of the parent, add duplicate immediately afterward.
|
||||
# Otherwise, add child to end.
|
||||
if duplicate_source_usage_key in parent.children:
|
||||
source_index = parent.children.index(duplicate_source_usage_key)
|
||||
parent.children.insert(source_index + 1, dest_usage_key)
|
||||
if source_item.location in parent.children:
|
||||
source_index = parent.children.index(source_item.location)
|
||||
parent.children.insert(source_index + 1, dest_module.location)
|
||||
else:
|
||||
parent.children.append(dest_usage_key)
|
||||
parent.children.append(dest_module.location)
|
||||
store.update_item(parent, user.id if user else None)
|
||||
|
||||
return dest_usage_key
|
||||
return dest_module.location
|
||||
|
||||
|
||||
def _delete_item(usage_key, user):
|
||||
@@ -553,12 +553,12 @@ def _get_module_info(usage_key, user, rewrite_static_links=True):
|
||||
data = replace_static_urls(
|
||||
data,
|
||||
None,
|
||||
course_id=usage_key.course_key
|
||||
course_id=module.location.course_key
|
||||
)
|
||||
|
||||
# Note that children aren't being returned until we have a use case.
|
||||
return {
|
||||
'id': unicode(usage_key),
|
||||
'id': unicode(module.location),
|
||||
'data': data,
|
||||
'metadata': own_metadata(module)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ Unit tests for getting the list of courses and the course outline.
|
||||
import json
|
||||
import lxml
|
||||
|
||||
from cms.urls import COURSE_KEY_PATTERN
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from contentstore.utils import reverse_course_url
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
@@ -38,7 +39,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
for link in course_link_eles:
|
||||
self.assertRegexpMatches(
|
||||
link.get("href"),
|
||||
'course/slashes:{0}'.format(Locator.ALLOWED_ID_CHARS)
|
||||
'course/{}'.format(COURSE_KEY_PATTERN)
|
||||
)
|
||||
# now test that url
|
||||
outline_response = authed_client.get(link.get("href"), {}, HTTP_ACCEPT='text/html')
|
||||
|
||||
@@ -53,7 +53,10 @@ class ItemTest(CourseTestCase):
|
||||
"""
|
||||
parsed = json.loads(response.content)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
return UsageKey.from_string(parsed['locator'])
|
||||
key = UsageKey.from_string(parsed['locator'])
|
||||
if key.course_key.run is None:
|
||||
key = key.map_into_course(CourseKey.from_string(parsed['courseKey']))
|
||||
return key
|
||||
|
||||
def create_xblock(self, parent_usage_key=None, display_name=None, category=None, boilerplate=None):
|
||||
data = {
|
||||
|
||||
48
cms/urls.py
48
cms/urls.py
@@ -5,6 +5,10 @@ from django.conf.urls import patterns, include, url
|
||||
from ratelimitbackend import admin
|
||||
admin.autodiscover()
|
||||
|
||||
COURSE_KEY_PATTERN = r'(?P<course_key_string>(?:[^/]+/[^/]+/[^/]+)|(?:[^/]+))'
|
||||
USAGE_KEY_PATTERN = r'(?P<usage_key_string>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
|
||||
ASSET_KEY_PATTERN = r'(?P<asset_key_string>(?:/?c4x(:/)?/[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
|
||||
|
||||
urlpatterns = patterns('', # nopep8
|
||||
|
||||
url(r'^transcripts/upload$', 'contentstore.views.upload_transcripts', name='upload_transcripts'),
|
||||
@@ -66,30 +70,30 @@ urlpatterns += patterns(
|
||||
url(r'^signin$', 'login_page', name='login'),
|
||||
url(r'^request_course_creator$', 'request_course_creator'),
|
||||
|
||||
url(r'^course_team/(?P<course_key_string>[^/]+)/(?P<email>.+)?$', 'course_team_handler'),
|
||||
url(r'^course_info/(?P<course_key_string>[^/]+)$', 'course_info_handler'),
|
||||
url(r'^course_team/{}/(?P<email>.+)?$'.format(COURSE_KEY_PATTERN), 'course_team_handler'),
|
||||
url(r'^course_info/{}$'.format(COURSE_KEY_PATTERN), 'course_info_handler'),
|
||||
url(
|
||||
r'^course_info_update/(?P<course_key_string>[^/]+)/(?P<provided_id>\d+)?$',
|
||||
r'^course_info_update/{}/(?P<provided_id>\d+)?$'.format(COURSE_KEY_PATTERN),
|
||||
'course_info_update_handler'
|
||||
),
|
||||
url(r'^course/(?P<course_key_string>[^/]+)?$', 'course_handler', name='course_handler'),
|
||||
url(r'^subsection/(?P<usage_key_string>[^/]+)$', 'subsection_handler'),
|
||||
url(r'^unit/(?P<usage_key_string>[^/]+)$', 'unit_handler'),
|
||||
url(r'^container/(?P<usage_key_string>[^/]+)$', 'container_handler'),
|
||||
url(r'^checklists/(?P<course_key_string>[^/]+)/(?P<checklist_index>\d+)?$', 'checklists_handler'),
|
||||
url(r'^orphan/(?P<course_key_string>[^/]+)$', 'orphan_handler'),
|
||||
url(r'^assets/(?P<course_key_string>[^/]+)/(?P<asset_key_string>.+)?$', 'assets_handler'),
|
||||
url(r'^import/(?P<course_key_string>[^/]+)$', 'import_handler'),
|
||||
url(r'^import_status/(?P<course_key_string>[^/]+)/(?P<filename>.+)$', 'import_status_handler'),
|
||||
url(r'^export/(?P<course_key_string>[^/]+)$', 'export_handler'),
|
||||
url(r'^xblock/(?P<usage_key_string>[^/]+)/(?P<view_name>[^/]+)$', 'xblock_view_handler'),
|
||||
url(r'^xblock/(?P<usage_key_string>[^/]+)?$', 'xblock_handler'),
|
||||
url(r'^tabs/(?P<course_key_string>[^/]+)$', 'tabs_handler'),
|
||||
url(r'^settings/details/(?P<course_key_string>[^/]+)$', 'settings_handler'),
|
||||
url(r'^settings/grading/(?P<course_key_string>[^/]+)(/)?(?P<grader_index>\d+)?$', 'grading_handler'),
|
||||
url(r'^settings/advanced/(?P<course_key_string>[^/]+)$', 'advanced_settings_handler'),
|
||||
url(r'^textbooks/(?P<course_key_string>[^/]+)$', 'textbooks_list_handler'),
|
||||
url(r'^textbooks/(?P<course_key_string>[^/]+)/(?P<textbook_id>\d[^/]*)$', 'textbooks_detail_handler'),
|
||||
url(r'^course/{}?$'.format(COURSE_KEY_PATTERN), 'course_handler', name='course_handler'),
|
||||
url(r'^subsection/{}$'.format(USAGE_KEY_PATTERN), 'subsection_handler'),
|
||||
url(r'^unit/{}$'.format(USAGE_KEY_PATTERN), 'unit_handler'),
|
||||
url(r'^container/{}$'.format(USAGE_KEY_PATTERN), 'container_handler'),
|
||||
url(r'^checklists/{}/(?P<checklist_index>\d+)?$'.format(COURSE_KEY_PATTERN), 'checklists_handler'),
|
||||
url(r'^orphan/{}$'.format(COURSE_KEY_PATTERN), 'orphan_handler'),
|
||||
url(r'^assets/{}/{}?$'.format(COURSE_KEY_PATTERN, ASSET_KEY_PATTERN), 'assets_handler'),
|
||||
url(r'^import/{}$'.format(COURSE_KEY_PATTERN), 'import_handler'),
|
||||
url(r'^import_status/{}/(?P<filename>.+)$'.format(COURSE_KEY_PATTERN), 'import_status_handler'),
|
||||
url(r'^export/{}$'.format(COURSE_KEY_PATTERN), 'export_handler'),
|
||||
url(r'^xblock/{}/(?P<view_name>[^/]+)$'.format(USAGE_KEY_PATTERN), 'xblock_view_handler'),
|
||||
url(r'^xblock/{}?$'.format(USAGE_KEY_PATTERN), 'xblock_handler'),
|
||||
url(r'^tabs/{}$'.format(COURSE_KEY_PATTERN), 'tabs_handler'),
|
||||
url(r'^settings/details/{}$'.format(COURSE_KEY_PATTERN), 'settings_handler'),
|
||||
url(r'^settings/grading/{}(/)?(?P<grader_index>\d+)?$'.format(COURSE_KEY_PATTERN), 'grading_handler'),
|
||||
url(r'^settings/advanced/{}$'.format(COURSE_KEY_PATTERN), 'advanced_settings_handler'),
|
||||
url(r'^textbooks/{}$'.format(COURSE_KEY_PATTERN), 'textbooks_list_handler'),
|
||||
url(r'^textbooks/{}/(?P<textbook_id>\d[^/]*)$'.format(COURSE_KEY_PATTERN), 'textbooks_detail_handler'),
|
||||
)
|
||||
|
||||
js_info_dict = {
|
||||
@@ -105,7 +109,7 @@ urlpatterns += patterns('',
|
||||
|
||||
|
||||
if settings.FEATURES.get('ENABLE_EXPORT_GIT'):
|
||||
urlpatterns += (url(r'^export_git/(?P<course_key_string>[^/]+)$',
|
||||
urlpatterns += (url(r'^export_git/{}$'.format(COURSE_KEY_PATTERN),
|
||||
'contentstore.views.export_git', name='export_git'),)
|
||||
|
||||
if settings.FEATURES.get('ENABLE_SERVICE_STATUS'):
|
||||
|
||||
@@ -197,7 +197,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
del metadata[old_name]
|
||||
|
||||
children = [
|
||||
location.course_key.make_usage_key_from_deprecated_string(childloc)
|
||||
self._convert_reference_to_key(childloc)
|
||||
for childloc in definition.get('children', [])
|
||||
]
|
||||
data = definition.get('data', {})
|
||||
@@ -254,6 +254,13 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
error_msg=exc_info_to_str(sys.exc_info())
|
||||
)
|
||||
|
||||
def _convert_reference_to_key(self, ref_string):
|
||||
"""
|
||||
Convert a single serialized UsageKey string in a ReferenceField into a UsageKey.
|
||||
"""
|
||||
key = Location.from_deprecated_string(ref_string)
|
||||
return key.replace(run=self.modulestore._fill_in_run(key.course_key).run)
|
||||
|
||||
def _convert_reference_fields_to_keys(self, class_, course_key, jsonfields):
|
||||
"""
|
||||
Find all fields of type reference and convert the payload into UsageKeys
|
||||
@@ -267,15 +274,15 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
if field is None:
|
||||
continue
|
||||
elif isinstance(field, Reference):
|
||||
jsonfields[field_name] = course_key.make_usage_key_from_deprecated_string(value)
|
||||
jsonfields[field_name] = self._convert_reference_to_key(value)
|
||||
elif isinstance(field, ReferenceList):
|
||||
jsonfields[field_name] = [
|
||||
course_key.make_usage_key_from_deprecated_string(ele) for ele in value
|
||||
self._convert_reference_to_key(ele) for ele in value
|
||||
]
|
||||
elif isinstance(field, ReferenceValueDict):
|
||||
for key, subvalue in value.iteritems():
|
||||
assert isinstance(subvalue, basestring)
|
||||
value[key] = course_key.make_usage_key_from_deprecated_string(subvalue)
|
||||
value[key] = self._convert_reference_to_key(subvalue)
|
||||
return jsonfields
|
||||
|
||||
|
||||
@@ -378,6 +385,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
# performance optimization to prevent updating the meta-data inheritance tree during
|
||||
# bulk write operations
|
||||
self.ignore_write_events_on_courses = set()
|
||||
self._course_run_cache = {}
|
||||
|
||||
def begin_bulk_write_operation_on_course(self, course_id):
|
||||
"""
|
||||
@@ -394,6 +402,27 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
self.ignore_write_events_on_courses.remove(course_id)
|
||||
self.refresh_cached_metadata_inheritance_tree(course_id)
|
||||
|
||||
def _fill_in_run(self, course_key):
|
||||
if course_key.run is not None:
|
||||
return course_key
|
||||
|
||||
cache_key = (course_key.org, course_key.course)
|
||||
if cache_key not in self._course_run_cache:
|
||||
|
||||
matching_courses = list(self.collection.find(SON([
|
||||
('_id.tag', 'i4x'),
|
||||
('_id.org', course_key.org),
|
||||
('_id.course', course_key.course),
|
||||
('_id.category', 'course'),
|
||||
])).limit(1))
|
||||
|
||||
if not matching_courses:
|
||||
return course_key
|
||||
|
||||
self._course_run_cache[cache_key] = matching_courses[0]['_id']['name']
|
||||
|
||||
return course_key.replace(run=self._course_run_cache[cache_key])
|
||||
|
||||
def _compute_metadata_inheritance_tree(self, course_id):
|
||||
'''
|
||||
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
|
||||
@@ -401,6 +430,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
# get all collections in the course, this query should not return any leaf nodes
|
||||
# note this is a bit ugly as when we add new categories of containers, we have to add it here
|
||||
|
||||
course_id = self._fill_in_run(course_id)
|
||||
block_types_with_children = set(
|
||||
name for name, class_ in XBlock.load_classes() if getattr(class_, 'has_children', False)
|
||||
)
|
||||
@@ -476,6 +506,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
'''
|
||||
tree = {}
|
||||
|
||||
course_id = self._fill_in_run(course_id)
|
||||
if not force_refresh:
|
||||
# see if we are first in the request cache (if present)
|
||||
if self.request_cache is not None and course_id in self.request_cache.data.get('metadata_inheritance', {}):
|
||||
@@ -554,6 +585,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
|
||||
data = {}
|
||||
to_process = list(items)
|
||||
course_key = self._fill_in_run(course_key)
|
||||
while to_process and depth is None or depth >= 0:
|
||||
children = []
|
||||
for item in to_process:
|
||||
@@ -581,6 +613,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
"""
|
||||
Load an XModuleDescriptor from item, using the children stored in data_cache
|
||||
"""
|
||||
course_key = self._fill_in_run(course_key)
|
||||
location = Location._from_deprecated_son(item['location'], course_key.run)
|
||||
data_dir = getattr(item, 'data_dir', location.course)
|
||||
root = self.fs_root / data_dir
|
||||
@@ -617,6 +650,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
Load a list of xmodules from the data in items, with children cached up
|
||||
to specified depth
|
||||
"""
|
||||
course_key = self._fill_in_run(course_key)
|
||||
data_cache = self._cache_children(course_key, items, depth)
|
||||
|
||||
# if we are loading a course object, if we're not prefetching children (depth != 0) then don't
|
||||
@@ -669,6 +703,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
Get the course with the given courseid (org/course/run)
|
||||
"""
|
||||
assert(isinstance(course_key, SlashSeparatedCourseKey))
|
||||
course_key = self._fill_in_run(course_key)
|
||||
location = course_key.make_usage_key('course', course_key.run)
|
||||
try:
|
||||
return self.get_item(location, depth=depth)
|
||||
@@ -685,6 +720,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
otherwise, do a case sensitive search
|
||||
"""
|
||||
assert(isinstance(course_key, SlashSeparatedCourseKey))
|
||||
course_key = self._fill_in_run(course_key)
|
||||
location = course_key.make_usage_key('course', course_key.run)
|
||||
if ignore_case:
|
||||
course_query = location.to_deprecated_son('_id.')
|
||||
@@ -873,6 +909,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
:param runtime: if you already have an xblock from the course, the xblock.runtime value
|
||||
:param fields: a dictionary of field names and values for the new xmodule
|
||||
"""
|
||||
location = location.replace(run=self._fill_in_run(location.course_key).run)
|
||||
# differs from split mongo in that I believe most of this logic should be above the persistence
|
||||
# layer but added it here to enable quick conversion. I'll need to reconcile these.
|
||||
if metadata is None:
|
||||
@@ -1073,6 +1110,7 @@ class MongoModuleStore(ModuleStoreWriteBase):
|
||||
"""
|
||||
Return an array of all of the locations (deprecated string format) for orphans in the course.
|
||||
"""
|
||||
course_key = self._fill_in_run(course_key)
|
||||
detached_categories = [name for name, __ in XBlock.load_tagged_classes("detached")]
|
||||
query = self._course_key_to_son(course_key)
|
||||
query['_id.category'] = {'$nin': detached_categories}
|
||||
|
||||
Reference in New Issue
Block a user