Пример #1
0
    def check_problem(self, data):
        """
        Checks whether answers to a problem are correct

        Returns a map of correct/incorrect answers:
          {'success' : 'correct' | 'incorrect' | AJAX alert msg string,
           'contents' : html}
        """
        event_info = dict()
        event_info['state'] = self.lcp.get_state()
        event_info['problem_id'] = self.location.url()

        answers = self.make_dict_of_responses(data)
        event_info['answers'] = convert_files_to_filenames(answers)

        # Too late. Cannot submit
        if self.closed():
            event_info['failure'] = 'closed'
            self.system.track_function('problem_check_fail', event_info)
            raise NotFoundError('Problem is closed')

        # Problem submitted. Student should reset before checking again
        if self.done and self.rerandomize == "always":
            event_info['failure'] = 'unreset'
            self.system.track_function('problem_check_fail', event_info)
            raise NotFoundError('Problem must be reset before it can be checked again')

        # Problem queued. Students must wait a specified waittime before they are allowed to submit
        if self.lcp.is_queued():
            current_time = datetime.datetime.now(UTC())
            prev_submit_time = self.lcp.get_recentmost_queuetime()
            waittime_between_requests = self.system.xqueue['waittime']
            if (current_time - prev_submit_time).total_seconds() < waittime_between_requests:
                msg = u'You must wait at least {wait} seconds between submissions'.format(
                    wait=waittime_between_requests)
                return {'success': msg, 'html': ''}  # Prompts a modal dialog in ajax callback

        try:
            correct_map = self.lcp.grade_answers(answers)
            self.attempts = self.attempts + 1
            self.lcp.done = True
            self.set_state_from_lcp()

        except (StudentInputError, ResponseError, LoncapaProblemError) as inst:
            log.warning("StudentInputError in capa_module:problem_check",
                        exc_info=True)

            # If the user is a staff member, include
            # the full exception, including traceback,
            # in the response
            if self.system.user_is_staff:
                msg = u"Staff debug info: {tb}".format(tb=cgi.escape(traceback.format_exc()))

            # Otherwise, display just an error message,
            # without a stack trace
            else:
                msg = u"Error: {msg}".format(msg=inst.message)

            return {'success': msg}

        except Exception as err:
            if self.system.DEBUG:
                msg = u"Error checking problem: {}".format(err.message)
                msg += u'\nTraceback:\n{}'.format(traceback.format_exc())
                return {'success': msg}
            raise

        published_grade = self.publish_grade()

        # success = correct if ALL questions in this problem are correct
        success = 'correct'
        for answer_id in correct_map:
            if not correct_map.is_correct(answer_id):
                success = 'incorrect'

        # NOTE: We are logging both full grading and queued-grading submissions. In the latter,
        #       'success' will always be incorrect
        event_info['grade'] = published_grade['grade']
        event_info['max_grade'] = published_grade['max_grade']
        event_info['correct_map'] = correct_map.get_dict()
        event_info['success'] = success
        event_info['attempts'] = self.attempts
        self.system.track_function('problem_check', event_info)

        if hasattr(self.system, 'psychometrics_handler'):  # update PsychometricsData using callback
            self.system.psychometrics_handler(self.get_state_for_lcp())

        # render problem into HTML
        html = self.get_problem_html(encapsulate=False)

        return {'success': success,
                'contents': html,
                }
Пример #2
0
 def handle_ajax(self, _dispatch, _data):
     raise NotFoundError('Unexpected dispatch type')
Пример #3
0
 def handle_ajax(self, dispatch, get):  # TODO: bounds checking
     ''' get = request.POST instance '''
     if dispatch == 'goto_position':
         self.position = int(get['position'])
         return json.dumps({'success': True})
     raise NotFoundError('Unexpected dispatch type')
