Beispiel #1
0
    def handle(self, *args, **options):
        """
        Gathers all course keys in the modulestore and updates the course tabs
        if there are any new default course tabs. Else, makes no updates.
        """
        store = modulestore()
        course_keys = sorted(
            (course.id for course in store.get_course_summaries()),
            key=
            str  # Different types of CourseKeys can't be compared without this.
        )
        logger.info(f'{len(course_keys)} courses read from modulestore.')

        for course_key in course_keys:
            try:
                course = store.get_course(course_key, depth=1)
                existing_tabs = {tab.type for tab in course.tabs}
                CourseTabList.initialize_default(course)
                new_tabs = {tab.type for tab in course.tabs}

                if existing_tabs != new_tabs:
                    # This will trigger the Course Published Signal which is necessary to update
                    # the corresponding Course Overview
                    logger.info(f'Updating tabs for {course_key}.')
                    store.update_item(course,
                                      ModuleStoreEnum.UserID.mgmt_command)
                    logger.info(f'Successfully updated tabs for {course_key}.')
            except Exception as err:  # pylint: disable=broad-except
                logger.exception(err)
                logger.error(
                    f'Course {course_key} encountered an Exception while trying to update.'
                )
Beispiel #2
0
    def handle(self, *args, **options):
        """
        Gathers all course keys in the modulestore and updates the course tabs
        if there are any new default course tabs. Else, makes no updates.
        """
        store = modulestore()
        all_course_keys = sorted(
            (course.id for course in store.get_course_summaries()),
            key=
            str  # Different types of CourseKeys can't be compared without this.
        )

        config = BackfillCourseTabsConfig.current()
        start = config.start_index if config.enabled and config.start_index >= 0 else 0
        end = (start +
               config.count) if config.enabled and config.count > 0 else len(
                   all_course_keys)
        course_keys = all_course_keys[start:end]

        logger.info(
            f'{len(all_course_keys)} courses read from modulestore. Processing {start} to {end}.'
        )

        error_keys = []
        for course_key in course_keys:
            try:
                course = store.get_course(course_key, depth=1)
                existing_tabs = {tab.type for tab in course.tabs}
                CourseTabList.initialize_default(course)
                new_tabs = {tab.type for tab in course.tabs}

                if existing_tabs != new_tabs:
                    # This will trigger the Course Published Signal which is necessary to update
                    # the corresponding Course Overview
                    logger.info(f'Updating tabs for {course_key}.')
                    store.update_item(course,
                                      ModuleStoreEnum.UserID.mgmt_command)
                    logger.info(f'Successfully updated tabs for {course_key}.')
            except Exception as err:  # pylint: disable=broad-except
                logger.exception(err)
                logger.error(
                    f'Course {course_key} encountered an Exception while trying to update.'
                )
                error_keys.append(course_key)

        if error_keys:
            msg = 'The following courses encountered errors and were not updated:\n'
            for error_key in error_keys:
                msg += f' - {error_key}\n'
            logger.info(msg)
Beispiel #3
0
    def test_is_added_in_courseware_tabs(self):
        tabs_list = CourseTabList()
        tabs_list.from_json([])

        # The tab should be added anyway,
        # unfortunately the platform don't have a dynamic loading so far
        # check the `CourseTabList` class for more details about this problem
        course = CourseFactory.create()
        course.enable_university_id = False

        tabs_list.initialize_default(course)

        course_tab_types = [tab.type for tab in course.tabs]
        self.assertIn('university_id', course_tab_types)
        self.assertLess(course_tab_types.index('progress'), course_tab_types.index('university_id'),
                        'Should appear after the progress tab')
