Beispiel #1
0
    def test_get_multi_skills(self):
        example_1 = skill_domain.WorkedExample(
            state_domain.SubtitledHtml('2', '<p>Example Question 1</p>'),
            state_domain.SubtitledHtml('3', '<p>Example Explanation 1</p>'))
        self.save_new_skill(
            'skill_a',
            self.user_id_admin,
            description='Description A',
            misconceptions=[],
            skill_contents=skill_domain.SkillContents(
                state_domain.SubtitledHtml('1', '<p>Explanation</p>'),
                [example_1],
                state_domain.RecordedVoiceovers.from_dict(
                    {'voiceovers_mapping': {
                        '1': {},
                        '2': {},
                        '3': {}
                    }}),
                state_domain.WrittenTranslations.from_dict(
                    {'translations_mapping': {
                        '1': {},
                        '2': {},
                        '3': {}
                    }})))
        self.save_new_skill(
            'skill_b',
            self.user_id_admin,
            description='Description B',
            misconceptions=[],
            skill_contents=skill_domain.SkillContents(
                state_domain.SubtitledHtml('1', '<p>Explanation</p>'),
                [example_1],
                state_domain.RecordedVoiceovers.from_dict(
                    {'voiceovers_mapping': {
                        '1': {},
                        '2': {},
                        '3': {}
                    }}),
                state_domain.WrittenTranslations.from_dict(
                    {'translations_mapping': {
                        '1': {},
                        '2': {},
                        '3': {}
                    }})))

        skills = skill_fetchers.get_multi_skills(['skill_a', 'skill_b'])

        self.assertEqual(len(skills), 2)

        self.assertEqual(skills[0].id, 'skill_a')
        self.assertEqual(skills[0].description, 'Description A')
        self.assertEqual(skills[0].misconceptions, [])

        self.assertEqual(skills[1].id, 'skill_b')
        self.assertEqual(skills[1].description, 'Description B')
        self.assertEqual(skills[1].misconceptions, [])

        with self.assertRaisesRegexp(Exception,
                                     'No skill exists for ID skill_c'):
            skill_fetchers.get_multi_skills(['skill_a', 'skill_c'])
Beispiel #2
0
    def get(self):
        """Handles GET requests."""
        comma_separated_skill_ids = (
            self.request.get('comma_separated_skill_ids'))
        if not comma_separated_skill_ids:
            raise self.InvalidInputException(
                'Expected request to contain parameter '
                'comma_separated_skill_ids.')

        skill_ids = comma_separated_skill_ids.split(',')

        try:
            for skill_id in skill_ids:
                skill_domain.Skill.require_valid_skill_id(skill_id)
        except utils.ValidationError:
            raise self.InvalidInputException('Invalid skill ID %s' % skill_id)

        try:
            skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)

        degrees_of_mastery = skill_services.get_multi_user_skill_mastery(
            self.user_id, skill_ids)

        self.values.update({
            'degrees_of_mastery': degrees_of_mastery
        })
        self.render_json(self.values)