def get_transcript_from_blockstore(video_block, language, output_format,
                                   transcripts_info):
    """
    Get video transcript from Blockstore.

    Blockstore expects video transcripts to be placed into the 'static/'
    subfolder of the XBlock's folder in a Blockstore bundle. For example, if the
    video XBlock's definition is in the standard location of
        video/video1/definition.xml
    Then the .srt files should be placed at e.g.
        video/video1/static/video1-en.srt
    This is the same place where other public static files are placed for other
    XBlocks, such as image files used by HTML blocks.

    Video XBlocks in Blockstore must set the 'transcripts' XBlock field to a
    JSON dictionary listing the filename of the transcript for each language:
        <video
            youtube_id_1_0="3_yD_cEKoCk"
            transcripts='{"en": "3_yD_cEKoCk-en.srt"}'
            display_name="Welcome Video with Transcript"
            download_track="true"
        />

    This method is tested in openedx/core/djangoapps/content_libraries/tests/test_static_assets.py

    Arguments:
        video_block (Video XBlock): The video XBlock
        language (str): transcript language
        output_format (str): transcript output format
        transcripts_info (dict): transcript info for a video, from video_block.get_transcripts_info()

    Returns:
        tuple containing content, filename, mimetype
    """
    if output_format not in (Transcript.SRT, Transcript.SJSON, Transcript.TXT):
        raise NotFoundError(f'Invalid transcript format `{output_format}`')
    transcripts = transcripts_info['transcripts']
    if language not in transcripts:
        raise NotFoundError(
            "Video {} does not have a transcript file defined for the '{}' language in its OLX."
            .format(
                video_block.scope_ids.usage_id,
                language,
            ))
    filename = transcripts[language]
    if not filename.endswith('.srt'):
        # We want to standardize on .srt
        raise NotFoundError(
            "Video XBlocks in Blockstore only support .srt transcript files.")
    # Try to load the transcript file out of Blockstore
    # In lieu of an XBlock API for this (like block.runtime.resources_fs), we use the blockstore API directly.
    bundle_uuid = video_block.scope_ids.def_id.bundle_uuid
    path = video_block.scope_ids.def_id.olx_path.rpartition(
        '/')[0] + '/static/' + filename
    bundle_version = video_block.scope_ids.def_id.bundle_version  # Either bundle_version or draft_name will be set.
    draft_name = video_block.scope_ids.def_id.draft_name
    try:
        content_binary = blockstore_cache.get_bundle_file_data_with_cache(
            bundle_uuid, path, bundle_version, draft_name)
    except blockstore_api.BundleFileNotFound:
        raise NotFoundError(
            "Transcript file '{}' missing for video XBlock {}".format(  # lint-amnesty, pylint: disable=raise-missing-from
                path,
                video_block.scope_ids.usage_id,
            ))
    # Now convert the transcript data to the requested format:
    filename_no_extension = os.path.splitext(filename)[0]
    output_filename = f'{filename_no_extension}.{output_format}'
    output_transcript = Transcript.convert(
        content_binary.decode('utf-8'),
        input_format=Transcript.SRT,
        output_format=output_format,
    )
    if not output_transcript.strip():
        raise NotFoundError('No transcript content')
    return output_transcript, output_filename, Transcript.mime_types[
        output_format]
def get_transcript_from_contentstore(video,
                                     language,
                                     output_format,
                                     transcripts_info,
                                     youtube_id=None):
    """
    Get video transcript from content store.

    Arguments:
        video (Video Descriptor): Video descriptor
        language (unicode): transcript language
        output_format (unicode): transcript output format
        transcripts_info (dict): transcript info for a video
        youtube_id (unicode): youtube video id

    Returns:
        tuple containing content, filename, mimetype
    """
    input_format, base_name, transcript_content = None, None, None
    if output_format not in (Transcript.SRT, Transcript.SJSON, Transcript.TXT):
        raise NotFoundError(f'Invalid transcript format `{output_format}`')

    sub, other_languages = transcripts_info['sub'], transcripts_info[
        'transcripts']
    transcripts = dict(other_languages)

    # this is sent in case of a translation dispatch and we need to use it as our subs_id.
    possible_sub_ids = [youtube_id, sub, video.youtube_id_1_0] + get_html5_ids(
        video.html5_sources)
    for sub_id in possible_sub_ids:
        try:
            transcripts['en'] = sub_id
            input_format, base_name, transcript_content = get_transcript_for_video(
                video.location,
                subs_id=sub_id,
                file_name=transcripts[language],
                language=language)
            break
        except (KeyError, NotFoundError):
            continue

    if transcript_content is None:
        raise NotFoundError(
            'No transcript for `{lang}` language'.format(lang=language))

    # add language prefix to transcript file only if language is not None
    language_prefix = f'{language}_' if language else ''
    transcript_name = f'{language_prefix}{base_name}.{output_format}'
    transcript_content = Transcript.convert(transcript_content,
                                            input_format=input_format,
                                            output_format=output_format)
    if not transcript_content.strip():
        raise NotFoundError('No transcript content')

    if youtube_id:
        youtube_ids = youtube_speed_dict(video)
        transcript_content = json.dumps(
            generate_subs(youtube_ids.get(youtube_id, 1), 1,
                          json.loads(transcript_content)))

    return transcript_content, transcript_name, Transcript.mime_types[
        output_format]
