Example #1
0
    def test_inherited_field(self):
        kvs = InheritanceKeyValueStore(
            initial_values={}, inherited_settings={'showanswer': 'inherited'})
        model_data = DbModel(kvs)
        descriptor = self.get_descriptor(model_data)
        editable_fields = descriptor.editable_metadata_fields
        self.assert_field_values(editable_fields,
                                 'showanswer',
                                 InheritanceMixin.showanswer,
                                 explicitly_set=False,
                                 value='inherited',
                                 default_value='inherited')

        # Mimic the case where display_name WOULD have been inherited, except we explicitly set it.
        kvs = InheritanceKeyValueStore(
            initial_values={'showanswer': 'explicit'},
            inherited_settings={'showanswer': 'inheritable value'})
        model_data = DbModel(kvs)
        descriptor = self.get_descriptor(model_data)
        editable_fields = descriptor.editable_metadata_fields
        self.assert_field_values(editable_fields,
                                 'showanswer',
                                 InheritanceMixin.showanswer,
                                 explicitly_set=True,
                                 value='explicit',
                                 default_value='inheritable value')
Example #2
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()
    db_model = DbModel(key_store)
    runtime = Runtime(Mock(), db_model, [TestMixin])
    tester = runtime.construct_xblock_from_class(TestXBlock, ScopeIds('s0', 'TestXBlock', 'd0', 'u0'))

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

    for field in tester.fields.values():
        new_value = 'new ' + field.name
        assert_false(db_model.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(db_model.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'))
Example #3
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()
    db_model = DbModel(key_store)
    runtime = Runtime(Mock(), db_model, [TestMixin])
    tester = runtime.construct_xblock_from_class(TestXBlock, ScopeIds('s0', 'TestXBlock', 'd0', 'u0'))

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

    for field in tester.fields.values():
        new_value = 'new ' + field.name
        assert_false(db_model.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(db_model.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'))
Example #4
0
def test_runtime_render():
    key_store = DictKeyValueStore()
    db_model = DbModel(key_store)
    runtime = MockRuntimeForQuerying()
    tester = TestXBlock(runtime, db_model, Mock())
    # string we want to update using the handler
    update_string = u"user state update"

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

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

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

    # test against the no-fallback XBlock
    update_string = u"ultimate update"
    tester = TestXBlockNoFallback(Mock(), db_model, Mock())
    with assert_raises(NoSuchViewError):
        runtime.render(tester, 'test_nonexistant_view', [update_string])
Example #5
0
 def preview_model_data(descriptor):
     return DbModel(
         SessionKeyValueStore(request, descriptor._model_data),
         descriptor.module_class,
         preview_id,
         MongoUsage(preview_id, descriptor.location.url()),
     )
Example #6
0
    def from_xml(cls, xml_data, system, org=None, course=None):
        """
        Creates an instance of this descriptor from the supplied xml_data.
        This may be overridden by subclasses

        xml_data: A string of xml that will be translated into data and children for
            this module
        system: A DescriptorSystem for interacting with external resources
        org and course are optional strings that will be used in the generated modules
            url identifiers
        """
        xml_object = etree.fromstring(xml_data)
        url_name = xml_object.get('url_name', xml_object.get('slug'))
        location = Location('i4x', org, course, 'video', url_name)
        if is_pointer_tag(xml_object):
            filepath = cls._format_filepath(xml_object.tag,
                                            name_to_pathname(url_name))
            xml_data = etree.tostring(
                cls.load_file(filepath, system.resources_fs, location))
        field_data = cls._parse_video_xml(xml_data)
        field_data['location'] = location
        kvs = InheritanceKeyValueStore(initial_values=field_data)
        field_data = DbModel(kvs)
        video = system.construct_xblock_from_class(
            cls,
            # We're loading a descriptor, so student_id is meaningless
            # We also don't have separate notions of definition and usage ids yet,
            # so we use the location for both
            ScopeIds(None, location.category, location, location),
            field_data,
        )
        return video
Example #7
0
 def preview_model_data(descriptor):
     "Helper method to create a DbModel from a descriptor"
     return DbModel(
         SessionKeyValueStore(request, descriptor._model_data),
         descriptor.module_class,
         preview_id,
         MongoUsage(preview_id, descriptor.location.url()),
     )
Example #8
0
    def load_item(self, location):
        """
        Return an XModule instance for the specified location
        """
        location = Location(location)
        json_data = self.module_data.get(location)
        if json_data is None:
            module = self.modulestore.get_item(location)
            if module is not None:
                # update our own cache after going to the DB to get cache miss
                self.module_data.update(module.runtime.module_data)
            return module
        else:
            # load the module and apply the inherited metadata
            try:
                category = json_data['location']['category']
                class_ = XModuleDescriptor.load_class(category,
                                                      self.default_class)
                definition = json_data.get('definition', {})
                metadata = json_data.get('metadata', {})
                for old_name, new_name in getattr(class_,
                                                  'metadata_translations',
                                                  {}).items():
                    if old_name in metadata:
                        metadata[new_name] = metadata[old_name]
                        del metadata[old_name]

                kvs = MongoKeyValueStore(
                    definition.get('data', {}),
                    definition.get('children', []),
                    metadata,
                )

                field_data = DbModel(kvs)
                scope_ids = ScopeIds(None, category, location, location)
                module = self.construct_xblock_from_class(
                    class_, field_data, scope_ids)
                if self.cached_metadata is not None:
                    # parent container pointers don't differentiate between draft and non-draft
                    # so when we do the lookup, we should do so with a non-draft location
                    non_draft_loc = location.replace(revision=None)

                    # Convert the serialized fields values in self.cached_metadata
                    # to python values
                    metadata_to_inherit = self.cached_metadata.get(
                        non_draft_loc.url(), {})
                    inherit_metadata(module, metadata_to_inherit)
                # decache any computed pending field settings
                module.save()
                return module
            except:
                log.warning("Failed to load descriptor", exc_info=True)
                return ErrorDescriptor.from_json(json_data,
                                                 self,
                                                 json_data['location'],
                                                 error_msg=exc_info_to_str(
                                                     sys.exc_info()))
    def xblock_from_json(self,
                         class_,
                         usage_id,
                         json_data,
                         course_entry_override=None):
        if course_entry_override is None:
            course_entry_override = self.course_entry
        # most likely a lazy loader or the id directly
        definition = json_data.get('definition', {})
        definition_id = self.modulestore.definition_locator(definition)

        # If no usage id is provided, generate an in-memory id
        if usage_id is None:
            usage_id = LocalId()

        block_locator = BlockUsageLocator(
            version_guid=course_entry_override['_id'],
            usage_id=usage_id,
            course_id=course_entry_override.get('course_id'),
            branch=course_entry_override.get('branch'))

        kvs = SplitMongoKVS(
            definition,
            json_data.get('fields', {}),
            json_data.get('_inherited_settings'),
        )
        field_data = DbModel(kvs)

        try:
            module = self.construct_xblock_from_class(
                class_, field_data,
                ScopeIds(None, json_data.get('category'), definition_id,
                         block_locator))
        except Exception:
            log.warning("Failed to load descriptor", exc_info=True)
            return ErrorDescriptor.from_json(
                json_data,
                self,
                BlockUsageLocator(version_guid=course_entry_override['_id'],
                                  usage_id=usage_id),
                error_msg=exc_info_to_str(sys.exc_info()))

        edit_info = json_data.get('edit_info', {})
        module.edited_by = edit_info.get('edited_by')
        module.edited_on = edit_info.get('edited_on')
        module.previous_version = edit_info.get('previous_version')
        module.update_version = edit_info.get('update_version')
        module.definition_locator = self.modulestore.definition_locator(
            definition)
        # decache any pending field settings
        module.save()

        # If this is an in-memory block, store it in this system
        if isinstance(block_locator.usage_id, LocalId):
            self.local_modules[block_locator] = module

        return module
Example #10
0
def create_xblock(usage, student_id=None):
    """Create an XBlock instance.

    This will be invoked to create new instances for every request.

    """
    block_cls = XBlock.load_class(usage.block_name)
    runtime = WorkbenchRuntime(block_cls, student_id, usage)
    model = DbModel(MEMORY_KVS, block_cls, student_id, usage)
    block = block_cls(runtime, model)
    return block
Example #11
0
def test_default_fn():
    key_store = SerialDefaultKVS()
    db_model = DbModel(key_store)
    tester = TestIntegerXblock(Mock(), db_model, Mock())
    tester2 = TestIntegerXblock(Mock(), db_model, Mock())

    # ensure value is not in tester before any actions
    assert_false(db_model.has(tester, 'counter'))
    # ensure value is same over successive calls for same DbModel
    first_call = tester.counter
    assert_equals(first_call, 1)
    assert_equals(first_call, tester.counter)
    # ensure the value is not saved in the object
    assert_false(db_model.has(tester, 'counter'))
    # ensure save does not save the computed default back to the object
    tester.save()
    assert_false(db_model.has(tester, 'counter'))

    # ensure second object gets another value
    second_call = tester2.counter
    assert_equals(second_call, 2)
Example #12
0
def test_default_fn():
    key_store = SerialDefaultKVS()
    db_model = DbModel(key_store)
    tester = TestIntegerXblock(Mock(), db_model, Mock())
    tester2 = TestIntegerXblock(Mock(), db_model, Mock())

    # ensure value is not in tester before any actions
    assert_false(db_model.has(tester, 'counter'))
    # ensure value is same over successive calls for same DbModel
    first_call = tester.counter
    assert_equals(first_call, 1)
    assert_equals(first_call, tester.counter)
    # ensure the value is not saved in the object
    assert_false(db_model.has(tester, 'counter'))
    # ensure save does not save the computed default back to the object
    tester.save()
    assert_false(db_model.has(tester, 'counter'))

    # ensure second object gets another value
    second_call = tester2.counter
    assert_equals(second_call, 2)
Example #13
0
    def _create_new_model_data(self, category, location, definition_data,
                               metadata):
        """
        To instantiate a new xmodule which will be saved latter, set up the dbModel and kvs
        """
        kvs = MongoKeyValueStore(definition_data, [], metadata, location,
                                 category)

        class_ = XModuleDescriptor.load_class(category, self.default_class)
        model_data = DbModel(kvs, class_, None, MongoUsage(None, location))
        model_data['category'] = category
        model_data['location'] = location
        return model_data
Example #14
0
def _load_preview_module(request, descriptor):
    """
    Return a preview XModule instantiated from the supplied descriptor.

    request: The active django request
    descriptor: An XModuleDescriptor
    """
    student_data = DbModel(SessionKeyValueStore(request))
    descriptor.bind_for_student(
        _preview_module_system(request, descriptor),
        LmsFieldData(descriptor._field_data, student_data),  # pylint: disable=protected-access
    )
    return descriptor
Example #15
0
    def _create_new_field_data(self, category, location, definition_data,
                               metadata):
        """
        To instantiate a new xmodule which will be saved latter, set up the dbModel and kvs
        """
        kvs = MongoKeyValueStore(
            definition_data,
            [],
            metadata,
        )

        field_data = DbModel(kvs)
        return field_data
Example #16
0
    def load_item(self, location):
        """
        Return an XModule instance for the specified location
        """
        location = Location(location)
        json_data = self.module_data.get(location)
        if json_data is None:
            module = self.modulestore.get_item(location)
            if module is not None:
                # update our own cache after going to the DB to get cache miss
                self.module_data.update(module.system.module_data)
            return module
        else:
            # load the module and apply the inherited metadata
            try:
                class_ = XModuleDescriptor.load_class(
                    json_data['location']['category'],
                    self.default_class
                )
                definition = json_data.get('definition', {})
                metadata = json_data.get('metadata', {})
                for old_name, new_name in class_.metadata_translations.items():
                    if old_name in metadata:
                        metadata[new_name] = metadata[old_name]
                        del metadata[old_name]

                kvs = MongoKeyValueStore(
                    definition.get('data', {}),
                    definition.get('children', []),
                    metadata,
                )

                model_data = DbModel(kvs, class_, None, MongoUsage(
                    self.course_id, location))
                module = class_(self, location, model_data)
                if self.cached_metadata is not None:
                    # parent container pointers don't differentiate between draft and non-draft
                    # so when we do the lookup, we should do so with a non-
                    # draft location
                    non_draft_loc = location._replace(revision=None)
                    metadata_to_inherit = self.cached_metadata.get(
                        non_draft_loc.url(), {})
                    inherit_metadata(module, metadata_to_inherit)
                return module
            except:
                log.warning("Failed to load descriptor", exc_info=True)
                return ErrorDescriptor.from_json(
                    json_data,
                    self,
                    error_msg=exc_info_to_str(sys.exc_info())
                )
Example #17
0
    def xblock_from_json(self,
                         class_,
                         usage_id,
                         json_data,
                         course_entry_override=None):
        if course_entry_override is None:
            course_entry_override = self.course_entry
        # most likely a lazy loader or the id directly
        definition = json_data.get('definition', {})

        block_locator = BlockUsageLocator(
            version_guid=course_entry_override['_id'],
            usage_id=usage_id,
            course_id=course_entry_override.get('course_id'),
            branch=course_entry_override.get('branch'))

        kvs = SplitMongoKVS(definition, json_data.get('fields', {}),
                            json_data.get('_inherited_settings'),
                            block_locator, json_data.get('category'))
        model_data = DbModel(
            kvs,
            class_,
            None,
            SplitMongoKVSid(
                # DbModel req's that these support .url()
                block_locator,
                self.modulestore.definition_locator(definition)))

        try:
            module = class_(self, model_data)
        except Exception:
            log.warning("Failed to load descriptor", exc_info=True)
            if usage_id is None:
                usage_id = "MISSING"
            return ErrorDescriptor.from_json(
                json_data,
                self,
                BlockUsageLocator(version_guid=course_entry_override['_id'],
                                  usage_id=usage_id),
                error_msg=exc_info_to_str(sys.exc_info()))

        edit_info = json_data.get('edit_info', {})
        module.edited_by = edit_info.get('edited_by')
        module.edited_on = edit_info.get('edited_on')
        module.previous_version = edit_info.get('previous_version')
        module.update_version = edit_info.get('update_version')
        module.definition_locator = self.modulestore.definition_locator(
            definition)
        # decache any pending field settings
        module.save()
        return module
Example #18
0
def load_preview_module(request, preview_id, descriptor):
    """
    Return a preview XModule instantiated from the supplied descriptor.

    request: The active django request
    preview_id (str): An identifier specifying which preview this module is used for
    descriptor: An XModuleDescriptor
    """
    student_data = DbModel(SessionKeyValueStore(request))
    descriptor.bind_for_student(
        preview_module_system(request, preview_id, descriptor),
        LmsFieldData(descriptor._field_data, student_data),  # pylint: disable=protected-access
    )
    return descriptor
Example #19
0
def test_view_counter_state():
    key_store = DictKeyValueStore()
    db_model = DbModel(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)
Example #20
0
def test_runtime_handle():
    # Test a simple handler and a fallback handler

    key_store = DictKeyValueStore()
    db_model = DbModel(key_store)
    tester = TestXBlock(Mock(), db_model, Mock())
    runtime = MockRuntimeForQuerying()
    # string we want to update using the handler
    update_string = "user state update"
    assert_equals(runtime.handle(tester, 'existing_handler', update_string),
                  'I am the existing test handler')
    assert_equals(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(tester, 'test_fallback_handler', new_update_string),
                  'I have been handled')
    assert_equals(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(tester, 'handler_without_correct_decoration', new_update_string),
                  'gone to fallback')
    assert_equals(tester.user_state, new_update_string)

    # handler can't be found & no fallback handler supplied, should throw an exception
    tester = TestXBlockNoFallback(Mock(), db_model, Mock())
    ultimate_string = "ultimate update"
    with assert_raises(NoSuchHandlerError):
        runtime.handle(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(tester, 'handler_without_correct_decoration', 'handled')
Example #21
0
    def from_xml(cls, xml_data, system, org=None, course=None):
        """
        Creates an instance of this descriptor from the supplied xml_data.
        This may be overridden by subclasses

        xml_data: A string of xml that will be translated into data and children for
            this module
        system: A DescriptorSystem for interacting with external resources
        org and course are optional strings that will be used in the generated modules
            url identifiers
        """

        xml_object = etree.fromstring(xml_data)
        # VS[compat] -- just have the url_name lookup, once translation is done
        url_name = xml_object.get('url_name', xml_object.get('slug'))
        location = Location('i4x', org, course, xml_object.tag, url_name)

        # VS[compat] -- detect new-style each-in-a-file mode
        if is_pointer_tag(xml_object):
            # new style:
            # read the actual definition file--named using url_name.replace(':','/')
            filepath = cls._format_filepath(xml_object.tag,
                                            name_to_pathname(url_name))
            definition_xml = cls.load_file(filepath, system.resources_fs,
                                           location)
        else:
            definition_xml = xml_object
            filepath = None

        definition, children = cls.load_definition(
            definition_xml, system, location)  # note this removes metadata

        # VS[compat] -- make Ike's github preview links work in both old and
        # new file layouts
        if is_pointer_tag(xml_object):
            # new style -- contents actually at filepath
            definition['filename'] = [filepath, filepath]

        metadata = cls.load_metadata(definition_xml)

        # move definition metadata into dict
        dmdata = definition.get('definition_metadata', '')
        if dmdata:
            metadata['definition_metadata_raw'] = dmdata
            try:
                metadata.update(json.loads(dmdata))
            except Exception as err:
                log.debug('Error %s in loading metadata %s' % (err, dmdata))
                metadata['definition_metadata_err'] = str(err)

        # Set/override any metadata specified by policy
        k = policy_key(location)
        if k in system.policy:
            cls.apply_policy(metadata, system.policy[k])

        field_data = {}
        field_data.update(metadata)
        field_data.update(definition)
        field_data['children'] = children

        field_data['xml_attributes']['filename'] = definition.get(
            'filename', ['', None])  # for git link
        field_data['location'] = location
        field_data['category'] = xml_object.tag
        kvs = InheritanceKeyValueStore(initial_values=field_data)
        field_data = DbModel(kvs)

        return system.construct_xblock_from_class(
            cls,
            # We're loading a descriptor, so student_id is meaningless
            # We also don't have separate notions of definition and usage ids yet,
            # so we use the location for both
            ScopeIds(None, location.category, location, location),
            field_data,
        )
Example #22
0
 def xblock_model_data(descriptor):
     return DbModel(
         LmsKeyValueStore(descriptor._model_data, model_data_cache),
         descriptor.module_class, user.id,
         LmsUsage(descriptor.location, descriptor.location))
Example #23
0
 def xblock_field_data(descriptor):
     student_data = DbModel(DjangoKeyValueStore(field_data_cache))
     return lms_field_data(descriptor._field_data, student_data)
Example #24
0
 def __init__(self, student_id=None):
     super(WorkbenchRuntime, self).__init__(USAGE_STORE,
                                            DbModel(WORKBENCH_KVS))
     self.student_id = student_id
Example #25
0
def get_module_for_descriptor_internal(user,
                                       descriptor,
                                       field_data_cache,
                                       course_id,
                                       track_function,
                                       xqueue_callback_url_prefix,
                                       position=None,
                                       wrap_xmodule_display=True,
                                       grade_bucket_type=None,
                                       static_asset_path=''):
    """
    Actually implement get_module, without requiring a request.

    See get_module() docstring for further details.
    """

    # Short circuit--if the user shouldn't have access, bail without doing any work
    if not has_access(user, descriptor, 'load', course_id):
        return None

    student_data = DbModel(DjangoKeyValueStore(field_data_cache))
    descriptor._field_data = LmsFieldData(descriptor._field_data, student_data)

    # Setup system context for module instance
    ajax_url = reverse(
        'modx_dispatch',
        kwargs=dict(course_id=course_id,
                    location=descriptor.location.url(),
                    dispatch=''),
    )
    # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
    ajax_url = ajax_url.rstrip('/')

    def make_xqueue_callback(dispatch='score_update'):
        # Fully qualified callback URL for external queueing system
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(course_id=course_id,
                        userid=str(user.id),
                        mod_id=descriptor.location.url(),
                        dispatch=dispatch),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': xqueue_interface,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor,
                                         "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface[
            'mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface[
            'mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key':
            getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key':
            getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name':
            getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user, descriptor, field_data_cache, course_id, track_function,
            make_xqueue_callback, position, wrap_xmodule_display,
            grade_bucket_type, static_asset_path)

    def publish(event):
        """A function that allows XModules to publish events. This only supports grade changes right now."""
        if event.get('event_name') != 'grade':
            return

        # Construct the key for the module
        key = KeyValueStore.Key(scope=Scope.user_state,
                                user_id=user.id,
                                block_scope_id=descriptor.location,
                                field_name='grade')

        student_module = field_data_cache.find_or_create(key)
        # Update the grades
        student_module.grade = event.get('value')
        student_module.max_grade = event.get('max_value')
        # Save all changes to the underlying KeyValueStore
        student_module.save()

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade,
                                        student_module.max_grade)
        org, course_num, run = course_id.split("/")

        tags = [
            "org:{0}".format(org), "course:{0}".format(course_num),
            "run:{0}".format(run), "score_bucket:{0}".format(score_bucket)
        ]

        if grade_bucket_type is not None:
            tags.append('type:%s' % grade_bucket_type)

        dog_stats_api.increment("lms.courseware.question_answered", tags=tags)

    # Build a list of wrapping functions that will be applied in order
    # to the Fragment content coming out of the xblocks that are about to be rendered.
    block_wrappers = []

    # Wrap the output display in a single div to allow for the XModule
    # javascript to be bound correctly
    if wrap_xmodule_display is True:
        block_wrappers.append(wrap_xblock)

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from

    # Rewrite urls beginning in /static to point to course-specific content
    block_wrappers.append(
        partial(replace_static_urls,
                getattr(descriptor, 'data_dir', None),
                course_id=course_id,
                static_asset_path=static_asset_path
                or descriptor.static_asset_path))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    block_wrappers.append(partial(replace_course_urls, course_id))

    # this will rewrite intra-courseware links (/jump_to_id/<id>). This format
    # is an improvement over the /course/... format for studio authored courses,
    # because it is agnostic to course-hierarchy.
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work.
    block_wrappers.append(
        partial(
            replace_jump_to_id_urls,
            course_id,
            reverse('jump_to_id',
                    kwargs={
                        'course_id': course_id,
                        'module_id': ''
                    }),
        ))

    if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
        if has_access(user, descriptor, 'staff', course_id):
            block_wrappers.append(partial(add_histogram, user))

    system = ModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        static_url=settings.STATIC_URL,
        ajax_url=ajax_url,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.runtime.resources_fs,
        get_module=inner_get_module,
        user=user,
        debug=settings.DEBUG,
        hostname=settings.SITE_NAME,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_id=course_id,
            static_asset_path=static_asset_path
            or descriptor.static_asset_path,
        ),
        replace_course_urls=partial(static_replace.replace_course_urls,
                                    course_id=course_id),
        replace_jump_to_id_urls=partial(static_replace.replace_jump_to_id_urls,
                                        course_id=course_id,
                                        jump_to_id_base_url=reverse(
                                            'jump_to_id',
                                            kwargs={
                                                'course_id': course_id,
                                                'module_id': ''
                                            })),
        node_path=settings.NODE_PATH,
        publish=publish,
        anonymous_student_id=unique_id_for_user(user),
        course_id=course_id,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
        # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
        mixins=descriptor.runtime.mixologist._mixins,  # pylint: disable=protected-access
        wrappers=block_wrappers,
    )

    # pass position specified in URL to module through ModuleSystem
    system.set('position', position)
    if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'):
        system.set(
            'psychometrics_handler',  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user,
                                                   descriptor.location.url()))

    system.set('user_is_staff',
               has_access(user, descriptor.location, 'staff', course_id))

    # make an ErrorDescriptor -- assuming that the descriptor's system is ok
    if has_access(user, descriptor.location, 'staff', course_id):
        system.error_descriptor_class = ErrorDescriptor
    else:
        system.error_descriptor_class = NonStaffErrorDescriptor

    descriptor.xmodule_runtime = system
    descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id)
    return descriptor
Example #26
0
 def preview_field_data(descriptor):
     "Helper method to create a DbModel from a descriptor"
     student_data = DbModel(SessionKeyValueStore(request))
     return lms_field_data(descriptor._field_data, student_data)