Beispiel #4
0
    def __init__(self, *args, **kwargs):
        """
        Expects the same arguments as XModuleDescriptor.__init__
        """
        super(CourseDescriptor, self).__init__(*args, **kwargs)
        _ = self.runtime.service(self, "i18n").ugettext

        if self.wiki_slug is None:
            if isinstance(self.location, Location):
                self.wiki_slug = self.location.course
            elif isinstance(self.location, CourseLocator):
                self.wiki_slug = self.location.package_id or self.display_name

        if self.due_date_display_format is None and self.show_timezone is False:
            # For existing courses with show_timezone set to False (and no due_date_display_format specified),
            # set the due_date_display_format to what would have been shown previously (with no timezone).
            # Then remove show_timezone so that if the user clears out the due_date_display_format,
            # they get the default date display.
            self.due_date_display_format = "DATE_TIME"
            delattr(self, 'show_timezone')

        # NOTE: relies on the modulestore to call set_grading_policy() right after
        # init.  (Modulestore is in charge of figuring out where to load the policy from)

        # NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
        #   disable the syllabus content for courses that do not provide a syllabus
        if self.system.resources_fs is None:
            self.syllabus_present = False
        else:
            self.syllabus_present = self.system.resources_fs.exists(
                path('syllabus'))

        self._grading_policy = {}
        self.set_grading_policy(self.grading_policy)

        if self.discussion_topics == {}:
            self.discussion_topics = {
                _('General'): {
                    'id': self.location.html_id()
                }
            }

        if not getattr(self, "tabs", []):
            CourseTabList.initialize_default(self)
Beispiel #5
0
    def static_updater(self, course, source_courselike, courselike_key, dest_id, runtime):
        """
        Update special static assets, such as PDF textbooks and wiki resources.
        """
        for entry in course.pdf_textbooks:
            for chapter in entry.get('chapters', []):
                if StaticContent.is_c4x_path(chapter.get('url', '')):
                    asset_key = StaticContent.get_location_from_path(chapter['url'])
                    chapter['url'] = StaticContent.get_static_path_from_location(asset_key)

        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
        # values then remap it so that the wiki does not point to the old wiki.
        if courselike_key != course.id:
            if self.xml_module_store.course_run is None:
                original_course_run = courselike_key.run
            else:
                original_course_run = self.xml_module_store.course_run

            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                courselike_key.org,
                courselike_key.course,
                original_course_run,
            )
            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == courselike_key.course:
                course.wiki_slug = u'{0}.{1}.{2}'.format(
                    course.id.org,
                    course.id.course,
                    course.id.run,
                )

        # cdodge: more hacks (what else). Seems like we have a
        # problem when importing a course (like 6.002) which
        # does not have any tabs defined in the policy file.
        # The import goes fine and then displays fine in LMS,
        # but if someone tries to add a new tab in the CMS, then
        # the LMS barfs because it expects that -- if there are
        # *any* tabs -- then there at least needs to be
        # some predefined ones
        if course.tabs is None or len(course.tabs) == 0:
            CourseTabList.initialize_default(course)
Beispiel #6
0
    def __init__(self, *args, **kwargs):
        """
        Expects the same arguments as XModuleDescriptor.__init__
        """
        super(CourseDescriptor, self).__init__(*args, **kwargs)
        _ = self.runtime.service(self, "i18n").ugettext

        if self.wiki_slug is None:
            if isinstance(self.location, Location):
                self.wiki_slug = self.location.course
            elif isinstance(self.location, CourseLocator):
                self.wiki_slug = self.location.package_id or self.display_name

        if self.due_date_display_format is None and self.show_timezone is False:
            # For existing courses with show_timezone set to False (and no due_date_display_format specified),
            # set the due_date_display_format to what would have been shown previously (with no timezone).
            # Then remove show_timezone so that if the user clears out the due_date_display_format,
            # they get the default date display.
            self.due_date_display_format = "DATE_TIME"
            delattr(self, 'show_timezone')

        # NOTE: relies on the modulestore to call set_grading_policy() right after
        # init.  (Modulestore is in charge of figuring out where to load the policy from)

        # NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
        #   disable the syllabus content for courses that do not provide a syllabus
        if self.system.resources_fs is None:
            self.syllabus_present = False
        else:
            self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))

        self._grading_policy = {}
        self.set_grading_policy(self.grading_policy)

        if self.discussion_topics == {}:
            self.discussion_topics = {_('General'): {'id': self.location.html_id()}}

        if not getattr(self, "tabs", []):
            CourseTabList.initialize_default(self)