Beispiel #3
0
    def put(self):
        """Handles PUT requests."""
        mastery_change_per_skill = (
            self.payload.get('mastery_change_per_skill'))
        if (not mastery_change_per_skill or
                not isinstance(mastery_change_per_skill, dict)):
            raise self.InvalidInputException(
                'Expected payload to contain mastery_change_per_skill '
                'as a dict.')

        skill_ids = list(mastery_change_per_skill.keys())

        current_degrees_of_mastery = (
            skill_services.get_multi_user_skill_mastery(self.user_id, skill_ids)
        )
        new_degrees_of_mastery = {}

        for skill_id in skill_ids:
            try:
                skill_domain.Skill.require_valid_skill_id(skill_id)
            except utils.ValidationError:
                raise self.InvalidInputException(
                    'Invalid skill ID %s' % skill_id)

            # float(bool) will not raise an error.
            if isinstance(mastery_change_per_skill[skill_id], bool):
                raise self.InvalidInputException(
                    'Expected degree of mastery of skill %s to be a number, '
                    'received %s.'
                    % (skill_id, mastery_change_per_skill[skill_id]))

            try:
                mastery_change_per_skill[skill_id] = (
                    float(mastery_change_per_skill[skill_id]))
            except (TypeError, ValueError):
                raise self.InvalidInputException(
                    'Expected degree of mastery of skill %s to be a number, '
                    'received %s.'
                    % (skill_id, mastery_change_per_skill[skill_id]))

            if current_degrees_of_mastery[skill_id] is None:
                current_degrees_of_mastery[skill_id] = 0.0
            new_degrees_of_mastery[skill_id] = (
                current_degrees_of_mastery[skill_id] +
                mastery_change_per_skill[skill_id])

            if new_degrees_of_mastery[skill_id] < 0.0:
                new_degrees_of_mastery[skill_id] = 0.0
            elif new_degrees_of_mastery[skill_id] > 1.0:
                new_degrees_of_mastery[skill_id] = 1.0

        try:
            skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)

        skill_services.create_multi_user_skill_mastery(
            self.user_id, new_degrees_of_mastery)

        self.render_json({})
    def get(self, comma_separated_skill_ids):
        """Handles GET requests."""
        start_cursor = self.request.get('cursor')
        skill_ids = comma_separated_skill_ids.split(',')
        skill_ids = list(set(skill_ids))

        try:
            _require_valid_skill_ids(skill_ids)
        except utils.ValidationError:
            raise self.InvalidInputException('Invalid skill id')

        try:
            skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)

        (question_summaries, merged_question_skill_links,
         next_start_cursor) = (
             question_services.get_displayable_question_skill_link_details(
                 constants.NUM_QUESTIONS_PER_PAGE, skill_ids, start_cursor))
        return_dicts = []
        for index, summary in enumerate(question_summaries):
            if summary is not None:
                if len(skill_ids) == 1:
                    return_dicts.append({
                        'summary':
                        summary.to_dict(),
                        'skill_id':
                        merged_question_skill_links[index].skill_ids[0],
                        'skill_description':
                        (merged_question_skill_links[index].
                         skill_descriptions[0]),
                        'skill_difficulty':
                        (merged_question_skill_links[index].
                         skill_difficulties[0])
                    })
                else:
                    return_dicts.append({
                        'summary':
                        summary.to_dict(),
                        'skill_ids':
                        merged_question_skill_links[index].skill_ids,
                        'skill_descriptions':
                        (merged_question_skill_links[index].skill_descriptions
                         ),
                        'skill_difficulties':
                        (merged_question_skill_links[index].skill_difficulties)
                    })

        self.values.update({
            'question_summary_dicts': return_dicts,
            'next_start_cursor': next_start_cursor
        })
        self.render_json(self.values)
Beispiel #5
0
    def get(self, topic_name):

        # Topic cannot be None as an exception will be thrown from its decorator
        # if so.
        topic = topic_fetchers.get_topic_by_name(topic_name)
        selected_subtopic_ids = (
            self.normalized_request.get('selected_subtopic_ids'))

        selected_skill_ids = []
        for subtopic in topic.subtopics:
            # An error is not thrown here, since it's fine to just ignore the
            # passed in subtopic IDs, if they don't exist, which would be the
            # case if the creator deletes subtopics after the learner has
            # loaded the topic viewer page.
            if subtopic.id in selected_subtopic_ids:
                selected_skill_ids.extend(subtopic.skill_ids)
        try:
            skills = skill_fetchers.get_multi_skills(selected_skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)
        skill_ids_to_descriptions_map = {}
        for skill in skills:
            skill_ids_to_descriptions_map[skill.id] = skill.description

        self.values.update({
            'topic_name':
            topic.name,
            'skill_ids_to_descriptions_map':
            skill_ids_to_descriptions_map
        })
        self.render_json(self.values)
