예제 #1
0
def make_block():
    """ Instantiate a DragAndDropBlock XBlock inside a WorkbenchRuntime """
    block_type = 'drag_and_drop_v2'
    key_store = DictKeyValueStore()
    field_data = KvsFieldData(key_store)
    runtime = WorkbenchRuntime()
    def_id = runtime.id_generator.create_definition(block_type)
    usage_id = runtime.id_generator.create_usage(def_id)
    scope_ids = ScopeIds('user', block_type, def_id, usage_id)
    return drag_and_drop_v2.DragAndDropBlock(runtime,
                                             field_data,
                                             scope_ids=scope_ids)
예제 #2
0
    def __init__(
        self, data_dir, default_class=None, course_dirs=None, course_ids=None,
        load_error_modules=True, i18n_service=None, **kwargs
    ):
        """
        Initialize an XMLModuleStore from data_dir

        Args:
            data_dir (str): path to data directory containing the course directories

            default_class (str): dot-separated string defining the default descriptor
                class to use if none is specified in entry_points

            course_dirs or course_ids (list of str): If specified, the list of course_dirs or course_ids to load. Otherwise,
                load all courses. Note, providing both
        """
        super(XMLModuleStore, self).__init__(**kwargs)

        self.data_dir = path(data_dir)
        self.modules = defaultdict(dict)  # course_id -> dict(location -> XBlock)
        self.courses = {}  # course_dir -> XBlock for the course
        self.errored_courses = {}  # course_dir -> errorlog, for dirs that failed to load

        if course_ids is not None:
            course_ids = [SlashSeparatedCourseKey.from_deprecated_string(course_id) for course_id in course_ids]

        self.load_error_modules = load_error_modules

        if default_class is None:
            self.default_class = None
        else:
            module_path, _, class_name = default_class.rpartition('.')
            class_ = getattr(import_module(module_path), class_name)
            self.default_class = class_

        self.parent_trackers = defaultdict(ParentTracker)
        self.reference_type = Location

        # All field data will be stored in an inheriting field data.
        self.field_data = inheriting_field_data(kvs=DictKeyValueStore())

        self.i18n_service = i18n_service

        # If we are specifically asked for missing courses, that should
        # be an error.  If we are asked for "all" courses, find the ones
        # that have a course.xml. We sort the dirs in alpha order so we always
        # read things in the same order (OS differences in load order have
        # bitten us in the past.)
        if course_dirs is None:
            course_dirs = sorted([d for d in os.listdir(self.data_dir) if
                                  os.path.exists(self.data_dir / d / "course.xml")])
        for course_dir in course_dirs:
            self.try_load_course(course_dir, course_ids)
예제 #3
0
    def setUp(self):
        block_type = 'hastexo'
        key_store = DictKeyValueStore()
        field_data = KvsFieldData(key_store)
        runtime = WorkbenchRuntime()
        def_id = runtime.id_generator.create_definition(block_type)
        usage_id = runtime.id_generator.create_usage(def_id)
        scope_ids = ScopeIds('user', block_type, def_id, usage_id)

        self.block = HastexoXBlock(runtime,
                                   field_data,
                                   scope_ids=scope_ids)
예제 #4
0
    def _prepare_asides(self, scope_ids):
        """
        Return list with connected aside xblocks
        """
        key_store = DictKeyValueStore()
        field_data = KvsFieldData(key_store)

        aside = AsideTest(
            scope_ids=scope_ids,
            runtime=TestRuntime(services={'field-data': field_data}))
        aside.fields[self.ASIDE_DATA_FIELD.field_name].write_to(
            aside, self.ASIDE_DATA_FIELD.initial)
        return [aside]
def test_view_counter_state():
    key_store = DictKeyValueStore()
    db_model = KvsFieldData(key_store)
    tester = ViewCounter(Mock(), db_model, Mock())

    assert_equals(tester.views, 0)

    # View the XBlock five times
    for i in xrange(5):
        generated_html = tester.student_view({})
        # Make sure the html fragment we're expecting appears in the body_html
        assert_in('<span class="views">{0}</span>'.format(i + 1),
                  generated_html.body_html())
        assert_equals(tester.views, i + 1)
