Exemple #1
0
    def test_replace_skill_id_for_all_questions(self):
        question_id_2 = question_services.get_new_question_id()
        self.save_new_question(question_id_2, self.editor_id,
                               self._create_valid_question_data('ABC'),
                               ['skill_1'])

        question_id_3 = question_services.get_new_question_id()
        self.save_new_question(question_id_3, self.editor_id,
                               self._create_valid_question_data('ABC'),
                               ['skill_2'])
        question_services.create_new_question_skill_link(
            self.editor_id, self.question_id, 'skill_1', 0.5)
        question_services.create_new_question_skill_link(
            self.editor_id, question_id_2, 'skill_1', 0.3)
        question_services.create_new_question_skill_link(
            self.editor_id, question_id_3, 'skill_2', 0.9)

        question_skill_links = (
            question_services.get_question_skill_links_of_skill(
                'skill_1', 'Skill Description 1'))

        self.assertEqual(len(question_skill_links), 2)
        question_ids = [
            question_skill.question_id
            for question_skill in question_skill_links
        ]
        self.assertItemsEqual(question_ids, [self.question_id, question_id_2])
        for question_skill in question_skill_links:
            if question_skill.question_id == self.question_id:
                self.assertEqual(question_skill.skill_difficulty, 0.5)

        question_services.replace_skill_id_for_all_questions(
            'skill_1', 'Description 1', 'skill_3')

        question_skill_links = (
            question_services.get_question_skill_links_of_skill(
                'skill_1', 'Description 1'))

        self.assertEqual(len(question_skill_links), 0)
        question_skill_links = (
            question_services.get_question_skill_links_of_skill(
                'skill_3', 'Skill Description 3'))

        question_ids = [
            question_skill.question_id
            for question_skill in question_skill_links
        ]
        self.assertItemsEqual(question_ids, [self.question_id, question_id_2])
        for question_skill in question_skill_links:
            if question_skill.question_id == self.question_id:
                self.assertEqual(question_skill.skill_difficulty, 0.5)

        questions = question_fetchers.get_questions_by_ids(
            [self.question_id, question_id_2, question_id_3])
        for question in questions:
            if question.id in ([self.question_id, question_id_2]):
                self.assertItemsEqual(question.linked_skill_ids, ['skill_3'])
            else:
                self.assertItemsEqual(question.linked_skill_ids, ['skill_2'])
Exemple #2
0
def untag_deleted_misconceptions(
        committer_id, skill_id, skill_description,
        deleted_skill_misconception_ids):
    """Untags deleted misconceptions from questions belonging
    to a skill with the provided skill_id.

    Args:
        committer_id: str. The id of the user who triggered the update.
        skill_id: str. The skill id.
        skill_description: str. The description of the skill.
        deleted_skill_misconception_ids: list(str). The skill misconception
            ids of deleted misconceptions. The list items take the form
            <skill_id>-<misconception_id>.
    """
    question_skill_links = get_question_skill_links_of_skill(
        skill_id, skill_description)
    question_ids = [model.question_id for model in question_skill_links]
    questions = question_fetchers.get_questions_by_ids(question_ids)
    for question in questions:
        change_list = []
        inapplicable_skill_misconception_ids = (
            question.inapplicable_skill_misconception_ids)
        deleted_inapplicable_skill_misconception_ids = (
            list(
                set(deleted_skill_misconception_ids) &
                set(inapplicable_skill_misconception_ids)))
        if deleted_inapplicable_skill_misconception_ids:
            new_inapplicable_skill_misconception_ids = (
                utils.compute_list_difference(
                    question.inapplicable_skill_misconception_ids,
                    deleted_inapplicable_skill_misconception_ids))
            change_list.append(question_domain.QuestionChange({
                'cmd': 'update_question_property',
                'property_name': 'inapplicable_skill_misconception_ids',
                'new_value': new_inapplicable_skill_misconception_ids,
                'old_value': question.inapplicable_skill_misconception_ids
            }))
        old_question_state_data_dict = question.question_state_data.to_dict()
        answer_groups = (
            list(question.question_state_data.interaction.answer_groups))
        for answer_group in answer_groups:
            tagged_skill_misconception_id = (
                answer_group.to_dict()['tagged_skill_misconception_id'])
            if (tagged_skill_misconception_id
                    in deleted_skill_misconception_ids):
                answer_group.tagged_skill_misconception_id = None
        question.question_state_data.interaction.answer_groups = answer_groups
        change_list.append(question_domain.QuestionChange({
            'cmd': 'update_question_property',
            'property_name': 'question_state_data',
            'new_value': question.question_state_data.to_dict(),
            'old_value': old_question_state_data_dict
        }))
        update_question(
            committer_id, question.id, change_list,
            'Untagged deleted skill misconception ids.')