Beispiel #7
0
    def static_updater(self, course, source_courselike, courselike_key,
                       dest_id, runtime):
        """
        Update special static assets, such as PDF textbooks and wiki resources.
        """
        for entry in course.pdf_textbooks:
            for chapter in entry.get('chapters', []):
                if StaticContent.is_c4x_path(chapter.get('url', '')):
                    asset_key = StaticContent.get_location_from_path(
                        chapter['url'])
                    chapter[
                        'url'] = StaticContent.get_static_path_from_location(
                            asset_key)

        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
        # values then remap it so that the wiki does not point to the old wiki.
        if courselike_key != course.id:
            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                courselike_key.org, courselike_key.course, courselike_key.run)
            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == courselike_key.course:
                course.wiki_slug = u'{0}.{1}.{2}'.format(
                    course.id.org,
                    course.id.course,
                    course.id.run,
                )

        # cdodge: more hacks (what else). Seems like we have a
        # problem when importing a course (like 6.002) which
        # does not have any tabs defined in the policy file.
        # The import goes fine and then displays fine in LMS,
        # but if someone tries to add a new tab in the CMS, then
        # the LMS barfs because it expects that -- if there are
        # *any* tabs -- then there at least needs to be
        # some predefined ones
        if course.tabs is None or len(course.tabs) == 0:
            CourseTabList.initialize_default(course)
Beispiel #8
0
def _import_course_module(
        store, runtime, user_id, data_dir, course_key, dest_course_id, source_course, do_import_static,
        verbose,
):
    if verbose:
        log.debug("Scanning {0} for course module...".format(course_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
    # first into the store
    course_data_path = path(data_dir) / source_course.data_dir

    log.debug(u'======> IMPORTING course {course_key}'.format(
        course_key=course_key,
    ))

    if not do_import_static:
        # for old-style xblock where this was actually linked to kvs
        source_course.static_asset_path = source_course.data_dir
        source_course.save()
        log.debug('course static_asset_path={path}'.format(
            path=source_course.static_asset_path
        ))

    log.debug('course data_dir={0}'.format(source_course.data_dir))

    with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, dest_course_id):

        course = _import_module_and_update_references(
            source_course, store, user_id,
            course_key,
            dest_course_id,
            do_import_static=do_import_static,
            runtime=runtime,
        )

        for entry in course.pdf_textbooks:
            for chapter in entry.get('chapters', []):
                if StaticContent.is_c4x_path(chapter.get('url', '')):
                    asset_key = StaticContent.get_location_from_path(chapter['url'])
                    chapter['url'] = StaticContent.get_static_path_from_location(asset_key)

        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
        # values then remap it so that the wiki does not point to the old wiki.
        if course_key != course.id:
            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                course_key.org,
                course_key.course,
                course_key.run
            )
            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course:
                course.wiki_slug = u'{0}.{1}.{2}'.format(
                    course.id.org,
                    course.id.course,
                    course.id.run,
                )

        # cdodge: more hacks (what else). Seems like we have a
        # problem when importing a course (like 6.002) which
        # does not have any tabs defined in the policy file.
        # The import goes fine and then displays fine in LMS,
        # but if someone tries to add a new tab in the CMS, then
        # the LMS barfs because it expects that -- if there are
        # *any* tabs -- then there at least needs to be
        # some predefined ones
        if course.tabs is None or len(course.tabs) == 0:
            CourseTabList.initialize_default(course)

        store.update_item(course, user_id)
    return course, course_data_path