예제 #6
0
def test_view_counter_state():
    key_store = DictKeyValueStore()
    field_data = KvsFieldData(key_store)
    runtime = Runtime(services={'field-data': field_data})
    tester = ViewCounter(runtime, scope_ids=Mock())

    assert tester.views == 0

    # View the XBlock five times
    for i in range(5):
        generated_html = tester.student_view({})
        # Make sure the html fragment we're expecting appears in the body_html
        assert f'<span class="views">{i + 1}</span>' in generated_html.body_html()
        assert tester.views == (i + 1)
예제 #7
0
 def test_aside_contains_tags(self):
     """
     Checks that available_tags list is not empty
     """
     sids = ScopeIds(user_id="bob",
                     block_type="bobs-type",
                     def_id="definition-id",
                     usage_id="usage-id")
     key_store = DictKeyValueStore()
     field_data = KvsFieldData(key_store)
     runtime = TestRuntime(services={'field-data': field_data})  # pylint: disable=abstract-class-instantiated
     xblock_aside = StructuredTagsAside(scope_ids=sids, runtime=runtime)
     available_tags = xblock_aside.get_available_tags()
     self.assertEquals(len(available_tags), 2, "StructuredTagsAside should contains two tag categories")
    def setUp(self):
        """
        Create a stub XBlock backed by in-memory storage.
        """
        self.runtime = mock.MagicMock(Runtime)
        self.field_data = KvsFieldData(kvs=DictKeyValueStore())
        self.scope_ids = ScopeIds('Bob', 'mutablestubxblock', '123', 'import')
        self.xblock = StubXBlockWithMutableFields(self.runtime, self.field_data, self.scope_ids)

        self.fake_children_locations = [
            BlockUsageLocator(CourseLocator('org', 'course', 'run'), 'mutablestubxblock', 'child1'),
            BlockUsageLocator(CourseLocator('org', 'course', 'run'), 'mutablestubxblock', 'child2'),
        ]

        super(UpdateLocationTest, self).setUp()
예제 #9
0
def make_xblock(xblock_name, xblock_cls, attributes):
    """
    Helper to construct XBlock objects
    """
    runtime = WorkbenchRuntime()
    key_store = DictKeyValueStore()
    db_model = KvsFieldData(key_store)
    ids = generate_scope_ids(runtime, xblock_name)
    xblock = xblock_cls(runtime, db_model, scope_ids=ids)
    xblock.category = Mock()
    xblock.location = Mock(html_id=Mock(return_value='sample_element_id'), )
    xblock.runtime = Mock(hostname='localhost', )
    xblock.course_id = 'course-v1:edX+DemoX+Demo_Course'
    for key, value in attributes.items():
        setattr(xblock, key, value)
    return xblock
예제 #10
0
    def __init__(self, load_error_modules):

        xmlstore = XMLModuleStore("data_dir", source_dirs=[],
                                  load_error_modules=load_error_modules)
        course_id = CourseKey.from_string('/'.join([ORG, COURSE, 'test_run']))
        course_dir = "test_dir"
        error_tracker = Mock()

        super(DummySystem, self).__init__(
            xmlstore=xmlstore,
            course_id=course_id,
            course_dir=course_dir,
            error_tracker=error_tracker,
            load_error_modules=load_error_modules,
            field_data=KvsFieldData(DictKeyValueStore()),
        )
예제 #11
0
    def __init__(self, load_error_modules):

        xmlstore = XMLModuleStore("data_dir",
                                  course_dirs=[],
                                  load_error_modules=load_error_modules)
        course_id = SlashSeparatedCourseKey(ORG, COURSE, 'test_run')
        course_dir = "test_dir"
        error_tracker = Mock()

        super(DummySystem, self).__init__(
            xmlstore=xmlstore,
            course_id=course_id,
            course_dir=course_dir,
            error_tracker=error_tracker,
            load_error_modules=load_error_modules,
            field_data=KvsFieldData(DictKeyValueStore()),
        )
예제 #12
0
    def __init__(self, load_error_modules, course_id=None):

        xmlstore = XMLModuleStore("data_dir", source_dirs=[],
                                  load_error_modules=load_error_modules)
        if course_id is None:
            course_id = CourseKey.from_string('/'.join([ORG, COURSE, 'test_run']))
        course_dir = "test_dir"
        error_tracker = Mock()

        super(DummySystem, self).__init__(  # lint-amnesty, pylint: disable=super-with-arguments
            xmlstore=xmlstore,
            course_id=course_id,
            course_dir=course_dir,
            error_tracker=error_tracker,
            load_error_modules=load_error_modules,
            field_data=KvsFieldData(DictKeyValueStore()),
        )
