From f4822c23dee665b676b2219bd01f6fef4afabef0 Mon Sep 17 00:00:00 2001
From: Chris Dodge <cdodge@edx.org>
Date: Tue, 30 Oct 2012 11:52:31 -0400
Subject: [PATCH] lots of tweeks to better support importing of existing
 courseware

---
 cms/djangoapps/contentstore/views.py          |   9 +-
 common/djangoapps/mitxmako/shortcuts.py       |   2 +-
 common/djangoapps/static_replace.py           |  24 +++-
 common/djangoapps/xmodule_modifiers.py        |   4 +-
 common/lib/xmodule/setup.py                   |   2 +
 .../xmodule/xmodule/contentstore/content.py   |   7 ++
 common/lib/xmodule/xmodule/course_module.py   |   3 +-
 .../lib/xmodule/xmodule/modulestore/mongo.py  |   2 +
 common/lib/xmodule/xmodule/modulestore/xml.py |  52 ++++-----
 .../xmodule/modulestore/xml_importer.py       | 103 ++++++++++++++----
 common/lib/xmodule/xmodule/template_module.py |  20 +++-
 lms/djangoapps/courseware/courses.py          |  26 +++--
 lms/djangoapps/courseware/module_render.py    |   3 +-
 lms/djangoapps/courseware/tests/tests.py      |  11 +-
 lms/djangoapps/courseware/views.py            |   2 +-
 15 files changed, 186 insertions(+), 84 deletions(-)

diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index be4a157de44..4d1cfed5533 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -287,9 +287,13 @@ def edit_unit(request, location):
     # TODO (cpennington): If we share units between courses,
     # this will need to change to check permissions correctly so as
     # to pick the correct parent subsection
+
+    logging.debug('looking for parent of {0}'.format(location))
+
     containing_subsection_locs = modulestore().get_parent_locations(location)
     containing_subsection = modulestore().get_item(containing_subsection_locs[0])
 
+    logging.debug('looking for parent of {0}'.format(containing_subsection.location))
     containing_section_locs = modulestore().get_parent_locations(containing_subsection.location)
     containing_section = modulestore().get_item(containing_section_locs[0])
 
@@ -997,7 +1001,8 @@ def import_course(request, org, course, name):
 
         data_root = path(settings.GITHUB_REPO_ROOT)
 
-        course_dir = data_root / "{0}-{1}-{2}".format(org, course, name)
+        course_subdir = "{0}-{1}-{2}".format(org, course, name)
+        course_dir = data_root / course_subdir
         if not course_dir.isdir():
             os.mkdir(course_dir)
 
@@ -1033,7 +1038,7 @@ def import_course(request, org, course, name):
                 shutil.move(r/fname, course_dir)
 
         module_store, course_items = import_from_xml(modulestore('direct'), settings.GITHUB_REPO_ROOT,
-            [course_dir], load_error_modules=False, static_content_store=contentstore(), target_location_namespace = Location(location))
+            [course_subdir], load_error_modules=False, static_content_store=contentstore(), target_location_namespace = Location(location))
 
         # we can blow this away when we're done importing.
         shutil.rmtree(course_dir)
diff --git a/common/djangoapps/mitxmako/shortcuts.py b/common/djangoapps/mitxmako/shortcuts.py
index ba22f2db207..181d3befd57 100644
--- a/common/djangoapps/mitxmako/shortcuts.py
+++ b/common/djangoapps/mitxmako/shortcuts.py
@@ -42,7 +42,7 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
         context_dictionary.update(context)
     # fetch and render template
     template = middleware.lookup[namespace].get_template(template_name)
-    return template.render(**context_dictionary)
+    return template.render_unicode(**context_dictionary)
 
 
 def render_to_response(template_name, dictionary, context_instance=None, namespace='main', **kwargs):
diff --git a/common/djangoapps/static_replace.py b/common/djangoapps/static_replace.py
index 58e2c8da159..ee63e3cf93e 100644
--- a/common/djangoapps/static_replace.py
+++ b/common/djangoapps/static_replace.py
@@ -5,6 +5,10 @@ from staticfiles.storage import staticfiles_storage
 from staticfiles import finders
 from django.conf import settings
 
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.xml import XMLModuleStore
+from xmodule.contentstore.content import StaticContent
+
 log = logging.getLogger(__name__)
 
 def try_staticfiles_lookup(path):
@@ -22,7 +26,7 @@ def try_staticfiles_lookup(path):
     return url
 
 