Пример #6
0
    def rescore_problem(self):
        """
        Checks whether the existing answers to a problem are correct.

        This is called when the correct answer to a problem has been changed,
        and the grade should be re-evaluated.

        Returns a dict with one key:
            {'success' : 'correct' | 'incorrect' | AJAX alert msg string }

        Raises NotFoundError if called on a problem that has not yet been
        answered, or NotImplementedError if it's a problem that cannot be rescored.

        Returns the error messages for exceptions occurring while performing
        the rescoring, rather than throwing them.
        """
        event_info = {
            'state': self.lcp.get_state(),
            'problem_id': self.location.url()
        }

        if not self.lcp.supports_rescoring():
            event_info['failure'] = 'unsupported'
            self.system.track_function('problem_rescore_fail', event_info)
            raise NotImplementedError(
                "Problem's definition does not support rescoring")

        if not self.done:
            event_info['failure'] = 'unanswered'
            self.system.track_function('problem_rescore_fail', event_info)
            raise NotFoundError(
                'Problem must be answered before it can be graded again')

        # get old score, for comparison:
        orig_score = self.lcp.get_score()
        event_info['orig_score'] = orig_score['score']
        event_info['orig_total'] = orig_score['total']

        try:
            correct_map = self.lcp.rescore_existing_answers()

        except (StudentInputError, ResponseError, LoncapaProblemError) as inst:
            log.warning("Input error in capa_module:problem_rescore",
                        exc_info=True)
            event_info['failure'] = 'input_error'
            self.system.track_function('problem_rescore_fail', event_info)
            return {'success': u"Error: {0}".format(inst.message)}

        except Exception as err:
            event_info['failure'] = 'unexpected'
            self.system.track_function('problem_rescore_fail', event_info)
            if self.system.DEBUG:
                msg = u"Error checking problem: {0}".format(err.message)
                msg += u'\nTraceback:\n' + traceback.format_exc()
                return {'success': msg}
            raise

        # rescoring should have no effect on attempts, so don't
        # need to increment here, or mark done.  Just save.
        self.set_state_from_lcp()

        self.publish_grade()

        new_score = self.lcp.get_score()
        event_info['new_score'] = new_score['score']
        event_info['new_total'] = new_score['total']

        # success = correct if ALL questions in this problem are correct
        success = 'correct'
        for answer_id in correct_map:
            if not correct_map.is_correct(answer_id):
                success = 'incorrect'

        # NOTE: We are logging both full grading and queued-grading submissions. In the latter,
        #       'success' will always be incorrect
        event_info['correct_map'] = correct_map.get_dict()
        event_info['success'] = success
        event_info['attempts'] = self.attempts
        self.system.track_function('problem_rescore', event_info)

        # psychometrics should be called on rescoring requests in the same way as check-problem
        if hasattr(self.system, 'psychometrics_handler'
                   ):  # update PsychometricsData using callback
            self.system.psychometrics_handler(self.get_state_for_lcp())

        return {'success': success}
Пример #7
0
    def check_problem(self, get):
        ''' Checks whether answers to a problem are correct, and
            returns a map of correct/incorrect answers:

            {'success' : 'correct' | 'incorrect' | AJAX alert msg string,
             'contents' : html}
            '''
        event_info = dict()
        event_info['state'] = self.lcp.get_state()
        event_info['problem_id'] = self.location.url()

        answers = self.make_dict_of_responses(get)
        event_info['answers'] = convert_files_to_filenames(answers)
        # Too late. Cannot submit
        if self.closed():
            event_info['failure'] = 'closed'
            self.system.track_function('save_problem_check_fail', event_info)
            raise NotFoundError('Problem is closed')

        # Problem submitted. Student should reset before checking again
        if self.done and self.rerandomize == "always":
            event_info['failure'] = 'unreset'
            self.system.track_function('save_problem_check_fail', event_info)
            raise NotFoundError(
                'Problem must be reset before it can be checked again')

        # Problem queued. Students must wait a specified waittime before they are allowed to submit
        if self.lcp.is_queued():
            current_time = datetime.datetime.now(UTC())
            prev_submit_time = self.lcp.get_recentmost_queuetime()
            waittime_between_requests = self.system.xqueue['waittime']
            if (current_time - prev_submit_time
                ).total_seconds() < waittime_between_requests:
                msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests
                return {
                    'success': msg,
                    'html': ''
                }  # Prompts a modal dialog in ajax callback

        try:
            correct_map = self.lcp.grade_answers(answers)
            self.set_state_from_lcp()

        except (StudentInputError, ResponseError, LoncapaProblemError) as inst:
            log.warning("StudentInputError in capa_module:problem_check",
                        exc_info=True)

            # If the user is a staff member, include
            # the full exception, including traceback,
            # in the response
            if self.system.user_is_staff:
                msg = "Staff debug info: %s" % traceback.format_exc()

            # Otherwise, display just an error message,
            # without a stack trace
            else:
                msg = "Error: %s" % str(inst.message)

            return {'success': msg}

        except Exception, err:
            if self.system.DEBUG:
                msg = "Error checking problem: " + str(err)
                msg += '\nTraceback:\n' + traceback.format_exc()
                return {'success': msg}
            raise