예제 #13
0
    def __init__(self, load_error_modules, library=False):

        if library:
            xmlstore = LibraryXMLModuleStore("data_dir", source_dirs=[], load_error_modules=load_error_modules)
        else:
            xmlstore = XMLModuleStore("data_dir", source_dirs=[], load_error_modules=load_error_modules)
        course_id = SlashSeparatedCourseKey(ORG, COURSE, 'test_run')
        course_dir = "test_dir"
        error_tracker = Mock()

        super(DummySystem, self).__init__(
            xmlstore=xmlstore,
            course_id=course_id,
            course_dir=course_dir,
            error_tracker=error_tracker,
            load_error_modules=load_error_modules,
            mixins=(InheritanceMixin, XModuleMixin),
            field_data=KvsFieldData(DictKeyValueStore()),
        )
예제 #14
0
def test_runtime_handle():
    # Test a simple handler and a fallback handler

    key_store = DictKeyValueStore()
    field_data = KvsFieldData(key_store)
    test_runtime = TestRuntime(services={'field-data': field_data})
    basic_tester = TestXBlock(test_runtime, scope_ids=Mock(spec=ScopeIds))
    runtime = MockRuntimeForQuerying()
    # string we want to update using the handler
    update_string = "user state update"
    assert_equals(
        runtime.handle(basic_tester, 'existing_handler', update_string),
        'I am the existing test handler')
    assert_equals(basic_tester.user_state, update_string)

    # when the handler needs to use the fallback as given name can't be found
    new_update_string = "new update"
    assert_equals(
        runtime.handle(basic_tester, 'test_fallback_handler',
                       new_update_string), 'I have been handled')
    assert_equals(basic_tester.user_state, new_update_string)

    # request to use a handler which doesn't have XBlock.handler decoration
    # should use the fallback
    new_update_string = "new update"
    assert_equals(
        runtime.handle(basic_tester, 'handler_without_correct_decoration',
                       new_update_string), 'gone to fallback')
    assert_equals(basic_tester.user_state, new_update_string)

    # handler can't be found & no fallback handler supplied, should throw an exception
    no_fallback_tester = TestXBlockNoFallback(runtime,
                                              scope_ids=Mock(spec=ScopeIds))
    ultimate_string = "ultimate update"
    with assert_raises(NoSuchHandlerError):
        runtime.handle(no_fallback_tester, 'test_nonexistant_fallback_handler',
                       ultimate_string)

    # request to use a handler which doesn't have XBlock.handler decoration
    # and no fallback should raise NoSuchHandlerError
    with assert_raises(NoSuchHandlerError):
        runtime.handle(no_fallback_tester,
                       'handler_without_correct_decoration', 'handled')
예제 #15
0
    def __init__(self, load_error_modules):

        xmlstore = XMLModuleStore("data_dir", course_dirs=[],
                                  load_error_modules=load_error_modules)
        course_id = "/".join([ORG, COURSE, 'test_run'])
        course_dir = "test_dir"
        error_tracker = Mock()
        parent_tracker = Mock()

        super(DummySystem, self).__init__(
            xmlstore=xmlstore,
            course_id=course_id,
            course_dir=course_dir,
            error_tracker=error_tracker,
            parent_tracker=parent_tracker,
            load_error_modules=load_error_modules,
            field_data=KvsFieldData(DictKeyValueStore()),
            id_reader=LocationReader(),
        )
예제 #16
0
    def __init__(self, load_error_modules, library=False):

        if library:
            xmlstore = LibraryXMLModuleStore("data_dir", source_dirs=[], load_error_modules=load_error_modules)
        else:
            xmlstore = XMLModuleStore("data_dir", source_dirs=[], load_error_modules=load_error_modules)
        course_id = CourseKey.from_string('/'.join([ORG, COURSE, RUN]))
        course_dir = "test_dir"
        error_tracker = Mock()

        super(DummySystem, self).__init__(  # lint-amnesty, pylint: disable=super-with-arguments
            xmlstore=xmlstore,
            course_id=course_id,
            course_dir=course_dir,
            error_tracker=error_tracker,
            load_error_modules=load_error_modules,
            mixins=(InheritanceMixin, XModuleMixin),
            field_data=KvsFieldData(DictKeyValueStore()),
        )