Beispiel #6
0
    def get(self, story_id):
        """Handles GET requests."""
        story = story_fetchers.get_story_by_id(story_id)
        latest_completed_node_ids = (
            story_fetchers.get_latest_completed_node_ids(self.user_id, story_id)
        )

        if len(latest_completed_node_ids) == 0:
            raise self.PageNotFoundException

        try:
            skills = skill_fetchers.get_multi_skills(
                story.get_acquired_skill_ids_for_node_ids(
                    latest_completed_node_ids
                ))
        except Exception as e:
            raise self.PageNotFoundException(e)
        skill_descriptions = {}
        for skill in skills:
            skill_descriptions[skill.id] = skill.description

        self.values.update({
            'skill_descriptions': skill_descriptions,
            'story_name': story.title
        })
        self.render_json(self.values)
Beispiel #7
0
def _get_target_id_to_skill_opportunity_dict(suggestions):
    """Returns a dict of target_id to skill opportunity summary dict.

    Args:
        suggestions: list(BaseSuggestion). A list of suggestions to retrieve
            opportunity dicts.

    Returns:
        dict. Dict mapping target_id to corresponding skill opportunity dict.
    """
    target_ids = set([s.target_id for s in suggestions])
    opportunity_id_to_opportunity_dict = {
        opp_id: (opp.to_dict() if opp is not None else None)
        for opp_id, opp in opportunity_services.get_skill_opportunities_by_ids(
            list(target_ids)).items()
    }
    opportunity_id_to_skill = {
        skill.id: skill
        for skill in skill_fetchers.get_multi_skills([
            opp['id'] for opp in opportunity_id_to_opportunity_dict.values()
            if opp is not None
        ])
    }

    for opp_id, skill in opportunity_id_to_skill.items():
        if skill is not None:
            opportunity_id_to_opportunity_dict[opp_id]['skill_rubrics'] = [
                rubric.to_dict() for rubric in skill.rubrics
            ]

    return opportunity_id_to_opportunity_dict
Beispiel #8
0
    def get(self, topic_name):

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        # Topic cannot be None as an exception will be thrown from its decorator
        # if so.
        topic = topic_fetchers.get_topic_by_name(topic_name)
        comma_separated_subtopic_ids = self.request.get('selected_subtopic_ids')
        selected_subtopic_ids = comma_separated_subtopic_ids.split(',')

        selected_skill_ids = []
        for subtopic in topic.subtopics:
            # An error is not thrown here, since it's fine to just ignore the
            # passed in subtopic IDs, if they don't exist, which would be the
            # case if the creator deletes subtopics after the learner has
            # loaded the topic viewer page.
            if python_utils.UNICODE(subtopic.id) in selected_subtopic_ids:
                selected_skill_ids.extend(subtopic.skill_ids)
        try:
            skills = skill_fetchers.get_multi_skills(selected_skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)
        skill_ids_to_descriptions_map = {}
        for skill in skills:
            skill_ids_to_descriptions_map[skill.id] = skill.description

        self.values.update({
            'topic_name': topic.name,
            'skill_ids_to_descriptions_map': skill_ids_to_descriptions_map
        })
        self.render_json(self.values)
Beispiel #9
0
def get_rubrics_of_skills(skill_ids):
    """Returns a list of rubrics corresponding to given skills.

    Args:
        skill_ids: list(str). The list of skill IDs.

    Returns:
        dict, list(str). The skill rubrics of skills keyed by their
        corresponding ids and the list of deleted skill ids, if any.
    """
    skills = skill_fetchers.get_multi_skills(skill_ids, strict=False)
    skill_id_to_rubrics_dict = {}

    for skill in skills:
        if skill is not None:
            rubric_dicts = [rubric.to_dict() for rubric in skill.rubrics]
            skill_id_to_rubrics_dict[skill.id] = rubric_dicts

    deleted_skill_ids = []
    for skill_id in skill_ids:
        if skill_id not in skill_id_to_rubrics_dict:
            skill_id_to_rubrics_dict[skill_id] = None
            deleted_skill_ids.append(skill_id)

    return skill_id_to_rubrics_dict, deleted_skill_ids