Beispiel #9
0
def import_from_xml(
        store, user_id, data_dir, course_dirs=None,
        default_class='xmodule.raw_module.RawDescriptor',
        load_error_modules=True, static_content_store=None,
        target_course_id=None, verbose=False,
        do_import_static=True, create_new_course_if_not_present=False):
    """
    Import the specified xml data_dir into the "store" modulestore,
    using org and course as the location org and course.

    course_dirs: If specified, the list of course_dirs to load. Otherwise, load
    all course dirs

    target_course_id is the CourseKey that all modules should be remapped to
    after import off disk. We do this remapping as a post-processing step
    because there's logic in the importing which expects a 'url_name' as an
    identifier to where things are on disk
    e.g. ../policies/<url_name>/policy.json as well as metadata keys in
    the policy.json. so we need to keep the original url_name during import

    :param do_import_static:
        if False, then static files are not imported into the static content
        store. This can be employed for courses which have substantial
        unchanging static content, which is to inefficient to import every
        time the course is loaded. Static content for some courses may also be
        served directly by nginx, instead of going through django.

    : create_new_course_if_not_present:
        If True, then a new course is created if it doesn't already exist.
        The check for existing courses is case-insensitive.
    """

    xml_module_store = XMLModuleStore(
        data_dir,
        default_class=default_class,
        course_dirs=course_dirs,
        load_error_modules=load_error_modules,
        xblock_mixins=store.xblock_mixins,
        xblock_select=store.xblock_select,
    )

    # If we're going to remap the course_id, then we can only do that with
    # a single course
    if target_course_id:
        assert(len(xml_module_store.modules) == 1)

    # NOTE: the XmlModuleStore does not implement get_items()
    # which would be a preferable means to enumerate the entire collection
    # of course modules. It will be left as a TBD to implement that
    # method on XmlModuleStore.
    course_items = []

    with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
        for course_key in xml_module_store.modules.keys():

            if target_course_id is not None:
                dest_course_id = target_course_id
            else:
                dest_course_id = course_key

            # Creates a new course if it doesn't already exist
            if create_new_course_if_not_present and not store.has_course(dest_course_id, ignore_case=True):
                try:
                    store.create_course(dest_course_id.org, dest_course_id.course, dest_course_id.run, user_id)
                except InvalidLocationError:
                    # course w/ same org and course exists
                    log.debug(
                        "Skipping import of course with id, {0},"
                        "since it collides with an existing one".format(dest_course_id)
                    )
                    continue

            with store.bulk_write_operations(dest_course_id):
                course_data_path = None

                if verbose:
                    log.debug("Scanning {0} for course module...".format(course_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
                # first into the store
                for module in xml_module_store.modules[course_key].itervalues():
                    if module.scope_ids.block_type == 'course':
                        course_data_path = path(data_dir) / module.data_dir

                        log.debug(u'======> IMPORTING course {course_key}'.format(
                            course_key=course_key,
                        ))

                        if not do_import_static:
                            # for old-style xblock where this was actually linked to kvs
                            module.static_asset_path = module.data_dir
                            module.save()
                            log.debug('course static_asset_path={path}'.format(
                                path=module.static_asset_path
                            ))

                        log.debug('course data_dir={0}'.format(module.data_dir))

                        course = _import_module_and_update_references(
                            module, store, user_id,
                            course_key,
                            dest_course_id,
                            do_import_static=do_import_static
                        )

                        for entry in course.pdf_textbooks:
                            for chapter in entry.get('chapters', []):
                                if StaticContent.is_c4x_path(chapter.get('url', '')):
                                    asset_key = StaticContent.get_location_from_path(chapter['url'])
                                    chapter['url'] = StaticContent.get_static_path_from_location(asset_key)

                        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
                        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
                        # values then remap it so that the wiki does not point to the old wiki.
                        if course_key != course.id:
                            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                                course_key.org,
                                course_key.course,
                                course_key.run
                            )
                            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course:
                                course.wiki_slug = u'{0}.{1}.{2}'.format(
                                    course.id.org,
                                    course.id.course,
                                    course.id.run,
                                )

                        # cdodge: more hacks (what else). Seems like we have a
                        # problem when importing a course (like 6.002) which
                        # does not have any tabs defined in the policy file.
                        # The import goes fine and then displays fine in LMS,
                        # but if someone tries to add a new tab in the CMS, then
                        # the LMS barfs because it expects that -- if there are
                        # *any* tabs -- then there at least needs to be
                        # some predefined ones
                        if course.tabs is None or len(course.tabs) == 0:
                            CourseTabList.initialize_default(course)

                        store.update_item(course, user_id)

                        course_items.append(course)
                        break

                # TODO: shouldn't this raise an exception if course wasn't found?

                # then import all the static content
                if static_content_store is not None and do_import_static:
                    # first pass to find everything in /static/
                    import_static_content(
                        course_data_path, static_content_store,
                        dest_course_id, subpath='static', verbose=verbose
                    )

                elif verbose and not do_import_static:
                    log.debug(
                        "Skipping import of static content, "
                        "since do_import_static={0}".format(do_import_static)
                    )

                # 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
                # "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
                # content into the "static_import" subdir.

                simport = 'static_import'
                if os.path.exists(course_data_path / simport):
                    import_static_content(
                        course_data_path, static_content_store,
                        dest_course_id, subpath=simport, verbose=verbose
                    )

                # now loop through all the modules
                for module in xml_module_store.modules[course_key].itervalues():
                    if module.scope_ids.block_type == 'course':
                        # we've already saved the course module up at the top
                        # of the loop so just skip over it in the inner loop
                        continue

                    if verbose:
                        log.debug('importing module location {loc}'.format(
                            loc=module.location
                        ))

                    _import_module_and_update_references(
                        module, store,
                        user_id,
                        course_key,
                        dest_course_id,
                        do_import_static=do_import_static,
                        runtime=course.runtime
                    )

                # finally, publish the course
                store.publish(course.location, user_id)

                # now import any DRAFT items
                _import_course_draft(
                    xml_module_store,
                    store,
                    user_id,
                    course_data_path,
                    course_key,
                    dest_course_id,
                    course.runtime
                )

    return xml_module_store, course_items
Beispiel #10
0
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_course_id=None,
                    verbose=False,
                    draft_store=None,
                    do_import_static=True,
                    create_new_course=False):
    """
    Import the specified xml data_dir into the "store" modulestore,
    using org and course as the location org and course.

    course_dirs: If specified, the list of course_dirs to load. Otherwise, load
    all course dirs

    target_course_id is the CourseKey that all modules should be remapped to
    after import off disk. We do this remapping as a post-processing step
    because there's logic in the importing which expects a 'url_name' as an
    identifier to where things are on disk
    e.g. ../policies/<url_name>/policy.json as well as metadata keys in
    the policy.json. so we need to keep the original url_name during import

    :param do_import_static:
        if False, then static files are not imported into the static content
        store. This can be employed for courses which have substantial
        unchanging static content, which is to inefficient to import every
        time the course is loaded. Static content for some courses may also be
        served directly by nginx, instead of going through django.

    : create_new_course:
        If True, then courses whose ids already exist in the store are not imported.
        The check for existing courses is case-insensitive.
    """

    xml_module_store = XMLModuleStore(
        data_dir,
        default_class=default_class,
        course_dirs=course_dirs,
        load_error_modules=load_error_modules,
        xblock_mixins=store.xblock_mixins,
        xblock_select=store.xblock_select,
    )

    # If we're going to remap the course_id, then we can only do that with
    # a single course

    if target_course_id:
        assert (len(xml_module_store.modules) == 1)

    # NOTE: the XmlModuleStore does not implement get_items()
    # which would be a preferable means to enumerate the entire collection
    # of course modules. It will be left as a TBD to implement that
    # method on XmlModuleStore.
    course_items = []
    for course_key in xml_module_store.modules.keys():

        if target_course_id is not None:
            dest_course_id = target_course_id
        else:
            dest_course_id = course_key

        if create_new_course:
            # this tests if exactly this course (ignoring case) exists; so, it checks the run
            if store.has_course(dest_course_id, ignore_case=True):
                log.debug("Skipping import of course with id, {0},"
                          "since it collides with an existing one".format(
                              dest_course_id))
                continue
            else:
                try:
                    store.create_course(dest_course_id.org,
                                        dest_course_id.offering)
                except InvalidLocationError:
                    # course w/ same org and course exists and store is old mongo
                    log.debug("Skipping import of course with id, {0},"
                              "since it collides with an existing one".format(
                                  dest_course_id))
                    continue

        try:
            # turn off all write signalling while importing as this
            # is a high volume operation on stores that need it
            if hasattr(store, 'ignore_write_events_on_courses'):
                store.ignore_write_events_on_courses.add(dest_course_id)

            course_data_path = None

            if verbose:
                log.debug(
                    "Scanning {0} for course module...".format(course_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
            # first into the store
            for module in xml_module_store.modules[course_key].itervalues():
                if module.scope_ids.block_type == 'course':
                    course_data_path = path(data_dir) / module.data_dir

                    log.debug(u'======> IMPORTING course {course_key}'.format(
                        course_key=course_key, ))

                    if not do_import_static:
                        # for old-style xblock where this was actually linked to kvs
                        module.static_asset_path = module.data_dir
                        module.save()
                        log.debug('course static_asset_path={path}'.format(
                            path=module.static_asset_path))

                    log.debug('course data_dir={0}'.format(module.data_dir))

                    course = import_module(module,
                                           store,
                                           course_key,
                                           dest_course_id,
                                           do_import_static=do_import_static)

                    for entry in course.pdf_textbooks:
                        for chapter in entry.get('chapters', []):
                            if StaticContent.is_c4x_path(chapter.get(
                                    'url', '')):
                                asset_key = StaticContent.get_location_from_path(
                                    chapter['url'])
                                chapter[
                                    'url'] = StaticContent.get_static_path_from_location(
                                        asset_key)

                    # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
                    # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
                    # values then remap it so that the wiki does not point to the old wiki.
                    if course_key != course.id:
                        original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                            course_key.org, course_key.course, course_key.run)
                        if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course:
                            course.wiki_slug = u'{0}.{1}.{2}'.format(
                                course.id.org,
                                course.id.course,
                                course.id.run,
                            )

                    # cdodge: more hacks (what else). Seems like we have a
                    # problem when importing a course (like 6.002) which
                    # does not have any tabs defined in the policy file.
                    # The import goes fine and then displays fine in LMS,
                    # but if someone tries to add a new tab in the CMS, then
                    # the LMS barfs because it expects that -- if there are
                    # *any* tabs -- then there at least needs to be
                    # some predefined ones
                    if course.tabs is None or len(course.tabs) == 0:
                        CourseTabList.initialize_default(course)

                    store.update_item(course)

                    course_items.append(course)

            # then import all the static content
            if static_content_store is not None and do_import_static:
                # first pass to find everything in /static/
                import_static_content(course_data_path,
                                      static_content_store,
                                      dest_course_id,
                                      subpath='static',
                                      verbose=verbose)

            elif verbose and not do_import_static:
                log.debug(
                    "Skipping import of static content, "
                    "since do_import_static={0}".format(do_import_static))

            # 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
            # "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
            # content into the "static_import" subdir.

            simport = 'static_import'
            if os.path.exists(course_data_path / simport):
                import_static_content(course_data_path,
                                      static_content_store,
                                      dest_course_id,
                                      subpath=simport,
                                      verbose=verbose)

            # finally loop through all the modules
            for module in xml_module_store.modules[course_key].itervalues():
                if module.scope_ids.block_type == 'course':
                    # we've already saved the course module up at the top
                    # of the loop so just skip over it in the inner loop
                    continue

                if verbose:
                    log.debug('importing module location {loc}'.format(
                        loc=module.location))

                import_module(module,
                              store,
                              course_key,
                              dest_course_id,
                              do_import_static=do_import_static,
                              system=course.runtime)

            # now import any 'draft' items
            if draft_store is not None:
                import_course_draft(xml_module_store, store, draft_store,
                                    course_data_path, static_content_store,
                                    course_key, dest_course_id, course.runtime)

        finally:
            # turn back on all write signalling on stores that need it
            if (hasattr(store, 'ignore_write_events_on_courses') and
                    dest_course_id in store.ignore_write_events_on_courses):
                store.ignore_write_events_on_courses.remove(dest_course_id)
                store.refresh_cached_metadata_inheritance_tree(dest_course_id)

    return xml_module_store, course_items