-def replace(static_url, prefix=None):
+def replace(static_url, prefix=None, course_namespace=None):
     if prefix is None:
         prefix = ''
     else:
@@ -41,13 +45,23 @@ def replace(static_url, prefix=None):
         return static_url.group(0)
     else:
         # don't error if file can't be found
-        url = try_staticfiles_lookup(prefix + static_url.group('rest'))
-        return "".join([quote, url, quote])
+        # cdodge: to support the change over to Mongo backed content stores, lets
+        # use the utility functions in StaticContent.py
+        if static_url.group('prefix') == '/static/' and not isinstance(modulestore(), XMLModuleStore):
+            if course_namespace is None:
+                raise BaseException('You must pass in course_namespace when remapping static content urls with MongoDB stores')
+            url = StaticContent.convert_legacy_static_url(static_url.group('rest'), course_namespace)
+        else:
+            url = try_staticfiles_lookup(prefix + static_url.group('rest'))
+
+        new_link = "".join([quote, url, quote])
+        return new_link
+
 
 
-def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/'):
+def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/', course_namespace=None):
     def replace_url(static_url):
-        return replace(static_url, staticfiles_prefix)
+        return replace(static_url, staticfiles_prefix, course_namespace = course_namespace)
 
     return re.sub(r"""
         (?x)                 # flags=re.VERBOSE
diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py
index 9473dfe26be..cda3d013cd0 100644
--- a/common/djangoapps/xmodule_modifiers.py
+++ b/common/djangoapps/xmodule_modifiers.py
@@ -50,7 +50,7 @@ def replace_course_urls(get_html, course_id):
         return replace_urls(get_html(), staticfiles_prefix='/courses/'+course_id, replace_prefix='/course/')
     return _get_html
 
-def replace_static_urls(get_html, prefix):
+def replace_static_urls(get_html, prefix, course_namespace=None):
     """
     Updates the supplied module with a new get_html function that wraps
     the old get_html function and substitutes urls of the form /static/...
@@ -59,7 +59,7 @@ def replace_static_urls(get_html, prefix):
 
     @wraps(get_html)
     def _get_html():
-        return replace_urls(get_html(), staticfiles_prefix=prefix)
+        return replace_urls(get_html(), staticfiles_prefix=prefix, course_namespace = course_namespace)
     return _get_html
 
 
diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py
index e9e78b57628..ff5be4d6dfe 100644
--- a/common/lib/xmodule/setup.py
+++ b/common/lib/xmodule/setup.py
@@ -37,6 +37,8 @@ setup(
             "discussion = xmodule.discussion_module:DiscussionDescriptor",
             "course_info = xmodule.html_module:HtmlDescriptor",
             "static_tab = xmodule.html_module:HtmlDescriptor",
+            "custom_tag_template = xmodule.raw_module:RawDescriptor",
+            "about = xmodule.html_module:HtmlDescriptor"
         ]
     }
 )
diff --git a/common/lib/xmodule/xmodule/contentstore/content.py b/common/lib/xmodule/xmodule/contentstore/content.py
index 6dcf70fbe1f..a7a76fa2421 100644
--- a/common/lib/xmodule/xmodule/contentstore/content.py
+++ b/common/lib/xmodule/xmodule/contentstore/content.py
@@ -62,6 +62,13 @@ class StaticContent(object):
     @staticmethod
     def get_id_from_path(path):
         return get_id_from_location(get_location_from_path(path))
+
+    @staticmethod
+    def convert_legacy_static_url(path, course_namespace):
+        loc = StaticContent.compute_location(course_namespace.org, course_namespace.course, path)
+        return StaticContent.get_url_path_from_location(loc)
+
+
     
 
 class ContentStore(object):
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index ae3c01f6393..94ab02cf398 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -202,7 +202,7 @@ class CourseDescriptor(SequenceDescriptor):
         # Try to load grading policy
         paths = ['grading_policy.json']
         if policy_dir:
-            paths = [policy_dir + 'grading_policy.json'] + paths
+            paths = [policy_dir + '/grading_policy.json'] + paths
 
         policy = json.loads(cls.read_grading_policy(paths, system))
         
@@ -394,3 +394,4 @@ class CourseDescriptor(SequenceDescriptor):
         return self.location.org
 
 
+
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index 164bfd3590f..deef3af3bfa 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -1,5 +1,6 @@
 import pymongo
 import sys
+import logging
 
 from bson.son import SON
 from fs.osfs import OSFS