Beispiel #10
0
def _get_target_id_to_skill_opportunity_dict(suggestions):
    """Returns a dict of target_id to skill opportunity summary dict.

    Args:
        suggestions: list(BaseSuggestion). A list of suggestions to retrieve
            opportunity dicts.

    Returns:
        dict. Dict mapping target_id to corresponding skill opportunity dict.
    """
    target_ids = set([s.target_id for s in suggestions])
    opportunities = (opportunity_services.get_skill_opportunities_by_ids(
        list(target_ids)))
    opportunity_skill_ids = [opp.id for opp in opportunities]
    opportunity_id_to_skill = {
        skill.id: skill
        for skill in skill_fetchers.get_multi_skills(opportunity_skill_ids)
    }
    opportunity_id_to_opportunity = {}
    for opp in opportunities:
        opp_dict = opp.to_dict()
        skill = opportunity_id_to_skill.get(opp.id)
        if skill is not None:
            opp_dict['skill_rubrics'] = [
                rubric.to_dict() for rubric in skill.rubrics
            ]
        opportunity_id_to_opportunity[opp.id] = opp_dict
    return opportunity_id_to_opportunity
Beispiel #11
0
    def get(self):
        """Returns all skill IDs linked to some topic."""

        skill_ids = topic_fetchers.get_all_skill_ids_assigned_to_some_topic()

        skills = skill_fetchers.get_multi_skills(skill_ids, strict=False)

        skill_dicts = [skill.to_dict() for skill in skills]
        self.values.update({'skills': skill_dicts})

        self.render_json(self.values)
Beispiel #12
0
    def get(self):
        """Handles GET requests."""
        skill_ids = (self.normalized_request.get('selected_skill_ids'))

        try:
            for skill_id in skill_ids:
                skill_domain.Skill.require_valid_skill_id(skill_id)
        except utils.ValidationError as e:
            raise self.InvalidInputException('Invalid skill ID %s' %
                                             skill_id) from e

        try:
            skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e) from e

        degrees_of_mastery = skill_services.get_multi_user_skill_mastery(
            self.user_id, skill_ids)

        self.values.update({'degrees_of_mastery': degrees_of_mastery})
        self.render_json(self.values)
Beispiel #13
0
def get_skills_linked_to_question(question_id):
    """Returns a list of skills linked to a particular question.

    Args:
        question_id: str. ID of the question.

    Returns:
        list(Skill). The list of skills that are linked to the question.
    """
    question = get_question_by_id(question_id)
    skills = skill_fetchers.get_multi_skills(question.linked_skill_ids)
    return skills
Beispiel #14
0
    def get(self, comma_separated_skill_ids):
        """Handles GET requests."""

        skill_ids = comma_separated_skill_ids.split(',')
        skills = skill_fetchers.get_multi_skills(skill_ids)

        concept_card_dicts = []
        for skill in skills:
            concept_card_dicts.append(skill.skill_contents.to_dict())

        self.values.update({'concept_card_dicts': concept_card_dicts})

        self.render_json(self.values)
Beispiel #15
0
    def put(self):
        """Handles PUT requests."""
        mastery_change_per_skill = (
            self.normalized_payload.get('mastery_change_per_skill'))

        skill_ids = list(mastery_change_per_skill.keys())

        current_degrees_of_mastery = (
            skill_services.get_multi_user_skill_mastery(
                self.user_id, skill_ids))
        new_degrees_of_mastery = {}

        for skill_id in skill_ids:
            try:
                skill_domain.Skill.require_valid_skill_id(skill_id)
            except utils.ValidationError as e:
                raise self.InvalidInputException('Invalid skill ID %s' %
                                                 skill_id) from e

            if current_degrees_of_mastery[skill_id] is None:
                current_degrees_of_mastery[skill_id] = 0.0
            new_degrees_of_mastery[skill_id] = (
                current_degrees_of_mastery[skill_id] +
                mastery_change_per_skill[skill_id])

            if new_degrees_of_mastery[skill_id] < 0.0:
                new_degrees_of_mastery[skill_id] = 0.0
            elif new_degrees_of_mastery[skill_id] > 1.0:
                new_degrees_of_mastery[skill_id] = 1.0

        try:
            skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e) from e

        skill_services.create_multi_user_skill_mastery(self.user_id,
                                                       new_degrees_of_mastery)

        self.render_json({})