예제 #17
0
    def __init__(self, xml_import_data):
        self.course_id = CourseKey.from_string(xml_import_data.course_id)
        self.default_class = xml_import_data.default_class
        self._descriptors = {}

        def get_policy(usage_id):
            """Return the policy data for the specified usage"""
            return xml_import_data.policy.get(policy_key(usage_id), {})

        super(InMemorySystem, self).__init__(
            get_policy=get_policy,
            process_xml=self.process_xml,
            load_item=self.load_item,
            error_tracker=Mock(),
            resources_fs=xml_import_data.filesystem,
            mixins=xml_import_data.xblock_mixins,
            select=xml_import_data.xblock_select,
            render_template=lambda template, context: pprint.pformat((template, context)),
            field_data=KvsFieldData(DictKeyValueStore()),
        )
예제 #18
0
def test_runtime_render():
    key_store = DictKeyValueStore()
    field_data = KvsFieldData(key_store)
    runtime = MockRuntimeForQuerying(services={'field-data': field_data})
    block_type = 'test'
    def_id = runtime.id_generator.create_definition(block_type)
    usage_id = runtime.id_generator.create_usage(def_id)
    tester = TestXBlock(runtime,
                        scope_ids=ScopeIds('user', block_type, def_id,
                                           usage_id))
    # string we want to update using the handler
    update_string = "user state update"

    # test against the student view
    frag = runtime.render(tester, 'student_view', [update_string])
    assert update_string in frag.body_html()
    assert tester.preferences == update_string

    # test against the fallback view
    update_string = "new update"
    frag = runtime.render(tester, 'test_fallback_view', [update_string])
    assert update_string in frag.body_html()
    assert tester.preferences == update_string

    # test block-first
    update_string = "penultimate update"
    frag = tester.render('student_view', [update_string])
    assert update_string in frag.body_html()
    assert tester.preferences == update_string

    # test against the no-fallback XBlock
    update_string = "ultimate update"
    no_fallback_tester = TestXBlockNoFallback(Mock(),
                                              scope_ids=Mock(spec=ScopeIds))
    with pytest.raises(NoSuchViewError):
        runtime.render(no_fallback_tester, 'test_nonexistent_view',
                       [update_string])
예제 #19
0
def test_db_model_keys():
    # Tests that updates to fields are properly recorded in the KeyValueStore,
    # and that the keys have been constructed correctly
    key_store = DictKeyValueStore()
    field_data = KvsFieldData(key_store)
    runtime = TestRuntime(Mock(), field_data, [TestMixin])
    tester = runtime.construct_xblock_from_class(
        TestXBlock, ScopeIds('s0', 'TestXBlock', 'd0', 'u0'))

    assert_false(field_data.has(tester, 'not a field'))

    for field in tester.fields.values():
        new_value = 'new ' + field.name
        assert_false(field_data.has(tester, field.name))
        setattr(tester, field.name, new_value)

    # Write out the values
    tester.save()

    # Make sure everything saved correctly
    for field in tester.fields.values():
        assert_true(field_data.has(tester, field.name))

    def get_key_value(scope, user_id, block_scope_id, field_name):
        """Gets the value, from `key_store`, of a Key with the given values."""
        new_key = KeyValueStore.Key(scope, user_id, block_scope_id, field_name)
        return key_store.db_dict[new_key]

    # Examine each value in the database and ensure that keys were constructed correctly
    assert_equals('new content',
                  get_key_value(Scope.content, None, 'd0', 'content'))
    assert_equals('new settings',
                  get_key_value(Scope.settings, None, 'u0', 'settings'))
    assert_equals('new user_state',
                  get_key_value(Scope.user_state, 's0', 'u0', 'user_state'))
    assert_equals(
        'new preferences',
        get_key_value(Scope.preferences, 's0', 'TestXBlock', 'preferences'))
    assert_equals('new user_info',
                  get_key_value(Scope.user_info, 's0', None, 'user_info'))
    assert_equals(
        'new by_type',
        get_key_value(Scope(UserScope.NONE, BlockScope.TYPE), None,
                      'TestXBlock', 'by_type'))
    assert_equals(
        'new for_all',
        get_key_value(Scope(UserScope.NONE, BlockScope.ALL), None, None,
                      'for_all'))
    assert_equals(
        'new user_def',
        get_key_value(Scope(UserScope.ONE, BlockScope.DEFINITION), 's0', 'd0',
                      'user_def'))
    assert_equals(
        'new agg_global',
        get_key_value(Scope(UserScope.ALL, BlockScope.ALL), None, None,
                      'agg_global'))
    assert_equals(
        'new agg_type',
        get_key_value(Scope(UserScope.ALL, BlockScope.TYPE), None,
                      'TestXBlock', 'agg_type'))
    assert_equals(
        'new agg_def',
        get_key_value(Scope(UserScope.ALL, BlockScope.DEFINITION), None, 'd0',
                      'agg_def'))
    assert_equals(
        'new agg_usage',
        get_key_value(Scope.user_state_summary, None, 'u0', 'agg_usage'))
    assert_equals('new mixin_content',
                  get_key_value(Scope.content, None, 'd0', 'mixin_content'))
    assert_equals('new mixin_settings',
                  get_key_value(Scope.settings, None, 'u0', 'mixin_settings'))
    assert_equals(
        'new mixin_user_state',
        get_key_value(Scope.user_state, 's0', 'u0', 'mixin_user_state'))
    assert_equals(
        'new mixin_preferences',
        get_key_value(Scope.preferences, 's0', 'TestXBlock',
                      'mixin_preferences'))
    assert_equals(
        'new mixin_user_info',
        get_key_value(Scope.user_info, 's0', None, 'mixin_user_info'))
    assert_equals(
        'new mixin_by_type',
        get_key_value(Scope(UserScope.NONE, BlockScope.TYPE), None,
                      'TestXBlock', 'mixin_by_type'))
    assert_equals(
        'new mixin_for_all',
        get_key_value(Scope(UserScope.NONE, BlockScope.ALL), None, None,
                      'mixin_for_all'))
    assert_equals(
        'new mixin_user_def',
        get_key_value(Scope(UserScope.ONE, BlockScope.DEFINITION), 's0', 'd0',
                      'mixin_user_def'))
    assert_equals(
        'new mixin_agg_global',
        get_key_value(Scope(UserScope.ALL, BlockScope.ALL), None, None,
                      'mixin_agg_global'))
    assert_equals(
        'new mixin_agg_type',
        get_key_value(Scope(UserScope.ALL, BlockScope.TYPE), None,
                      'TestXBlock', 'mixin_agg_type'))
    assert_equals(
        'new mixin_agg_def',
        get_key_value(Scope(UserScope.ALL, BlockScope.DEFINITION), None, 'd0',
                      'mixin_agg_def'))
    assert_equals(
        'new mixin_agg_usage',
        get_key_value(Scope.user_state_summary, None, 'u0', 'mixin_agg_usage'))