@@ -49,6 +50,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
                 self.load_item, resources_fs, error_tracker, render_template)
         self.modulestore = modulestore
         self.module_data = module_data
+
         self.default_class = default_class
 
     def load_item(self, location):
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index ddb437afe28..6794703998e 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -427,42 +427,38 @@ class XMLModuleStore(ModuleStoreBase):
 
             # now import all pieces of course_info which is expected to be stored
             # in <content_dir>/info or <content_dir>/info/<url_name>
-            if url_name:
-                info_path = self.data_dir / course_dir / 'info' / url_name
-
-            if not os.path.exists(info_path):
-                info_path = self.data_dir / course_dir / 'info'
-
-            # we have a fixed number of .html info files that we expect there
-            for info_filename in ['handouts', 'guest_handouts', 'updates', 'guest_updates']:
-                filepath = info_path / info_filename + '.html'
-                if os.path.exists(filepath):
-                    with open(filepath) as info_file:
-                        html = info_file.read().decode('utf-8')
-                        loc = Location('i4x', course_descriptor.location.org, course_descriptor.location.course, 'course_info', info_filename)
-                        info_module = HtmlDescriptor(system, definition={'data' : html}, **{'location' : loc})
-                        self.modules[course_id][info_module.location] = info_module
+            self.load_extra_content(system, course_descriptor, 'course_info', self.data_dir / course_dir / 'info', course_dir, url_name)
 
             # now import all static tabs which are expected to be stored in
-            # in <content_dir>/tabs or <content_dir>/tabs/<url_name>
-            if url_name:
-                tabs_path = self.data_dir / course_dir / 'tabs' / url_name
+            # in <content_dir>/tabs or <content_dir>/tabs/<url_name>           
+            self.load_extra_content(system, course_descriptor, 'static_tab', self.data_dir / course_dir / 'tabs', course_dir, url_name)
 
-            if not os.path.exists(tabs_path):
-                tabs_path = self.data_dir / course_dir / 'tabs'
+            self.load_extra_content(system, course_descriptor, 'custom_tag_template', self.data_dir / course_dir / 'custom_tags', course_dir, url_name)
 
-            for tab_filepath in glob.glob(tabs_path / '*.html'):
-                with open(tab_filepath) as tab_file:
-                    html = tab_file.read().decode('utf-8')
-                    # tabs are referenced in policy.json through a 'slug' which is just the filename without the .html suffix
-                    slug = os.path.splitext(os.path.basename(tab_filepath))[0]
-                    loc = Location('i4x', course_descriptor.location.org, course_descriptor.location.course, 'static_tab', slug)
-                    tab_module = HtmlDescriptor(system, definition={'data' : html}, **{'location' : loc})
-                    self.modules[course_id][tab_module.location] = tab_module               
+            self.load_extra_content(system, course_descriptor, 'about', self.data_dir / course_dir / 'about', course_dir, url_name)
 
             log.debug('========> Done with course import from {0}'.format(course_dir))
             return course_descriptor
 