Beispiel #16
0
    def get(self):
        """Returns all skill IDs linked to some topic."""

        skill_ids = topic_services.get_all_skill_ids_assigned_to_some_topic()

        try:
            skills = skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)

        skill_dicts = [skill.to_dict() for skill in skills]
        self.values.update({'skills': skill_dicts})

        self.render_json(self.values)
    def get(self, question_id):
        """Gets the data for the question overview page."""
        question = question_services.get_question_by_id(
            question_id, strict=False)

        associated_skill_dicts = [
            skill.to_dict() for skill in skill_fetchers.get_multi_skills(
                question.linked_skill_ids)]

        self.values.update({
            'question_dict': question.to_dict(),
            'associated_skill_dicts': associated_skill_dicts
        })
        self.render_json(self.values)
    def get(self, selected_skill_ids):
        """Handles GET requests.

        Args:
            selected_skill_ids: list(str). List of skill ids.
        """

        skills = skill_fetchers.get_multi_skills(selected_skill_ids)

        concept_card_dicts = []
        for skill in skills:
            concept_card_dicts.append(skill.skill_contents.to_dict())

        self.values.update({
            'concept_card_dicts': concept_card_dicts
        })

        self.render_json(self.values)
Beispiel #19
0
    def get(self, comma_separated_skill_ids):
        """Handles GET requests."""

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        skill_ids = comma_separated_skill_ids.split(',')
        skills = skill_fetchers.get_multi_skills(skill_ids)

        concept_card_dicts = []
        for skill in skills:
            concept_card_dicts.append(skill.skill_contents.to_dict())

        self.values.update({
            'concept_card_dicts': concept_card_dicts
        })

        self.render_json(self.values)
Beispiel #20
0
    def get(self, comma_separated_skill_ids):
        """Populates the data on skill pages of the skill ids."""

        skill_ids = comma_separated_skill_ids.split(',')

        try:
            for skill_id in skill_ids:
                skill_domain.Skill.require_valid_skill_id(skill_id)
        except utils.ValidationError:
            raise self.PageNotFoundException('Invalid skill id.')
        try:
            skills = skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)

        skill_dicts = [skill.to_dict() for skill in skills]
        self.values.update({'skills': skill_dicts})

        self.render_json(self.values)
Beispiel #21
0
    def post(self, story_id, node_id):
        story = story_fetchers.get_story_by_id(story_id)
        completed_nodes = story_fetchers.get_completed_nodes_in_story(
            self.user_id, story_id)
        completed_node_ids = [
            completed_node.id for completed_node in completed_nodes
        ]
        ordered_nodes = story.story_contents.get_ordered_nodes()

        (next_exp_ids, next_node_id,
         completed_node_ids) = (self._record_node_completion(
             story_id, node_id, completed_node_ids, ordered_nodes))

        ready_for_review_test = False
        exp_summaries = (
            summary_services.get_displayable_exp_summary_dicts_matching_ids(
                next_exp_ids))

        # If there are no questions for any of the acquired skills that the
        # learner has completed, do not show review tests.
        acquired_skills = skill_fetchers.get_multi_skills(
            story.get_acquired_skill_ids_for_node_ids(completed_node_ids))

        acquired_skill_ids = [skill.id for skill in acquired_skills]
        questions_available = len(
            question_services.get_questions_by_skill_ids(
                1, acquired_skill_ids, False)) > 0

        learner_completed_story = len(completed_node_ids) == len(ordered_nodes)
        learner_at_review_point_in_story = (
            len(exp_summaries) != 0
            and (len(completed_node_ids)
                 & constants.NUM_EXPLORATIONS_PER_REVIEW_TEST == 0))
        if questions_available and (learner_at_review_point_in_story
                                    or learner_completed_story):
            ready_for_review_test = True

        return self.render_json({
            'summaries': exp_summaries,
            'ready_for_review_test': ready_for_review_test,
            'next_node_id': next_node_id
        })