예제 #20
0
 def setUp(self):
     key_store = DictKeyValueStore()
     field_data = KvsFieldData(key_store)
     self.runtime = TestRuntime(services={'field-data': field_data})
예제 #21
0
 def make_xblock(self):
     key_store = DictKeyValueStore()
     field_data = KvsFieldData(key_store)
     runtime = TestRuntime(services={'field-data': field_data})
     xblock = WistiaVideoXBlock(runtime, scope_ids=Mock())
     return xblock
예제 #22
0
def import_course_draft(
        xml_module_store, store, draft_store, course_data_path,
        static_content_store, source_location_namespace,
        target_location_namespace):
    '''
    This will import all the content inside of the 'drafts' folder, if it exists
    NOTE: This is not a full course import, basically in our current
    application only verticals (and downwards) can be in draft.
    Therefore, we need to use slightly different call points into
    the import process_xml as we can't simply call XMLModuleStore() constructor
    (like we do for importing public content)
    '''
    draft_dir = course_data_path + "/drafts"
    if not os.path.exists(draft_dir):
        return

    # create a new 'System' object which will manage the importing
    errorlog = make_error_tracker()

    # The course_dir as passed to ImportSystem is expected to just be relative, not
    # the complete path including data_dir. ImportSystem will concatenate the two together.
    data_dir = xml_module_store.data_dir
    # Whether or not data_dir ends with a "/" differs in production vs. test.
    if not data_dir.endswith("/"):
        data_dir += "/"
    draft_course_dir = draft_dir.replace(data_dir, '', 1)
    system = ImportSystem(
        xmlstore=xml_module_store,
        course_id=target_location_namespace.course_id,
        course_dir=draft_course_dir,
        error_tracker=errorlog.tracker,
        parent_tracker=ParentTracker(),
        load_error_modules=False,
        mixins=xml_module_store.xblock_mixins,
        field_data=KvsFieldData(kvs=DictKeyValueStore()),
    )

    # now walk the /vertical directory where each file in there
    # will be a draft copy of the Vertical

    # First it is necessary to order the draft items by their desired index in the child list
    # (order os.walk returns them in is not guaranteed).
    drafts = dict()
    for dirname, _dirnames, filenames in os.walk(draft_dir + "/vertical"):
        for filename in filenames:
            module_path = os.path.join(dirname, filename)
            with open(module_path, 'r') as f:
                try:
                    # note, on local dev it seems like OSX will put
                    # some extra files in the directory with "quarantine"
                    # information. These files are binary files and will
                    # throw exceptions when we try to parse the file
                    # as an XML string. Let's make sure we're
                    # dealing with a string before ingesting
                    data = f.read()

                    try:
                        xml = data.decode('utf-8')
                    except UnicodeDecodeError, err:
                        # seems like on OSX localdev, the OS is making
                        # quarantine files in the unzip directory
                        # when importing courses so if we blindly try to
                        # enumerate through the directory, we'll try
                        # to process a bunch of binary quarantine files
                        # (which are prefixed with a '._' character which
                        # will dump a bunch of exceptions to the output,
                        # although they are harmless.
                        #
                        # Reading online docs there doesn't seem to be
                        # a good means to detect a 'hidden' file that works
                        # well across all OS environments. So for now, I'm using
                        # OSX's utilization of a leading '.' in the filename
                        # to indicate a system hidden file.
                        #
                        # Better yet would be a way to figure out if this is
                        # a binary file, but I haven't found a good way
                        # to do this yet.
                        if filename.startswith('._'):
                            continue
                        # Not a 'hidden file', then re-raise exception
                        raise err

                    descriptor = system.process_xml(xml)

                    # HACK: since we are doing partial imports of drafts
                    # the vertical doesn't have the 'url-name' set in the
                    # attributes (they are normally in the parent object,
                    # aka sequential), so we have to replace the location.name
                    # with the XML filename that is part of the pack
                    fn, fileExtension = os.path.splitext(filename)
                    descriptor.location = descriptor.location.replace(name=fn)

                    index = int(descriptor.xml_attributes['index_in_children_list'])
                    if index in drafts:
                        drafts[index].append(descriptor)
                    else:
                        drafts[index] = [descriptor]

                except Exception, e:
                    logging.exception('There was an error. {err}'.format(
                        err=unicode(e)
                    ))
