def fragment_text_rewriting(self, fragment):
        """
        Do replacements like `/jump_to_id` URL rewriting in the provided text
        """
        # TODO: Why do we need to use `xmodule_runtime` and not `runtime`?
        try:
            course_id = self.xmodule_runtime.course_id
        except AttributeError:
            # TODO-WORKBENCH-WORKAROUND: To allow to load from the workbench
            course_id = 'sample-course'

        try:
            jump_to_url = reverse('jump_to_id', kwargs={'course_id': course_id, 'module_id': ''})
        except:
            # TODO-WORKBENCH-WORKAROUND: To allow to load from the workbench
            jump_to_url = '/jump_to_id'

        fragment = replace_jump_to_id_urls(course_id, jump_to_url, self, 'student_view', fragment, {})
        return fragment
示例#2
0
    def fragment_text_rewriting(self, fragment):
        """
        Do replacements like `/jump_to_id` URL rewriting in the provided text
        """
        # TODO: Why do we need to use `xmodule_runtime` and not `runtime`?
        try:
            course_id = self.xmodule_runtime.course_id
        except AttributeError:
            # TODO-WORKBENCH-WORKAROUND: To allow to load from the workbench
            course_id = 'sample-course'

        try:
            jump_to_url = reverse('jump_to_id',
                                  kwargs={
                                      'course_id': course_id,
                                      'module_id': ''
                                  })
        except:
            # TODO-WORKBENCH-WORKAROUND: To allow to load from the workbench
            jump_to_url = '/jump_to_id'

        fragment = replace_jump_to_id_urls(course_id, jump_to_url, self,
                                           'student_view', fragment, {})
        return fragment
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

    # 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", ""),
        }

    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 xblock_field_data(descriptor):
        student_data = DbModel(DjangoKeyValueStore(field_data_cache))
        return lms_field_data(descriptor._field_data, student_data)

    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, student_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)

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

    # 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

    system = ModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        ajax_url=ajax_url,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.system.resources_fs,
        get_module=inner_get_module,
        user=user,
        # 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,
        xblock_field_data=xblock_field_data,
        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.system.mixologist._mixins,
    )

    # pass position specified in URL to module through ModuleSystem
    system.set("position", position)
    system.set("DEBUG", settings.DEBUG)
    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()),
        )

    try:
        module = descriptor.xmodule(system)
    except:
        log.exception("Error creating module from descriptor {0}".format(descriptor))

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

        err_descriptor = err_descriptor_class.from_descriptor(descriptor, error_msg=exc_info_to_str(sys.exc_info()))

        # Make an error module
        return err_descriptor.xmodule(system)

    system.set("user_is_staff", has_access(user, descriptor.location, "staff", course_id))
    _get_html = module.get_html

    if wrap_xmodule_display is True:
        _get_html = wrap_xmodule(module.get_html, module, "xmodule_display.html")

    module.get_html = replace_static_urls(
        _get_html,
        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
    module.get_html = replace_course_urls(module.get_html, course_id)

    # this will rewrite intra-courseware links
    # that use the shorthand /jump_to_id/<id>. This is very helpful
    # for studio authored courses (compared to the /course/... format) since it is
    # is durable with respect to moves and the author doesn't need to
    # know the 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
    module.get_html = replace_jump_to_id_urls(
        module.get_html, 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, module, "staff", course_id):
            module.get_html = add_histogram(module.get_html, module, user)

    # force the module to save after rendering
    module.get_html = save_module(module.get_html, module)
    return module
示例#4
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

    # 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 xblock_field_data(descriptor):
        student_data = DbModel(DjangoKeyValueStore(field_data_cache))
        return lms_field_data(descriptor._field_data, student_data)

    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,
                                student_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)

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

    # 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

    system = ModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        ajax_url=ajax_url,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.system.resources_fs,
        get_module=inner_get_module,
        user=user,
        # 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,
        xblock_field_data=xblock_field_data,
        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.system.mixologist._mixins,
    )

    # pass position specified in URL to module through ModuleSystem
    system.set('position', position)
    system.set('DEBUG', settings.DEBUG)
    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()))

    try:
        module = descriptor.xmodule(system)
    except:
        log.exception(
            "Error creating module from descriptor {0}".format(descriptor))

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

        err_descriptor = err_descriptor_class.from_descriptor(
            descriptor, error_msg=exc_info_to_str(sys.exc_info()))

        # Make an error module
        return err_descriptor.xmodule(system)

    system.set('user_is_staff',
               has_access(user, descriptor.location, 'staff', course_id))
    _get_html = module.get_html

    if wrap_xmodule_display is True:
        _get_html = wrap_xmodule(module.get_html, module,
                                 'xmodule_display.html')

    module.get_html = replace_static_urls(_get_html,
                                          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
    module.get_html = replace_course_urls(module.get_html, course_id)

    # this will rewrite intra-courseware links
    # that use the shorthand /jump_to_id/<id>. This is very helpful
    # for studio authored courses (compared to the /course/... format) since it is
    # is durable with respect to moves and the author doesn't need to
    # know the 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
    module.get_html = replace_jump_to_id_urls(
        module.get_html, 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, module, 'staff', course_id):
            module.get_html = add_histogram(module.get_html, module, user)

    # force the module to save after rendering
    module.get_html = save_module(module.get_html, module)
    return module