Beispiel #22
0
    def _validate_inapplicable_skill_misconception_ids(cls, item):
        """Validate that inapplicable skill misconception ids are valid.

        Args:
            item: datastore_services.Model. QuestionModel to validate.
        """
        inapplicable_skill_misconception_ids = (
            item.inapplicable_skill_misconception_ids)
        skill_misconception_id_mapping = {}
        skill_ids = []
        for skill_misconception_id in inapplicable_skill_misconception_ids:
            skill_id, misconception_id = skill_misconception_id.split('-')
            skill_misconception_id_mapping[skill_id] = misconception_id
            skill_ids.append(skill_id)

        skills = skill_fetchers.get_multi_skills(skill_ids, strict=False)
        for skill in skills:
            if skill is not None:
                misconception_ids = [
                    misconception.id for misconception in skill.misconceptions
                ]
                expected_misconception_id = (
                    skill_misconception_id_mapping[skill.id])
                if int(expected_misconception_id) not in misconception_ids:
                    cls._add_error(
                        'misconception id',
                        'Entity id %s: misconception with the id %s does '
                        'not exist in the skill with id %s' %
                        (item.id, expected_misconception_id, skill.id))
        missing_skill_ids = utils.compute_list_difference(
            skill_ids, [skill.id for skill in skills if skill is not None])
        for skill_id in missing_skill_ids:
            cls._add_error(
                'skill id',
                'Entity id %s: skill with the following id does not exist:'
                ' %s' % (item.id, skill_id))
Beispiel #23
0
    def get(self, comma_separated_skill_ids):
        """Handles GET requests."""
        try:
            offset = int(self.request.get('offset'))
        except Exception:
            raise self.InvalidInputException('Invalid offset')

        skill_ids = comma_separated_skill_ids.split(',')
        skill_ids = list(set(skill_ids))

        try:
            _require_valid_skill_ids(skill_ids)
        except utils.ValidationError:
            raise self.InvalidInputException('Invalid skill id')

        try:
            skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)

        question_summaries, merged_question_skill_links = (
            question_services.get_displayable_question_skill_link_details(
                constants.NUM_QUESTIONS_PER_PAGE + 1, skill_ids, offset))

        if len(question_summaries) <= constants.NUM_QUESTIONS_PER_PAGE:
            more = False
        else:
            more = True
            question_summaries.pop()
            merged_question_skill_links.pop()

        return_dicts = []
        for index, summary in enumerate(question_summaries):
            if summary is not None:
                if len(skill_ids) == 1:
                    return_dicts.append({
                        'summary':
                        summary.to_dict(),
                        'skill_id':
                        merged_question_skill_links[index].skill_ids[0],
                        'skill_description':
                        (merged_question_skill_links[index].
                         skill_descriptions[0]),
                        'skill_difficulty':
                        (merged_question_skill_links[index].
                         skill_difficulties[0])
                    })
                else:
                    return_dicts.append({
                        'summary':
                        summary.to_dict(),
                        'skill_ids':
                        merged_question_skill_links[index].skill_ids,
                        'skill_descriptions':
                        (merged_question_skill_links[index].skill_descriptions
                         ),
                        'skill_difficulties':
                        (merged_question_skill_links[index].skill_difficulties)
                    })

        self.values.update({
            'question_summary_dicts': return_dicts,
            'more': more
        })
        self.render_json(self.values)