예제 #23
0
def _import_course_draft(
        xml_module_store,
        store,
        user_id,
        course_data_path,
        source_course_id,
        target_course_id,
        mongo_runtime
):
    '''
    This will import all the content inside of the 'drafts' folder, if it exists
    NOTE: This is not a full course import, basically in our current
    application only verticals (and downwards) can be in draft.
    Therefore, we need to use slightly different call points into
    the import process_xml as we can't simply call XMLModuleStore() constructor
    (like we do for importing public content)
    '''
    draft_dir = course_data_path + "/drafts"
    if not os.path.exists(draft_dir):
        return

    # create a new 'System' object which will manage the importing
    errorlog = make_error_tracker()

    # The course_dir as passed to ImportSystem is expected to just be relative, not
    # the complete path including data_dir. ImportSystem will concatenate the two together.
    data_dir = xml_module_store.data_dir
    # Whether or not data_dir ends with a "/" differs in production vs. test.
    if not data_dir.endswith("/"):
        data_dir += "/"
    draft_course_dir = draft_dir.replace(data_dir, '', 1)
    system = ImportSystem(
        xmlstore=xml_module_store,
        course_id=source_course_id,
        course_dir=draft_course_dir,
        error_tracker=errorlog.tracker,
        parent_tracker=ParentTracker(),
        load_error_modules=False,
        mixins=xml_module_store.xblock_mixins,
        field_data=KvsFieldData(kvs=DictKeyValueStore()),
    )

    def _import_module(module):
        # IMPORTANT: Be sure to update the module location in the NEW namespace
        module_location = module.location.map_into_course(target_course_id)
        # Update the module's location to DRAFT revision
        # We need to call this method (instead of updating the location directly)
        # to ensure that pure XBlock field data is updated correctly.
        _update_module_location(module, module_location.replace(revision=MongoRevisionKey.draft))

        parent_url = get_parent_url(module)
        index = index_in_children_list(module)

        # make sure our parent has us in its list of children
        # this is to make sure private only modules show up
        # in the list of children since they would have been
        # filtered out from the non-draft store export.
        if parent_url is not None and index is not None:
            course_key = descriptor.location.course_key
            parent_location = course_key.make_usage_key_from_deprecated_string(parent_url)

            # IMPORTANT: Be sure to update the parent in the NEW namespace
            parent_location = parent_location.map_into_course(target_course_id)

            parent = store.get_item(parent_location, depth=0)

            non_draft_location = module.location.map_into_course(target_course_id)
            if not any(child.block_id == module.location.block_id for child in parent.children):
                parent.children.insert(index, non_draft_location)
                store.update_item(parent, user_id)

        _import_module_and_update_references(
            module, store, user_id,
            source_course_id,
            target_course_id,
            runtime=mongo_runtime,
        )
        for child in module.get_children():
            _import_module(child)

    # now walk the /vertical directory where each file in there
    # will be a draft copy of the Vertical

    # First it is necessary to order the draft items by their desired index in the child list
    # (order os.walk returns them in is not guaranteed).
    drafts = []
    for dirname, _dirnames, filenames in os.walk(draft_dir):
        for filename in filenames:
            module_path = os.path.join(dirname, filename)
            with open(module_path, 'r') as f:
                try:
                    # note, on local dev it seems like OSX will put
                    # some extra files in the directory with "quarantine"
                    # information. These files are binary files and will
                    # throw exceptions when we try to parse the file
                    # as an XML string. Let's make sure we're
                    # dealing with a string before ingesting
                    data = f.read()

                    try:
                        xml = data.decode('utf-8')
                    except UnicodeDecodeError, err:
                        # seems like on OSX localdev, the OS is making
                        # quarantine files in the unzip directory
                        # when importing courses so if we blindly try to
                        # enumerate through the directory, we'll try
                        # to process a bunch of binary quarantine files
                        # (which are prefixed with a '._' character which
                        # will dump a bunch of exceptions to the output,
                        # although they are harmless.
                        #
                        # Reading online docs there doesn't seem to be
                        # a good means to detect a 'hidden' file that works
                        # well across all OS environments. So for now, I'm using
                        # OSX's utilization of a leading '.' in the filename
                        # to indicate a system hidden file.
                        #
                        # Better yet would be a way to figure out if this is
                        # a binary file, but I haven't found a good way
                        # to do this yet.
                        if filename.startswith('._'):
                            continue
                        # Not a 'hidden file', then re-raise exception
                        raise err

                    # process_xml call below recursively processes all descendants. If
                    # we call this on all verticals in a course with verticals nested below
                    # the unit level, we try to import the same content twice, causing naming conflicts.
                    # Therefore only process verticals at the unit level, assuming that any other
                    # verticals must be descendants.
                    if 'index_in_children_list' in xml:
                        descriptor = system.process_xml(xml)

                        # HACK: since we are doing partial imports of drafts
                        # the vertical doesn't have the 'url-name' set in the
                        # attributes (they are normally in the parent object,
                        # aka sequential), so we have to replace the location.name
                        # with the XML filename that is part of the pack
                        filename, __ = os.path.splitext(filename)
                        descriptor.location = descriptor.location.replace(name=filename)

                        index = index_in_children_list(descriptor)
                        parent_url = get_parent_url(descriptor, xml)
                        draft_url = descriptor.location.to_deprecated_string()

                        draft = draft_node_constructor(
                            module=descriptor, url=draft_url, parent_url=parent_url, index=index
                        )

                        drafts.append(draft)

                except Exception:  # pylint: disable=broad-except
                    logging.exception('Error while parsing course xml.')
