From d9d8d96e0540770f6925a2820296a55f978618ed Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 9 Aug 2013 10:32:28 -0400 Subject: [PATCH] also support a link rewriting on course import and clone for the following format /courses/[org]/[course]/[run]/. We just need to substitute in the new course-id. --- .../xmodule/modulestore/store_utilities.py | 56 ++++++++++++++++--- .../xmodule/modulestore/xml_importer.py | 17 ++++-- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/store_utilities.py b/common/lib/xmodule/xmodule/modulestore/store_utilities.py index a8ace87831..2ff94e6eda 100644 --- a/common/lib/xmodule/xmodule/modulestore/store_utilities.py +++ b/common/lib/xmodule/xmodule/modulestore/store_utilities.py @@ -40,13 +40,17 @@ def _jump_to_url_replace_regex(prefix): """.format(prefix=prefix) -def convert_to_portable_links(source_course_id, text): +def rewrite_nonportable_content_links(source_course_id, dest_course_id, text): """ Does a regex replace on non-portable links: /c4x///asset/ -> /static/ /jump_to/i4x:///// -> /jump_to_id/ + """ + org, course, run = source_course_id.split("/") + dest_org, dest_course, dest_run = dest_course_id.split("/") + def portable_asset_link_subtitution(match): quote = match.group('quote') rest = match.group('rest') @@ -57,15 +61,50 @@ def convert_to_portable_links(source_course_id, text): rest = match.group('rest') return "".join([quote, '/jump_to_id/'+rest, quote]) - org, course, run = source_course_id.split("/") + def generic_courseware_link_substitution(match): + quote = match.group('quote') + rest = match.group('rest') + dest_generic_courseware_lik_base = '/courses/{org}/{course}/{run}/'.format( + org=dest_org, course=dest_course, run=dest_run) + + return "".join([quote, dest_generic_courseware_lik_base+rest, quote]) + course_location = Location(['i4x', org, course, 'course', run]) - c4x_link_base = '{0}/'.format(StaticContent.get_base_url_path_for_course_assets(course_location)) - text = re.sub(_asset_url_replace_regex(c4x_link_base), portable_asset_link_subtitution, text) + # NOTE: ultimately link updating is not a hard requirement, so if something blows up with + # the regex subsitution, log the error and continue + try: + c4x_link_base = '{0}/'.format(StaticContent.get_base_url_path_for_course_assets(course_location)) + text = re.sub(_asset_url_replace_regex(c4x_link_base), portable_asset_link_subtitution, text) + except Exception, e: + logging.warning("Error going regex subtituion (0) on text = {1}.\n\nError msg = {2}".format( + c4x_link_base, text, str(e))) + pass - jump_to_link_base = '/courses/{org}/{course}/{run}/jump_to/i4x://{org}/{course}/'.format( - org=org, course=course, run=run) - text = re.sub(_jump_to_url_replace_regex(jump_to_link_base), portable_jump_to_link_substitution, text) + try: + jump_to_link_base = '/courses/{org}/{course}/{run}/jump_to/i4x://{org}/{course}/'.format( + org=org, course=course, run=run) + text = re.sub(_jump_to_url_replace_regex(jump_to_link_base), portable_jump_to_link_substitution, text) + except Exception, e: + logging.warning("Error going regex subtituion (0) on text = {1}.\n\nError msg = {2}".format( + jump_to_link_base, text, str(e))) + pass + + # Also, there commonly is a set of link URL's used in the format: + # /courses/// which will be broken if migrated to a different course_id + # so let's rewrite those, but the target will also be non-portable, + # + # Note: we only need to do this if we are changing course-id's + # + if source_course_id != dest_course_id: + try: + generic_courseware_link_base = '/courses/{org}/{course}/{run}/'.format( + org=org, course=course, run=run) + text = re.sub(_asset_url_replace_regex(generic_courseware_link_base), portable_asset_link_subtitution, text) + except Exception, e: + logging.warning("Error going regex subtituion (0) on text = {1}.\n\nError msg = {2}".format( + generic_courseware_link_base, text, str(e))) + pass return text @@ -87,7 +126,8 @@ def _clone_modules(modulestore, modules, source_location, dest_location): # NOTE: usage of the the internal module.xblock_kvs._data does not include any 'default' values for the fields data = module.xblock_kvs._data if isinstance(data, basestring): - data = convert_to_portable_links(source_location.course_id, data) + data = rewrite_nonportable_content_links( + source_location.course_id, dest_location.course_id, data) modulestore.update_item(module.location, data) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index c4edb2f6d6..f24dad7cf0 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -10,7 +10,7 @@ from xmodule.modulestore import Location from xmodule.contentstore.content import StaticContent from .inheritance import own_metadata from xmodule.errortracker import make_error_tracker -from .store_utilities import convert_to_portable_links +from .store_utilities import rewrite_nonportable_content_links log = logging.getLogger(__name__) @@ -128,7 +128,8 @@ def import_from_xml(store, data_dir, course_dirs=None, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}] # note, add 'progress' when we can support it on Edge - import_module(module, store, course_data_path, static_content_store, course_location) + import_module(module, store, course_data_path, static_content_store, course_location, + target_location_namespace if target_location_namespace else course_location) course_items.append(module) @@ -155,7 +156,8 @@ def import_from_xml(store, data_dir, course_dirs=None, if verbose: log.debug('importing module location {0}'.format(module.location)) - import_module(module, store, course_data_path, static_content_store, course_location) + import_module(module, store, course_data_path, static_content_store, course_location, + target_location_namespace if target_location_namespace else course_location) # now import any 'draft' items if draft_store is not None: @@ -173,7 +175,8 @@ def import_from_xml(store, data_dir, course_dirs=None, return xml_module_store, course_items -def import_module(module, store, course_data_path, static_content_store, source_course_location, allow_not_found=False): +def import_module(module, store, course_data_path, static_content_store, + source_course_location, dest_course_location, allow_not_found=False): content = {} for field in module.fields: if field.scope != Scope.content: @@ -193,7 +196,8 @@ def import_module(module, store, course_data_path, static_content_store, source_ if isinstance(module_data, basestring): # we want to convert all 'non-portable' links in the module_data (if it is a string) to # portable strings (e.g. /static/) - module_data = convert_to_portable_links(source_course_location.course_id, module_data) + module_data = rewrite_nonportable_content_links( + source_course_location.course_id, dest_course_location.course_id, module_data) if allow_not_found: store.update_item(module.location, module_data, allow_not_found=allow_not_found) @@ -265,7 +269,8 @@ def import_course_draft(xml_module_store, store, draft_store, course_data_path, del module.xml_attributes['parent_sequential_url'] del module.xml_attributes['index_in_children_list'] - import_module(module, draft_store, course_data_path, static_content_store, source_location_namespace, allow_not_found=True) + import_module(module, draft_store, course_data_path, static_content_store, + source_location_namespace, target_location_namespace, allow_not_found=True) for child in module.get_children(): _import_module(child)