Beispiel #24
0
    def post(self):
        """Handles POST requests."""
        skill_ids = self.payload.get('skill_ids')

        if not skill_ids:
            raise self.InvalidInputException(
                'skill_ids parameter isn\'t present in the payload')

        if len(skill_ids) > constants.MAX_SKILLS_PER_QUESTION:
            raise self.InvalidInputException(
                'More than %d QuestionSkillLinks for one question '
                'is not supported.' % constants.MAX_SKILLS_PER_QUESTION)
        try:
            for skill_id in skill_ids:
                skill_domain.Skill.require_valid_skill_id(skill_id)
        except Exception as e:
            raise self.InvalidInputException('Skill ID(s) aren\'t valid: ', e)

        try:
            skill_fetchers.get_multi_skills(skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)

        question_dict = self.payload.get('question_dict')
        if ((question_dict['id'] is not None)
                or ('question_state_data' not in question_dict)
                or ('language_code' not in question_dict)
                or (question_dict['version'] != 0)):
            raise self.InvalidInputException(
                'Question Data should contain id, state data, language code, '
                + 'and its version should be set as 0')

        question_dict['question_state_data_schema_version'] = (
            feconf.CURRENT_STATE_SCHEMA_VERSION)
        question_dict['id'] = question_services.get_new_question_id()
        question_dict['linked_skill_ids'] = skill_ids

        try:
            question = question_domain.Question.from_dict(question_dict)
        except Exception as e:
            raise self.InvalidInputException('Question structure is invalid:',
                                             e)

        skill_difficulties = self.payload.get('skill_difficulties')

        if not skill_difficulties:
            raise self.InvalidInputException(
                'skill_difficulties not present in the payload')
        if len(skill_ids) != len(skill_difficulties):
            raise self.InvalidInputException(
                'Skill difficulties don\'t match up with skill IDs')

        try:
            skill_difficulties = [
                float(difficulty) for difficulty in skill_difficulties
            ]
        except (ValueError, TypeError) as e:
            raise self.InvalidInputException(
                'Skill difficulties must be a float value') from e
        if any((difficulty < 0 or difficulty > 1)
               for difficulty in skill_difficulties):
            raise self.InvalidInputException(
                'Skill difficulties must be between 0 and 1')

        question_services.add_question(self.user_id, question)
        question_services.link_multiple_skills_for_question(
            self.user_id, question.id, skill_ids, skill_difficulties)
        html_list = question.question_state_data.get_all_html_content_strings()
        filenames = (
            html_cleaner.get_image_filenames_from_html_strings(html_list))
        image_validation_error_message_suffix = (
            'Please go to the question editor for question with id %s and edit '
            'the image.' % question.id)
        for filename in filenames:
            image = self.request.get(filename)
            if not image:
                logging.exception(
                    'Image not provided for file with name %s when the question'
                    ' with id %s was created.' % (filename, question.id))
                raise self.InvalidInputException(
                    'No image data provided for file with name %s. %s' %
                    (filename, image_validation_error_message_suffix))
            try:
                file_format = (
                    image_validation_services.validate_image_and_filename(
                        image, filename))
            except utils.ValidationError as e:
                e = '%s %s' % (e, image_validation_error_message_suffix)
                raise self.InvalidInputException(e)
            image_is_compressible = (file_format
                                     in feconf.COMPRESSIBLE_IMAGE_FORMATS)
            fs_services.save_original_and_compressed_versions_of_image(
                filename, feconf.ENTITY_TYPE_QUESTION, question.id, image,
                'image', image_is_compressible)

        self.values.update({'question_id': question.id})
        self.render_json(self.values)