def make_block():
    runtime = WorkbenchRuntime()
    key_store = DictKeyValueStore()
    db_model = KvsFieldData(key_store)
    return drag_and_drop_v2.DragAndDropBlock(runtime, db_model, Mock())
예제 #25
0
def _import_course_draft(
        xml_module_store,
        store,
        user_id,
        course_data_path,
        source_course_id,
        target_id,
        mongo_runtime
):
    """
    This method will import all the content inside of the 'drafts' folder, if content exists.
    NOTE: This is not a full course import! In our current application, only verticals
    (and blocks beneath) can be in draft. Therefore, different call points into the import
    process_xml are used as the XMLModuleStore() constructor cannot simply be called
    (as is done for importing public content).
    """
    draft_dir = course_data_path + "/drafts"
    if not os.path.exists(draft_dir):
        return

    # create a new 'System' object which will manage the importing
    errorlog = make_error_tracker()

    # The course_dir as passed to ImportSystem is expected to just be relative, not
    # the complete path including data_dir. ImportSystem will concatenate the two together.
    data_dir = xml_module_store.data_dir
    # Whether or not data_dir ends with a "/" differs in production vs. test.
    if not data_dir.endswith("/"):
        data_dir += "/"
    # Remove absolute path, leaving relative <course_name>/drafts.
    draft_course_dir = draft_dir.replace(data_dir, '', 1)

    system = ImportSystem(
        xmlstore=xml_module_store,
        course_id=source_course_id,
        course_dir=draft_course_dir,
        error_tracker=errorlog.tracker,
        load_error_modules=False,
        mixins=xml_module_store.xblock_mixins,
        field_data=KvsFieldData(kvs=DictKeyValueStore()),
        target_course_id=target_id,
    )

    def _import_module(module):
        # IMPORTANT: Be sure to update the module location in the NEW namespace
        module_location = module.location.map_into_course(target_id)
        # Update the module's location to DRAFT revision
        # We need to call this method (instead of updating the location directly)
        # to ensure that pure XBlock field data is updated correctly.
        _update_module_location(module, module_location.replace(revision=MongoRevisionKey.draft))

        parent_url = get_parent_url(module)
        index = index_in_children_list(module)

        # make sure our parent has us in its list of children
        # this is to make sure private only modules show up
        # in the list of children since they would have been
        # filtered out from the non-draft store export.
        if parent_url is not None and index is not None:
            course_key = descriptor.location.course_key
            parent_location = course_key.make_usage_key_from_deprecated_string(parent_url)

            # IMPORTANT: Be sure to update the parent in the NEW namespace
            parent_location = parent_location.map_into_course(target_id)

            parent = store.get_item(parent_location, depth=0)

            non_draft_location = module.location.map_into_course(target_id)
            if not any(child.block_id == module.location.block_id for child in parent.children):
                parent.children.insert(index, non_draft_location)
                store.update_item(parent, user_id)

        _update_and_import_module(
            module, store, user_id,
            source_course_id,
            target_id,
            runtime=mongo_runtime,
        )
        for child in module.get_children():
            _import_module(child)

    # Now walk the /drafts directory.
    # Each file in the directory will be a draft copy of the vertical.

    # First it is necessary to order the draft items by their desired index in the child list,
    # since the order in which os.walk() returns the files is not guaranteed.
    drafts = []
    for rootdir, __, filenames in os.walk(draft_dir):
        for filename in filenames:
            if filename.startswith('._'):
                # Skip any OSX quarantine files, prefixed with a '._'.
                continue
            module_path = os.path.join(rootdir, filename)
            with open(module_path, 'r') as f:
                try:
                    xml = f.read().decode('utf-8')

                    # The process_xml() call below recursively processes all descendants. If
                    # we call this on all verticals in a course with verticals nested below
                    # the unit level, we try to import the same content twice, causing naming conflicts.
                    # Therefore only process verticals at the unit level, assuming that any other
                    # verticals must be descendants.
                    if 'index_in_children_list' in xml:
                        descriptor = system.process_xml(xml)

                        # HACK: since we are doing partial imports of drafts
                        # the vertical doesn't have the 'url-name' set in the
                        # attributes (they are normally in the parent object,
                        # aka sequential), so we have to replace the location.name
                        # with the XML filename that is part of the pack
                        filename, __ = os.path.splitext(filename)
                        descriptor.location = descriptor.location.replace(name=filename)

                        index = index_in_children_list(descriptor)
                        parent_url = get_parent_url(descriptor, xml)
                        draft_url = unicode(descriptor.location)

                        draft = draft_node_constructor(
                            module=descriptor, url=draft_url, parent_url=parent_url, index=index
                        )
                        drafts.append(draft)

                except Exception:  # pylint: disable=broad-except
                    logging.exception('Error while parsing course drafts xml.')

    # Sort drafts by `index_in_children_list` attribute.
    drafts.sort(key=lambda x: x.index)

    for draft in get_draft_subtree_roots(drafts):
        try:
            _import_module(draft.module)
        except Exception:  # pylint: disable=broad-except
            logging.exception('while importing draft descriptor %s', draft.module)
예제 #26
0
 def __init__(self, **kwargs):
     field_data = kwargs.get('field_data',
                             KvsFieldData(DictKeyValueStore()))
     super(MockRuntime, self).__init__(field_data=field_data)
예제 #27
0
 def setUp(self):
     self.runtime = TestRuntime(field_data=KvsFieldData(DictKeyValueStore()))