def untag_deleted_misconceptions(committer_id, skill_id, skill_description):
    """Untags deleted misconceptions from questions belonging
    to a skill with the provided skill_id.

    Args:
        committer_id: str. The id of the user who triggered the update.
        skill_id: str. The skill id.
        skill_description: str. The description of the skill.
    """
    question_skill_links = get_question_skill_links_of_skill(
        skill_id, skill_description)
    question_ids = [model.question_id for model in question_skill_links]
    questions = question_fetchers.get_questions_by_ids(question_ids)
    skill = skill_fetchers.get_skill_by_id(skill_id)
    skill_misconception_ids = (
        [
            skill.generate_skill_misconception_id(misconception.id)
            for misconception in skill.misconceptions
        ]
    )
    for question in questions:
        change_list = []
        inapplicable_skill_misconception_ids = (
            question.inapplicable_skill_misconception_ids)
        deleted_inapplicable_skill_misconception_ids = (
            utils.compute_list_difference(
                inapplicable_skill_misconception_ids, skill_misconception_ids))
        for misconception_id in deleted_inapplicable_skill_misconception_ids:
            old_misconception_ids = list(
                question.inapplicable_skill_misconception_ids)
            question.inapplicable_skill_misconception_ids.remove(
                misconception_id)
            change_list.append(question_domain.QuestionChange({
                'cmd': 'update_question_property',
                'property_name': 'inapplicable_skill_misconception_ids',
                'new_value': question.inapplicable_skill_misconception_ids,
                'old_value': old_misconception_ids
            }))
        old_question_state_data_dict = question.question_state_data.to_dict()
        answer_groups = (
            list(question.question_state_data.interaction.answer_groups))
        for i in python_utils.RANGE(len(answer_groups)):
            tagged_skill_misconception_id = (
                answer_groups[i].to_dict()['tagged_skill_misconception_id'])
            if tagged_skill_misconception_id not in skill_misconception_ids:
                answer_groups[i].tagged_skill_misconception_id = None
        question.question_state_data.interaction.answer_groups = answer_groups
        change_list.append(question_domain.QuestionChange({
            'cmd': 'update_question_property',
            'property_name': 'question_state_data',
            'new_value': question.question_state_data.to_dict(),
            'old_value': old_question_state_data_dict
        }))
        update_question(
            committer_id, question.id, change_list,
            'Untagged deleted misconception id.')
Exemple #4
0
 def test_get_questions_by_ids(self):
     question_id_2 = question_services.get_new_question_id()
     self.save_new_question(question_id_2, self.editor_id,
                            self._create_valid_question_data('DEF'),
                            ['skill_1'])
     questions = question_fetchers.get_questions_by_ids(
         [self.question_id, 'invalid_question_id', question_id_2])
     self.assertEqual(len(questions), 3)
     self.assertEqual(questions[0].id, self.question_id)
     self.assertIsNone(questions[1])
     self.assertEqual(questions[2].id, question_id_2)
def get_questions_by_skill_ids(total_question_count, skill_ids,
                               require_medium_difficulty):
    """Returns constant number of questions linked to each given skill id.

    Args:
        total_question_count: int. The total number of questions to return.
        skill_ids: list(str). The IDs of the skills to which the questions
            should be linked.
        require_medium_difficulty: bool. Indicates whether the returned
            questions should be of medium difficulty.

    Returns:
        list(Question). The list containing an expected number of
        total_question_count questions linked to each given skill id.
        question count per skill will be total_question_count divided by
        length of skill_ids, and it will be rounded up if not evenly
        divisible. If not enough questions for one skill, simply return
        all questions linked to it. The order of questions will follow the
        order of given skill ids, and the order of questions for the same
        skill is random when require_medium_difficulty is false, otherwise
        the order is sorted by absolute value of the difference between
        skill difficulty and the medium difficulty.

    Raises:
        Exception. Question count is higher than the maximum limit.
    """

    if total_question_count > feconf.MAX_QUESTIONS_FETCHABLE_AT_ONE_TIME:
        raise Exception(
            'Question count is too high, please limit the question count to '
            '%d.' % feconf.MAX_QUESTIONS_FETCHABLE_AT_ONE_TIME)

    if require_medium_difficulty:
        question_skill_link_models = (
            question_models.QuestionSkillLinkModel.
            get_question_skill_links_based_on_difficulty_equidistributed_by_skill(  # pylint: disable=line-too-long
                total_question_count, skill_ids,
                constants.SKILL_DIFFICULTY_LABEL_TO_FLOAT[
                    constants.SKILL_DIFFICULTY_MEDIUM]))
    else:
        question_skill_link_models = (
            question_models.QuestionSkillLinkModel.
            get_question_skill_links_equidistributed_by_skill(
                total_question_count, skill_ids))

    question_ids = [model.question_id for model in question_skill_link_models]
    questions = question_fetchers.get_questions_by_ids(question_ids)
    return questions
 def test_get_questions_by_ids(self) -> None:
     question_id_2 = question_services.get_new_question_id(
     )  # type: ignore[no-untyped-call]
     self.save_new_question(  # type: ignore[no-untyped-call]
         question_id_2, self.editor_id,
         self._create_valid_question_data('DEF'),
         ['skill_1'])  # type: ignore[no-untyped-call]
     questions = question_fetchers.get_questions_by_ids(
         [self.question_id, 'invalid_question_id', question_id_2])
     self.assertEqual(len(questions), 3)
     # Ruling out the possibility of None for mypy type checking.
     assert questions[0] is not None
     self.assertEqual(questions[0].id, self.question_id)
     self.assertIsNone(questions[1])
     # Ruling out the possibility of None for mypy type checking.
     assert questions[2] is not None
     self.assertEqual(questions[2].id, question_id_2)