Beispiel #25
0
    def post(self, story_id, node_id):
        if not constants.ENABLE_NEW_STRUCTURE_VIEWER_UPDATES:
            raise self.PageNotFoundException

        try:
            story_fetchers.get_node_index_by_story_id_and_node_id(
                story_id, node_id)
        except Exception as e:
            raise self.PageNotFoundException(e)

        story = story_fetchers.get_story_by_id(story_id)
        completed_nodes = story_fetchers.get_completed_nodes_in_story(
            self.user_id, story_id)
        completed_node_ids = [
            completed_node.id for completed_node in completed_nodes
        ]

        ordered_nodes = [
            node for node in story.story_contents.get_ordered_nodes()
        ]

        next_exp_ids = []
        next_node_id = None
        if not node_id in completed_node_ids:
            story_services.record_completed_node_in_story_context(
                self.user_id, story_id, node_id)

            completed_nodes = story_fetchers.get_completed_nodes_in_story(
                self.user_id, story_id)
            completed_node_ids = [
                completed_node.id for completed_node in completed_nodes
            ]

            for node in ordered_nodes:
                if node.id not in completed_node_ids:
                    next_exp_ids = [node.exploration_id]
                    next_node_id = node.id
                    break

        ready_for_review_test = False
        exp_summaries = (
            summary_services.get_displayable_exp_summary_dicts_matching_ids(
                next_exp_ids))

        # If there are no questions for any of the acquired skills that the
        # learner has completed, do not show review tests.
        acquired_skills = skill_fetchers.get_multi_skills(
            story.get_acquired_skill_ids_for_node_ids(completed_node_ids))

        acquired_skill_ids = [skill.id for skill in acquired_skills]
        questions_available = len(
            question_services.get_questions_by_skill_ids(
                1, acquired_skill_ids, False)) > 0

        learner_completed_story = len(completed_nodes) == len(ordered_nodes)
        learner_at_review_point_in_story = (
            len(exp_summaries) != 0 and
            (len(completed_nodes) & constants.NUM_EXPLORATIONS_PER_REVIEW_TEST
             == 0))
        if questions_available and (learner_at_review_point_in_story
                                    or learner_completed_story):
            ready_for_review_test = True

        return self.render_json({
            'summaries': exp_summaries,
            'ready_for_review_test': ready_for_review_test,
            'next_node_id': next_node_id
        })
Beispiel #26
0
    def post(self, story_id, node_id):
        story = story_fetchers.get_story_by_id(story_id)
        if story is None:
            logging.error('Could not find a story corresponding to '
                          '%s id.' % story_id)
            self.render_json({})
            return
        topic = topic_fetchers.get_topic_by_id(story.corresponding_topic_id)
        completed_nodes = story_fetchers.get_completed_nodes_in_story(
            self.user_id, story_id)
        completed_node_ids = [
            completed_node.id for completed_node in completed_nodes
        ]
        ordered_nodes = story.story_contents.get_ordered_nodes()

        (next_exp_ids, next_node_id,
         completed_node_ids) = (self._record_node_completion(
             story_id, node_id, completed_node_ids, ordered_nodes))

        ready_for_review_test = False
        exp_summaries = (
            summary_services.get_displayable_exp_summary_dicts_matching_ids(
                next_exp_ids))

        # If there are no questions for any of the acquired skills that the
        # learner has completed, do not show review tests.
        acquired_skills = skill_fetchers.get_multi_skills(
            story.get_acquired_skill_ids_for_node_ids(completed_node_ids))

        acquired_skill_ids = [skill.id for skill in acquired_skills]
        questions_available = len(
            question_services.get_questions_by_skill_ids(
                1, acquired_skill_ids, False)) > 0

        learner_completed_story = len(completed_node_ids) == len(ordered_nodes)
        learner_at_review_point_in_story = (
            len(exp_summaries) != 0
            and (len(completed_node_ids)
                 & constants.NUM_EXPLORATIONS_PER_REVIEW_TEST == 0))
        if questions_available and (learner_at_review_point_in_story
                                    or learner_completed_story):
            ready_for_review_test = True

        # If there is no next_node_id, the story is marked as completed else
        # mark the story as incomplete.
        if next_node_id is None:
            learner_progress_services.mark_story_as_completed(
                self.user_id, story_id)
        else:
            learner_progress_services.record_story_started(
                self.user_id, story.id)

        completed_story_ids = (
            learner_progress_services.get_all_completed_story_ids(
                self.user_id))
        story_ids_in_topic = []
        for story in topic.canonical_story_references:
            story_ids_in_topic.append(story.story_id)

        is_topic_completed = set(story_ids_in_topic).intersection(
            set(completed_story_ids))

        # If at least one story in the topic is completed,
        # mark the topic as learnt else mark it as partially learnt.
        if not is_topic_completed:
            learner_progress_services.record_topic_started(
                self.user_id, topic.id)
        else:
            learner_progress_services.mark_topic_as_learnt(
                self.user_id, topic.id)

        return self.render_json({
            'summaries': exp_summaries,
            'ready_for_review_test': ready_for_review_test,
            'next_node_id': next_node_id
        })