Example #1
0
    def include_theme_files(self, fragment):
        """
        Gets theme configuration and renders theme css into fragment
        """
        theme = self.get_theme()
        if not theme or 'package' not in theme:
            return

        theme_package, theme_files = theme.get('package', None), theme.get('locations', [])
        resource_loader = ResourceLoader(theme_package)
        for theme_file in theme_files:
            fragment.add_css(resource_loader.load_unicode(theme_file))
    def student_view(self, context):
        """
        XBlock student view of this component.

        Makes a request to `lti_launch_handler` either
        in an iframe or in a new window depending on the
        configuration of the instance of this XBlock

        Arguments:
            context (dict): XBlock context

        Returns:
            xblock.fragment.Fragment: XBlock HTML fragment
        """
        fragment = Fragment()
        loader = ResourceLoader(__name__)
        context.update(self._get_context_for_template())
        fragment.add_content(loader.render_mako_template('/templates/html/student.html', context))
        fragment.add_css(loader.load_unicode('static/css/student.css'))
        fragment.add_javascript(loader.load_unicode('static/js/xblock_lti_consumer.js'))
        fragment.initialize_js('LtiConsumerXBlock')
        return fragment
    def student_view(self, context):
        """
        XBlock student view of this component.

        Arguments:
            context (dict): XBlock context

        Returns:
            xblock.fragment.Fragment: XBlock HTML fragment
        """
        fragment = Fragment()
        loader = ResourceLoader(__name__)
        context.update(self._get_context_for_template())
        fragment.add_content(loader.render_mako_template('/templates/player.html', context))

        '''
        Note: DO NOT USE the "latest" folder in production, but specify a version
                from https://aka.ms/ampchangelog . This allows us to run a test
                pass prior to ingesting later versions.
        '''
        fragment.add_javascript(loader.load_unicode('node_modules/videojs-vtt.js/lib/vttcue.js'))

        fragment.add_css_url('//amp.azure.net/libs/amp/1.8.1/skins/amp-default/azuremediaplayer.min.css')
        fragment.add_javascript_url('//amp.azure.net/libs/amp/1.8.1/azuremediaplayer.min.js')

        fragment.add_javascript(loader.load_unicode('static/js/player.js'))

        fragment.add_css(loader.load_unicode('public/css/player.css'))

        # NOTE: The Azure Media Player JS file includes the VTT JavaScript library, so we don't
        # actually need to include our local copy of public/js/vendor/vtt.js. In fact, if we do
        # the overlay subtitles stop working

        # @TODO: Make sure all fields are well structured/formatted, if it is not correct, then
        # print out an error msg in view rather than just silently failing

        fragment.initialize_js('AzureMediaServicesBlock')
        return fragment
    def handle_request(self, request):
        """
        Handler for Outcome Service requests.

        Parses and validates XML request body. Currently, only the
        replaceResultRequest action is supported.

        Example of request body from LTI provider::

        <?xml version = "1.0" encoding = "UTF-8"?>
            <imsx_POXEnvelopeRequest xmlns = "some_link (may be not required)">
              <imsx_POXHeader>
                <imsx_POXRequestHeaderInfo>
                  <imsx_version>V1.0</imsx_version>
                  <imsx_messageIdentifier>528243ba5241b</imsx_messageIdentifier>
                </imsx_POXRequestHeaderInfo>
              </imsx_POXHeader>
              <imsx_POXBody>
                <replaceResultRequest>
                  <resultRecord>
                    <sourcedGUID>
                      <sourcedId>feb-123-456-2929::28883</sourcedId>
                    </sourcedGUID>
                    <result>
                      <resultScore>
                        <language>en-us</language>
                        <textString>0.4</textString>
                      </resultScore>
                    </result>
                  </resultRecord>
                </replaceResultRequest>
              </imsx_POXBody>
            </imsx_POXEnvelopeRequest>

        See /templates/xml/outcome_service_response.xml for the response body format.

        Arguments:
            request (xblock.django.request.DjangoWebobRequest): Request object for current HTTP request

        Returns:
            str: Outcome Service XML response
        """
        resource_loader = ResourceLoader(__name__)
        response_xml_template = resource_loader.load_unicode(
            '/templates/xml/outcome_service_response.xml')

        # Returns when `action` is unsupported.
        # Supported actions:
        #   - replaceResultRequest.
        unsupported_values = {
            'imsx_codeMajor': 'unsupported',
            'imsx_description':
            'Target does not support the requested operation.',
            'imsx_messageIdentifier': 'unknown',
            'response': ''
        }
        # Returns if:
        #   - past due grades are not accepted and grade is past due
        #   - score is out of range
        #   - can't parse response from TP;
        #   - can't verify OAuth signing or OAuth signing is incorrect.
        failure_values = {
            'imsx_codeMajor': 'failure',
            'imsx_description': 'The request has failed.',
            'imsx_messageIdentifier': 'unknown',
            'response': ''
        }
        request_body = request.body.decode('utf-8')

        if not self.xblock.accept_grades_past_due and self.xblock.is_past_due(
        ):
            failure_values['imsx_description'] = "Grade is past due"
            return response_xml_template.format(**failure_values)

        try:
            imsx_message_identifier, sourced_id, score, action = parse_grade_xml_body(
                request_body)
        except LtiError as ex:  # pylint: disable=no-member
            body = escape(request_body) if request_body else ''
            error_message = "Request body XML parsing error: {} {}".format(
                str(ex), body)
            log.debug("[LTI]: %s", error_message)
            failure_values['imsx_description'] = error_message
            return response_xml_template.format(**failure_values)

        # Verify OAuth signing.
        __, secret = self.xblock.lti_provider_key_secret
        try:
            verify_oauth_body_signature(request, secret,
                                        self.xblock.outcome_service_url)
        except (ValueError, LtiError) as ex:
            failure_values['imsx_messageIdentifier'] = escape(
                imsx_message_identifier)
            error_message = "OAuth verification error: " + escape(str(ex))
            failure_values['imsx_description'] = error_message
            log.debug("[LTI]: %s", error_message)
            return response_xml_template.format(**failure_values)

        real_user = self.xblock.runtime.get_real_user(
            urllib.parse.unquote(sourced_id.split(':')[-1]))
        if not real_user:  # that means we can't save to database, as we do not have real user id.
            failure_values['imsx_messageIdentifier'] = escape(
                imsx_message_identifier)
            failure_values['imsx_description'] = "User not found."
            return response_xml_template.format(**failure_values)

        if action == 'replaceResultRequest':
            self.xblock.set_user_module_score(real_user, score,
                                              self.xblock.max_score())

            values = {
                'imsx_codeMajor':
                'success',
                'imsx_description':
                'Score for {sourced_id} is now {score}'.format(
                    sourced_id=sourced_id, score=score),
                'imsx_messageIdentifier':
                escape(imsx_message_identifier),
                'response':
                '<replaceResultResponse/>'
            }
            log.debug(u"[LTI]: Grade is saved.")
            return response_xml_template.format(**values)

        unsupported_values['imsx_messageIdentifier'] = escape(
            imsx_message_identifier)
        log.debug(u"[LTI]: Incorrect action.")
        return response_xml_template.format(**unsupported_values)
    def student_view(self, context=None):
        """
        The primary view of the StaffGradedXBlock, shown to students
        when viewing courses.
        """
        frag = Fragment()
        frag.add_css(self.resource_string("static/css/staff_graded.css"))
        loader = ResourceLoader(__name__)
        _ = self.runtime.service(self, "i18n").ugettext

        # Add i18n js
        statici18n_js_url = self._get_statici18n_js_url()
        if statici18n_js_url:
            frag.add_javascript_url(
                self.runtime.local_resource_url(self, statici18n_js_url))

        frag.add_javascript(
            self.resource_string("static/js/src/staff_graded.js"))
        frag.initialize_js('StaffGradedXBlock')

        context['id'] = self.location.html_id()
        context['instructions'] = markdown.markdown(self.instructions)
        context['display_name'] = self.display_name
        context['is_staff'] = self.runtime.user_is_staff

        course_id = self.location.course_key
        context['available_cohorts'] = [
            cohort.name for cohort in get_course_cohorts(course_id=course_id)
        ]
        context['available_tracks'] = [
            (mode.slug, mode.name)
            for mode in modes_for_course(course_id, only_selectable=False)
        ]

        if context['is_staff']:
            from crum import get_current_request
            from django.middleware.csrf import get_token
            context['import_url'] = self.runtime.handler_url(
                self, "csv_import_handler")
            context['export_url'] = self.runtime.handler_url(
                self, "csv_export_handler")
            context['poll_url'] = self.runtime.handler_url(
                self, "get_results_handler")
            context['csrf_token'] = get_token(get_current_request())
            frag.add_javascript(
                loader.load_unicode('static/js/src/staff_graded.js'))
            frag.initialize_js('StaffGradedProblem',
                               json_args={
                                   k: context[k]
                                   for k in ('csrf_token', 'import_url',
                                             'export_url', 'poll_url', 'id')
                               })

        try:
            score = get_score(self.location, self.runtime.user_id) or {}
            context['grades_available'] = True
        except NoSuchServiceError:
            context['grades_available'] = False
        else:
            if score:
                grade = score['score']
                context['score_string'] = _('{score} / {total} points').format(
                    score=grade, total=self.weight)
            else:
                context['score_string'] = _('{total} points possible').format(
                    total=self.weight)
        frag.add_content(
            loader.render_django_template('static/html/staff_graded.html',
                                          context))
        return frag
    def handle_request(self, request):
        """
        Handler for Outcome Service requests.

        Parses and validates XML request body. Currently, only the
        replaceResultRequest action is supported.

        Example of request body from LTI provider::

        <?xml version = "1.0" encoding = "UTF-8"?>
            <imsx_POXEnvelopeRequest xmlns = "some_link (may be not required)">
              <imsx_POXHeader>
                <imsx_POXRequestHeaderInfo>
                  <imsx_version>V1.0</imsx_version>
                  <imsx_messageIdentifier>528243ba5241b</imsx_messageIdentifier>
                </imsx_POXRequestHeaderInfo>
              </imsx_POXHeader>
              <imsx_POXBody>
                <replaceResultRequest>
                  <resultRecord>
                    <sourcedGUID>
                      <sourcedId>feb-123-456-2929::28883</sourcedId>
                    </sourcedGUID>
                    <result>
                      <resultScore>
                        <language>en-us</language>
                        <textString>0.4</textString>
                      </resultScore>
                    </result>
                  </resultRecord>
                </replaceResultRequest>
              </imsx_POXBody>
            </imsx_POXEnvelopeRequest>

        See /templates/xml/outcome_service_response.xml for the response body format.

        Arguments:
            request (xblock.django.request.DjangoWebobRequest): Request object for current HTTP request

        Returns:
            str: Outcome Service XML response
        """
        resource_loader = ResourceLoader(__name__)
        response_xml_template = resource_loader.load_unicode('/templates/xml/outcome_service_response.xml')

        # Returns when `action` is unsupported.
        # Supported actions:
        #   - replaceResultRequest.
        unsupported_values = {
            'imsx_codeMajor': 'unsupported',
            'imsx_description': 'Target does not support the requested operation.',
            'imsx_messageIdentifier': 'unknown',
            'response': ''
        }
        # Returns if:
        #   - past due grades are not accepted and grade is past due
        #   - score is out of range
        #   - can't parse response from TP;
        #   - can't verify OAuth signing or OAuth signing is incorrect.
        failure_values = {
            'imsx_codeMajor': 'failure',
            'imsx_description': 'The request has failed.',
            'imsx_messageIdentifier': 'unknown',
            'response': ''
        }

        if not self.xblock.accept_grades_past_due and self.xblock.is_past_due:
            failure_values['imsx_description'] = "Grade is past due"
            return response_xml_template.format(**failure_values)

        try:
            imsx_message_identifier, sourced_id, score, action = parse_grade_xml_body(request.body)
        except LtiError as ex:  # pylint: disable=no-member
            body = escape(request.body) if request.body else ''
            error_message = "Request body XML parsing error: {} {}".format(ex.message, body)
            log.debug("[LTI]: %s" + error_message)
            failure_values['imsx_description'] = error_message
            return response_xml_template.format(**failure_values)

        # Verify OAuth signing.
        __, secret = self.xblock.lti_provider_key_secret
        try:
            verify_oauth_body_signature(request, secret, self.xblock.outcome_service_url)
        except (ValueError, LtiError) as ex:
            failure_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
            error_message = "OAuth verification error: " + escape(ex.message)
            failure_values['imsx_description'] = error_message
            log.debug("[LTI]: " + error_message)
            return response_xml_template.format(**failure_values)

        real_user = self.xblock.runtime.get_real_user(urllib.unquote(sourced_id.split(':')[-1]))
        if not real_user:  # that means we can't save to database, as we do not have real user id.
            failure_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
            failure_values['imsx_description'] = "User not found."
            return response_xml_template.format(**failure_values)

        if action == 'replaceResultRequest':
            self.xblock.set_user_module_score(real_user, score, self.xblock.max_score())

            values = {
                'imsx_codeMajor': 'success',
                'imsx_description': 'Score for {sourced_id} is now {score}'.format(sourced_id=sourced_id, score=score),
                'imsx_messageIdentifier': escape(imsx_message_identifier),
                'response': '<replaceResultResponse/>'
            }
            log.debug("[LTI]: Grade is saved.")
            return response_xml_template.format(**values)

        unsupported_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
        log.debug("[LTI]: Incorrect action.")
        return response_xml_template.format(**unsupported_values)