+    def load_extra_content(self, system, course_descriptor, category, base_dir, course_dir, url_name):
+        if url_name:
+            path =  base_dir / url_name
+
+        if not os.path.exists(path):
+            path = base_dir
+
+        for filepath in glob.glob(path/ '*'):
+            with open(filepath) as f:
+                try:
+                    html = f.read().decode('utf-8')
+                    # tabs are referenced in policy.json through a 'slug' which is just the filename without the .html suffix
+                    slug = os.path.splitext(os.path.basename(filepath))[0]
+                    loc = Location('i4x', course_descriptor.location.org, course_descriptor.location.course, category, slug)
+                    module = HtmlDescriptor(system, definition={'data' : html}, **{'location' : loc})
+                    module.metadata['data_dir'] = course_dir
+                    self.modules[course_descriptor.id][module.location] = module   
+                except Exception, e:
+                    logging.exception("Failed to load {0}. Skipping... Exception: {1}".format(filepath, str(e)))          
 
     def get_instance(self, course_id, location, depth=0):
         """
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index bced371001a..6a7d44489bf 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -1,6 +1,7 @@
 import logging
 import os
 import mimetypes
+from lxml.html import rewrite_links as lxml_rewrite_links
 
 from .xml import XMLModuleStore
 from .exceptions import DuplicateItemError
@@ -9,7 +10,7 @@ from xmodule.contentstore.content import StaticContent, XASSET_SRCREF_PREFIX
 
 log = logging.getLogger(__name__)
 
-def import_static_content(modules, data_dir, static_content_store):
+def import_static_content(modules, data_dir, static_content_store, target_location_namespace):
     
     remap_dict = {}
 
@@ -31,15 +32,13 @@ def import_static_content(modules, data_dir, static_content_store):
     # now import all static assets
     static_dir = '{0}/static/'.format(course_data_dir)
 
-    logging.debug("Importing static assets in {0}".format(static_dir))
-
     for dirname, dirnames, filenames in os.walk(static_dir):
         for filename in filenames:
 
             try:
                 content_path = os.path.join(dirname, filename)
                 fullname_with_subpath = content_path.replace(static_dir, '')  # strip away leading path from the name
-                content_loc = StaticContent.compute_location(course_loc.org, course_loc.course, fullname_with_subpath)
+                content_loc = StaticContent.compute_location(target_location_namespace.org, target_location_namespace.course, fullname_with_subpath)
                 mime_type = mimetypes.guess_type(filename)[0]
 
                 f = open(content_path, 'rb')
@@ -59,12 +58,50 @@ def import_static_content(modules, data_dir, static_content_store):
 
                 #store the remapping information which will be needed to subsitute in the module data
                 remap_dict[fullname_with_subpath] = content_loc.name
-
             except:
-                raise    
+                raise 
 
     return remap_dict
 
+def verify_content_links(module, base_dir, static_content_store, link, remap_dict = None):
+    if link.startswith('/static/'):
+        # yes, then parse out the name
+        path = link[len('/static/'):]
+
+        static_pathname = base_dir / path
+
+        if os.path.exists(static_pathname):
+            try:
+                content_loc = StaticContent.compute_location(module.location.org, module.location.course, path)
+                filename = os.path.basename(path)
+                mime_type = mimetypes.guess_type(filename)[0]
+
+                f = open(static_pathname, 'rb')
+                data = f.read()
+                f.close()
+
+                content = StaticContent(content_loc, filename, mime_type, data) 
+
+                # first let's save a thumbnail so we can get back a thumbnail location
+                thumbnail_content = static_content_store.generate_thumbnail(content)
+
+                if thumbnail_content is not None:
+                    content.thumbnail_location = thumbnail_content.location
+
+                #then commit the content
+                static_content_store.save(content)   
+
+                new_link = StaticContent.get_url_path_from_location(content_loc)   
+
+                if remap_dict is not None:
+                    remap_dict[link] = new_link
+
+                return new_link                 
+            except Exception, e:
+                logging.exception('Skipping failed content load from {0}. Exception: {1}'.format(path, e))
+
+    return link
+
 def import_from_xml(store, data_dir, course_dirs=None, 
                     default_class='xmodule.raw_module.RawDescriptor',
                     load_error_modules=True, static_content_store=None, target_location_namespace = None):
@@ -94,15 +131,20 @@ def import_from_xml(store, data_dir, course_dirs=None,
     # method on XmlModuleStore.
     course_items = []
     for course_id in module_store.modules.keys():
-        remap_dict = {}
+
+        course_data_dir = None
+        for module in module_store.modules[course_id].itervalues():
+            if module.category == 'course':
+                course_data_dir = module.metadata['data_dir']
+
         if static_content_store is not None:
-            remap_dict = import_static_content(module_store.modules[course_id], data_dir, static_content_store)
+            import_static_content(module_store.modules[course_id], data_dir, static_content_store, 
+                target_location_namespace if target_location_namespace is not None else  module_store.modules[course_id].location)
 
         for module in module_store.modules[course_id].itervalues():
 
             # remap module to the new namespace
             if target_location_namespace is not None:
-
                 # This looks a bit wonky as we need to also change the 'name' of the imported course to be what
                 # the caller passed in
                 if module.location.category != 'course':
@@ -120,8 +162,8 @@ def import_from_xml(store, data_dir, course_dirs=None,
                         child_loc = Location(child)
                         new_child_loc = child_loc._replace(tag=target_location_namespace.tag, org=target_location_namespace.org, 
                             course=target_location_namespace.course)
-                        
-                        new_locs.append(new_child_loc)
+
+                        new_locs.append(new_child_loc.url())
 
                     module.definition['children'] = new_locs
 
@@ -129,22 +171,37 @@ def import_from_xml(store, data_dir, course_dirs=None,
             if module.category == 'course':
                 # HACK: for now we don't support progress tabs. There's a special metadata configuration setting for this.
                 module.metadata['hide_progress_tab'] = True
+
+                # a bit of a hack, but typically the "course image" which is shown on marketing pages is hard coded to /images/course_image.jpg
+                # so let's make sure we import in case there are no other references to it in the modules
+                verify_content_links(module, data_dir / course_data_dir, static_content_store, '/static/images/course_image.jpg')
+
                 course_items.append(module)
 
             if 'data' in module.definition:
                 module_data = module.definition['data']
 
-                # cdodge: update any references to the static content paths
-                # This is a bit brute force - simple search/replace - but it's unlikely that such references to '/static/....'
-                # would occur naturally (in the wild)
-                # @TODO, sorry a bit of technical debt here. There are some helper methods in xmodule_modifiers.py and static_replace.py which could
-                # better do the url replace on the html rendering side rather than on the ingest side
-                try:
-                    if '/static/' in module_data:
-                        for subkey in remap_dict.keys():
-                            module_data = module_data.replace('/static/' + subkey, 'xasset:' + remap_dict[subkey])
-                except:
-                    pass    # part of the techincal debt is that module_data might not be a string (e.g. ABTest)
+                # cdodge: now go through any link references to '/static/' and make sure we've imported
+                # it as a StaticContent asset
+                try:   
+                    remap_dict = {}
+                    
+                    # use the rewrite_links as a utility means to enumerate through all links
+                    # in the module data. We use that to load that reference into our asset store
+                    # IMPORTANT: There appears to be a bug in lxml.rewrite_link which makes us not be able to
+                    # do the rewrites natively in that code.
+                    # For example, what I'm seeing is <img src='foo.jpg' />   ->   <img src='bar.jpg'>  
+                    # Note the dropped element closing tag. This causes the LMS to fail when rendering modules - that's
+                    # no good, so we have to do this kludge
+                    if isinstance(module_data, str) or isinstance(module_data, unicode):   # some module 'data' fields are non strings which blows up the link traversal code
+                        lxml_rewrite_links(module_data, lambda link: verify_content_links(module, data_dir / course_data_dir, 
+                            static_content_store, link, remap_dict))                     
+
+                        for key in remap_dict.keys():
+                            module_data = module_data.replace(key, remap_dict[key])
+
+                except Exception, e:
+                    logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location))
 
                 store.update_item(module.location, module_data)
 
@@ -156,4 +213,6 @@ def import_from_xml(store, data_dir, course_dirs=None,
             # inherited metadata everywhere.
             store.update_metadata(module.location, dict(module.own_metadata))
 
+
+
     return module_store, course_items
diff --git a/common/lib/xmodule/xmodule/template_module.py b/common/lib/xmodule/xmodule/template_module.py
index 4df1c4ee3a7..cca2cb0ca8b 100644
--- a/common/lib/xmodule/xmodule/template_module.py
+++ b/common/lib/xmodule/xmodule/template_module.py
@@ -2,6 +2,7 @@ from xmodule.x_module import XModule
 from xmodule.raw_module import RawDescriptor
 from lxml import etree
 from mako.template import Template
+from xmodule.modulestore.django import modulestore
 
 
 class CustomTagModule(XModule):
@@ -40,8 +41,7 @@ class CustomTagDescriptor(RawDescriptor):
     module_class = CustomTagModule
     template_dir_name = 'customtag'
 
-    @staticmethod
-    def render_template(system, xml_data):
+    def render_template(self, system, xml_data):
         '''Render the template, given the definition xml_data'''
         xmltree = etree.fromstring(xml_data)
         if 'impl' in xmltree.attrib:
@@ -57,15 +57,23 @@ class CustomTagDescriptor(RawDescriptor):
                                 .format(location))
 
         params = dict(xmltree.items())
-        with system.resources_fs.open('custom_tags/{name}'
-                                   .format(name=template_name)) as template:
-            return Template(template.read()).render(**params)
+
+        # cdodge: look up the template as a module
+        template_loc = self.location._replace(category='custom_tag_template', name=template_name)
+
+        template_module = modulestore().get_item(template_loc)
+        template_module_data = template_module.definition['data']
+        template = Template(template_module_data)
+        return template.render(**params)
 
 
     def __init__(self, system, definition, **kwargs):
         '''Render and save the template for this descriptor instance'''
         super(CustomTagDescriptor, self).__init__(system, definition, **kwargs)
-        self.rendered_html = self.render_template(system, definition['data'])
+
+    @property
+    def rendered_html(self):
+        return self.render_template(self.system, self.definition['data'])
 
     def export_to_file(self):
         """
diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py
index 0103e9de004..c4dc6fa77b9 100644
--- a/lms/djangoapps/courseware/courses.py
+++ b/lms/djangoapps/courseware/courses.py
@@ -14,6 +14,7 @@ from module_render import get_module
 from xmodule.course_module import CourseDescriptor
 from xmodule.modulestore import Location
 from xmodule.modulestore.django import modulestore
+from xmodule.contentstore.content import StaticContent
 from xmodule.modulestore.xml import XMLModuleStore
 from xmodule.modulestore.exceptions import ItemNotFoundError
 from xmodule.x_module import XModule
@@ -68,8 +69,13 @@ def get_opt_course_with_access(user, course_id, action):
 def course_image_url(course):
     """Try to look up the image url for the course.  If it's not found,
     log an error and return the dead link"""
-    path = course.metadata['data_dir'] + "/images/course_image.jpg"
-    return try_staticfiles_lookup(path)
+    if isinstance(modulestore(), XMLModuleStore):
+        path = course.metadata['data_dir'] + "/images/course_image.jpg"
+        return try_staticfiles_lookup(path)
+    else:
+        loc = course.location._replace(tag='c4x', category='asset', name='images_course_image.jpg')
+        path = StaticContent.get_url_path_from_location(loc)
+        return path
 
 def find_file(fs, dirs, filename):
     """
@@ -123,14 +129,12 @@ def get_course_about_section(course, section_key):
                        'effort', 'end_date', 'prerequisites', 'ocw_links']:
 
         try:
-            fs = course.system.resources_fs
-            # first look for a run-specific version
-            dirs = [path("about") / course.url_name, path("about")]
-            filepath = find_file(fs, dirs, section_key + ".html")
-            with fs.open(filepath) as htmlFile:
-                return replace_urls(htmlFile.read().decode('utf-8'),
-                                    course.metadata['data_dir'])
-        except ResourceNotFoundError:
+            loc = course.location._replace(category='about', name=section_key)
+            item = modulestore().get_instance(course.id, loc)
+
+            return item.definition['data']
+
+        except ItemNotFoundError:
             log.warning("Missing about section {key} in course {url}".format(
                 key=section_key, url=course.location.url()))
             return None
@@ -160,8 +164,6 @@ def get_course_info_section(request, cache, course, section_key):
     loc = Location(course.location.tag, course.location.org, course.location.course, 'course_info', section_key)
     course_module = get_module(request.user, request, loc, cache, course.id)
 
-    logging.debug('course_module = {0}'.format(course_module))
-
     html = ''
 
     if course_module is not None:
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 3c9958b36d9..d9f87d77b6c 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -256,7 +256,8 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
 
     module.get_html = replace_static_urls(
         wrap_xmodule(module.get_html, module, 'xmodule_display.html'),
-        module.metadata['data_dir'] if 'data_dir' in module.metadata else '')
+        module.metadata['data_dir'] if 'data_dir' in module.metadata else '', 
+        course_namespace = module.location._replace(category=None, name=None))
 
     # Allow URLs of the form '/course/' refer to the root of multicourse directory
     #   hierarchy of this course
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index 3de344b1566..8bdcf59815e 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -247,6 +247,7 @@ class PageLoader(ActivateLoginTestCase):
         all_ok = True
         for descriptor in modstore.get_items(
                 Location(None, None, None, None, None)):
+
             n += 1
             print "Checking ", descriptor.location.url()
             #print descriptor.__class__, descriptor.location
@@ -256,9 +257,13 @@ class PageLoader(ActivateLoginTestCase):
             msg = str(resp.status_code)
 
             if resp.status_code != 302:
-                msg = "ERROR " + msg
-                all_ok = False
-                num_bad += 1
+                # cdodge: we're adding new module category as part of the Studio work
+                # such as 'course_info', etc, which are not supposed to be jump_to'able
+                # so a successful return value here should be a 404
+                if descriptor.location.category not in ['about', 'static_tab', 'course_info', 'custom_tag_template'] or resp.status_code != 404:
+                    msg = "ERROR " + msg
+                    all_ok = False
+                    num_bad += 1
             print msg
             self.assertTrue(all_ok)  # fail fast
 
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index fc834768e7e..bacc41fdf49 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -421,7 +421,7 @@ def course_about(request, course_id):
                             settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'))
 
     return render_to_response('portal/course_about.html',
-                              {'course': course,
+                              { 'course': course,
                                'registered': registered,
                                'course_target': course_target,
                                'show_courseware_link' : show_courseware_link})
-- 
GitLab