Пример #8
0
    def handle_ajax(self, dispatch, data):
        """
        Update values of xfields, that were changed by student.
        """
        from openedx.core.djangoapps.video_analytics.views import create_video_analytics_record
        accepted_keys = [
            'speed', 'auto_advance', 'saved_video_position',
            'transcript_language', 'transcript_download_format',
            'youtube_is_available', 'bumper_last_view_date',
            'bumper_do_not_show_again'
        ]

        conversions = {
            'speed': json.loads,
            'auto_advance': json.loads,
            'saved_video_position': RelativeTime.isotime_to_timedelta,
            'youtube_is_available': json.loads,
            'bumper_last_view_date': to_boolean,
            'bumper_do_not_show_again': to_boolean,
        }

        if dispatch == 'save_user_state':
            for key in data:
                if key in accepted_keys:
                    if key in conversions:
                        value = conversions[key](data[key])
                    else:
                        value = data[key]

                    if key == 'bumper_last_view_date':
                        value = now()

                    if key == 'speed' and math.isnan(value):
                        message = u"Invalid speed value {}, must be a float.".format(
                            value)
                        log.warning(message)
                        return json.dumps({'success': False, 'error': message})

                    setattr(self, key, value)

                    if key == 'speed':
                        self.global_speed = self.speed
            log.info("start vide analytics record code")
            user_id = self.runtime.user_id
            course_id = str(self.runtime.course_id)
            if self.html5_sources:
                video_path = str(self.html5_sources[0])
            elif self.youtube_id_1_0:
                video_path = self.youtube_id_1_0
            elif self.youtube_id_1_25:
                video_path = self.youtube_id_1_25
            elif self.youtube_id_1_5:
                video_path = self.youtube_id_1_5
            elif self.youtube_id_0_75:
                video_path = self.youtube_id_0_75
            else:
                video_path = ''
            watch_min = round(self.saved_video_position.total_seconds() / 60,
                              2)
            block_id = str(self.parent.block_id)
            start_date = datetime.datetime.now()
            total_length = 0.0
            log.info("video analytics record will create")
            create_video_analytics_record(user_id, course_id, video_path,
                                          watch_min, block_id, start_date,
                                          total_length)
            log.info("video analytics record created")
            return json.dumps({'success': True})

        log.debug(u"GET {0}".format(data))
        log.debug(u"DISPATCH {0}".format(dispatch))

        raise NotFoundError('Unexpected dispatch type')
Пример #9
0
 def handle_ajax(self, dispatch, get):
     raise NotFoundError('Unexpected dispatch type')
Пример #10
0
def get_transcript_from_contentstore(video,
                                     language,
                                     output_format,
                                     youtube_id=None,
                                     is_bumper=False):
    """
    Get video transcript from content store.

    Arguments:
        video (Video Descriptor): Video descriptor
        language (unicode): transcript language
        output_format (unicode): transcript output format
        youtube_id (unicode): youtube video id
        is_bumper (bool): indicates bumper video

    Returns:
        tuple containing content, filename, mimetype
    """
    if output_format not in (Transcript.SRT, Transcript.SJSON, Transcript.TXT):
        raise NotFoundError(
            'Invalid transcript format `{output_format}`'.format(
                output_format=output_format))

    transcripts_info = video.get_transcripts_info(is_bumper=is_bumper)
    sub, other_languages = transcripts_info['sub'], transcripts_info[
        'transcripts']
    transcripts = dict(other_languages)

    # this is sent in case of a translation dispatch and we need to use it as our subs_id.
    if youtube_id:
        transcripts['en'] = youtube_id
    elif sub:
        transcripts['en'] = sub
    elif video.youtube_id_1_0:
        transcripts['en'] = video.youtube_id_1_0
    elif language == u'en':
        raise NotFoundError('No transcript for `en` language')

    try:
        input_format, base_name, transcript_content = get_transcript_for_video(
            video.location,
            subs_id=transcripts['en'],
            file_name=language and transcripts[language],
            language=language)
    except KeyError:
        raise NotFoundError

    # add language prefix to transcript file only if language is not None
    language_prefix = '{}_'.format(language) if language else ''
    transcript_name = u'{}{}.{}'.format(language_prefix, base_name,
                                        output_format)
    transcript_content = Transcript.convert(transcript_content,
                                            input_format=input_format,
                                            output_format=output_format)

    if not transcript_content.strip():
        raise NotFoundError('No transcript content')

    if youtube_id:
        youtube_ids = youtube_speed_dict(video)
        transcript_content = json.dumps(
            generate_subs(youtube_ids.get(youtube_id, 1), 1,
                          json.loads(transcript_content)))

    return transcript_content, transcript_name, Transcript.mime_types[
        output_format]