예제 #1
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({})
예제 #2
0
    def test_put_with_skill_mastery_lower_than_zero(self):
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_1,
                                                 self.degree_of_mastery_1)
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_2,
                                                 self.degree_of_mastery_2)

        payload = {}
        mastery_change_per_skill = {
            self.skill_id_1: -0.5,
            self.skill_id_2: 0.3
        }
        payload['mastery_change_per_skill'] = mastery_change_per_skill

        self.login(self.NEW_USER_EMAIL)
        csrf_token = self.get_new_csrf_token()
        self.put_json('%s' % feconf.SKILL_MASTERY_DATA_URL,
                      payload,
                      csrf_token=csrf_token)

        degrees_of_mastery = {self.skill_id_1: 0.0, self.skill_id_2: 0.8}

        self.assertEqual(
            skill_services.get_multi_user_skill_mastery(
                self.user_id, [self.skill_id_1, self.skill_id_2]),
            degrees_of_mastery)

        self.logout()
예제 #3
0
    def get(self):
        """Handles GET requests."""
        comma_separated_topic_ids = (
            self.request.get('comma_separated_topic_ids'))
        topic_ids = comma_separated_topic_ids.split(',')
        topics = topic_fetchers.get_topics_by_ids(topic_ids)
        all_skill_ids = []
        subtopic_mastery_dict = {}

        for ind, topic in enumerate(topics):
            if not topic:
                raise self.InvalidInputException('Invalid topic ID %s' %
                                                 topic_ids[ind])
            all_skill_ids.extend(topic.get_all_skill_ids())

        all_skill_ids = list(set(all_skill_ids))
        all_skills_mastery_dict = skill_services.get_multi_user_skill_mastery(
            self.user_id, all_skill_ids)
        for topic in topics:
            subtopic_mastery_dict[topic.id] = {}
            for subtopic in topic.subtopics:
                skill_mastery_dict = {
                    skill_id: mastery
                    for skill_id, mastery in all_skills_mastery_dict.items()
                    if mastery is not None and skill_id in subtopic.skill_ids
                }
                if skill_mastery_dict:
                    # Subtopic mastery is average of skill masteries.
                    subtopic_mastery_dict[topic.id][subtopic.id] = (
                        python_utils.divide(sum(skill_mastery_dict.values()),
                                            len(skill_mastery_dict)))

        self.values.update({'subtopic_mastery_dict': subtopic_mastery_dict})
        self.render_json(self.values)
예제 #4
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)
예제 #5
0
    def get(self, topic_name):
        """Handles GET requests."""

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        topic = topic_fetchers.get_topic_by_name(topic_name)
        canonical_story_ids = topic.get_canonical_story_ids(
            include_only_published=True)
        additional_story_ids = topic.get_additional_story_ids(
            include_only_published=True)
        canonical_story_summaries = [
            story_fetchers.get_story_summary_by_id(canonical_story_id)
            for canonical_story_id in canonical_story_ids
        ]

        additional_story_summaries = [
            story_fetchers.get_story_summary_by_id(additional_story_id)
            for additional_story_id in additional_story_ids
        ]

        canonical_story_dicts = [
            summary.to_human_readable_dict()
            for summary in canonical_story_summaries
        ]

        additional_story_dicts = [
            summary.to_human_readable_dict()
            for summary in additional_story_summaries
        ]

        uncategorized_skill_ids = topic.get_all_uncategorized_skill_ids()
        subtopics = topic.get_all_subtopics()

        assigned_skill_ids = topic.get_all_skill_ids()
        skill_descriptions = skill_services.get_skill_descriptions_by_ids(
            topic.id, assigned_skill_ids)

        if self.user_id:
            degrees_of_mastery = skill_services.get_multi_user_skill_mastery(
                self.user_id, assigned_skill_ids)
        else:
            degrees_of_mastery = {}
            for skill_id in assigned_skill_ids:
                degrees_of_mastery[skill_id] = None

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'canonical_story_dicts': canonical_story_dicts,
            'additional_story_dicts': additional_story_dicts,
            'uncategorized_skill_ids': uncategorized_skill_ids,
            'subtopics': subtopics,
            'degrees_of_mastery': degrees_of_mastery,
            'skill_descriptions': skill_descriptions
        })
        self.render_json(self.values)
예제 #6
0
    def test_get_multi_user_skill_mastery(self):
        degree_of_mastery = skill_services.get_multi_user_skill_mastery(
            self.USER_ID, self.SKILL_IDS)

        self.assertEqual(
            degree_of_mastery, {
                self.SKILL_ID_1: self.DEGREE_OF_MASTERY_1,
                self.SKILL_ID_2: self.DEGREE_OF_MASTERY_2,
                self.SKILL_ID_3: None
            })
예제 #7
0
    def test_create_multi_user_skill_mastery(self):
        skill_id_4 = skill_services.get_new_skill_id()
        skill_id_5 = skill_services.get_new_skill_id()
        skill_services.create_multi_user_skill_mastery(
            self.USER_ID, {skill_id_4: 0.3, skill_id_5: 0.5})

        degrees_of_mastery = skill_services.get_multi_user_skill_mastery(
            self.USER_ID, [skill_id_4, skill_id_5])

        self.assertEqual(
            degrees_of_mastery, {skill_id_4: 0.3, skill_id_5: 0.5})
예제 #8
0
    def test_filter_skills_by_mastery(self):
        # Create feconf.MAX_NUMBER_OF_SKILL_IDS + 3 skill_ids
        # to test two things:
        #   1. The skill_ids should be sorted.
        #   2. The filtered skill_ids should be feconf.MAX_NUMBER_OF_SKILL_IDS
        # in number.

        # List of mastery values (float values between 0.0 and 1.0)
        masteries = [self.DEGREE_OF_MASTERY_1, self.DEGREE_OF_MASTERY_2, None]

        # Creating feconf.MAX_NUMBER_OF_SKILL_IDS additional user skill
        # masteries.
        for _ in python_utils.RANGE(feconf.MAX_NUMBER_OF_SKILL_IDS):
            skill_id = skill_services.get_new_skill_id()
            mastery = random.random()
            masteries.append(mastery)
            skill_services.create_user_skill_mastery(self.USER_ID, skill_id,
                                                     mastery)
            self.SKILL_IDS.append(skill_id)

        # Sorting the masteries, which should represent the masteries of the
        # skill_ids that are finally returned.
        masteries.sort()
        degrees_of_masteries = skill_services.get_multi_user_skill_mastery(
            self.USER_ID, self.SKILL_IDS)
        arranged_filtered_skill_ids = skill_services.filter_skills_by_mastery(
            self.USER_ID, self.SKILL_IDS)

        self.assertEqual(len(arranged_filtered_skill_ids),
                         feconf.MAX_NUMBER_OF_SKILL_IDS)

        # Assert that all the returned skill_ids are a part of the first
        # feconf.MAX_NUMBER_OF_SKILL_IDS sorted skill_ids.
        for i in python_utils.RANGE(feconf.MAX_NUMBER_OF_SKILL_IDS):
            self.assertIn(degrees_of_masteries[arranged_filtered_skill_ids[i]],
                          masteries[:feconf.MAX_NUMBER_OF_SKILL_IDS])

        # Testing the arrangement.
        excluded_skill_ids = list(
            set(self.SKILL_IDS) - set(arranged_filtered_skill_ids))
        for skill_id in excluded_skill_ids:
            self.SKILL_IDS.remove(skill_id)
        self.assertEqual(arranged_filtered_skill_ids, self.SKILL_IDS)
예제 #9
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)
예제 #10
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({})
예제 #11
0
    def get(self, topic_name):
        """Handles GET requests."""

        topic = topic_fetchers.get_topic_by_name(topic_name)
        canonical_story_ids = topic.get_canonical_story_ids(
            include_only_published=True)
        additional_story_ids = topic.get_additional_story_ids(
            include_only_published=True)
        canonical_story_summaries = [
            story_fetchers.get_story_summary_by_id(
                canonical_story_id) for canonical_story_id
            in canonical_story_ids]

        additional_story_summaries = [
            story_fetchers.get_story_summary_by_id(
                additional_story_id) for additional_story_id
            in additional_story_ids]

        canonical_story_dicts = []
        for story_summary in canonical_story_summaries:
            completed_node_titles = [
                completed_node.title for completed_node in
                story_fetchers.get_completed_nodes_in_story(
                    self.user_id, story_summary.id)]
            story_summary_dict = story_summary.to_human_readable_dict()
            story_summary_dict['story_is_published'] = True
            story_summary_dict['completed_node_titles'] = completed_node_titles
            canonical_story_dicts.append(story_summary_dict)

        additional_story_dicts = []
        for story_summary in additional_story_summaries:
            completed_node_titles = [
                completed_node.title for completed_node in
                story_fetchers.get_completed_nodes_in_story(
                    self.user_id, story_summary.id)]
            story_summary_dict = story_summary.to_human_readable_dict()
            story_summary_dict['story_is_published'] = True
            story_summary_dict['completed_node_titles'] = completed_node_titles
            additional_story_dicts.append(story_summary_dict)

        uncategorized_skill_ids = topic.get_all_uncategorized_skill_ids()
        subtopics = topic.get_all_subtopics()

        all_skill_ids = topic.get_all_skill_ids()
        skill_descriptions, deleted_skill_ids = (
            skill_services.get_descriptions_of_skills(
                all_skill_ids))

        if deleted_skill_ids:
            deleted_skills_string = ', '.join(deleted_skill_ids)
            logging.error(
                'The deleted skills: %s are still present in topic with id %s'
                % (deleted_skills_string, topic.id)
            )
            if feconf.CAN_SEND_EMAILS:
                email_manager.send_mail_to_admin(
                    'Deleted skills present in topic',
                    'The deleted skills: %s are still present in topic with '
                    'id %s' % (deleted_skills_string, topic.id))

        if self.user_id:
            degrees_of_mastery = skill_services.get_multi_user_skill_mastery(
                self.user_id, all_skill_ids)
        else:
            degrees_of_mastery = {}
            for skill_id in all_skill_ids:
                degrees_of_mastery[skill_id] = None

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'topic_description': topic.description,
            'canonical_story_dicts': canonical_story_dicts,
            'additional_story_dicts': additional_story_dicts,
            'uncategorized_skill_ids': uncategorized_skill_ids,
            'subtopics': subtopics,
            'degrees_of_mastery': degrees_of_mastery,
            'skill_descriptions': skill_descriptions,
            'practice_tab_is_displayed': topic.practice_tab_is_displayed
        })
        self.render_json(self.values)
예제 #12
0
    def get(self, topic_name):
        """Handles GET requests."""

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        topic = topic_fetchers.get_topic_by_name(topic_name)
        canonical_story_ids = topic.get_canonical_story_ids(
            include_only_published=True)
        additional_story_ids = topic.get_additional_story_ids(
            include_only_published=True)
        canonical_story_summaries = [
            story_fetchers.get_story_summary_by_id(canonical_story_id)
            for canonical_story_id in canonical_story_ids
        ]

        additional_story_summaries = [
            story_fetchers.get_story_summary_by_id(additional_story_id)
            for additional_story_id in additional_story_ids
        ]

        canonical_story_dicts = [
            summary.to_human_readable_dict()
            for summary in canonical_story_summaries
        ]

        additional_story_dicts = [
            summary.to_human_readable_dict()
            for summary in additional_story_summaries
        ]

        uncategorized_skill_ids = topic.get_all_uncategorized_skill_ids()
        subtopics = topic.get_all_subtopics()

        assigned_skill_ids = topic.get_all_skill_ids()
        skill_descriptions, deleted_skill_ids = (
            skill_services.get_descriptions_of_skills(assigned_skill_ids))

        if deleted_skill_ids:
            deleted_skills_string = ', '.join(deleted_skill_ids)
            logging.error(
                'The deleted skills: %s are still present in topic with id %s'
                % (deleted_skills_string, topic.id))
            if feconf.CAN_SEND_EMAILS:
                email_manager.send_mail_to_admin(
                    'Deleted skills present in topic',
                    'The deleted skills: %s are still present in topic with '
                    'id %s' % (deleted_skills_string, topic.id))

        if self.user_id:
            degrees_of_mastery = skill_services.get_multi_user_skill_mastery(
                self.user_id, assigned_skill_ids)
        else:
            degrees_of_mastery = {}
            for skill_id in assigned_skill_ids:
                degrees_of_mastery[skill_id] = None

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'canonical_story_dicts': canonical_story_dicts,
            'additional_story_dicts': additional_story_dicts,
            'uncategorized_skill_ids': uncategorized_skill_ids,
            'subtopics': subtopics,
            'degrees_of_mastery': degrees_of_mastery,
            'skill_descriptions': skill_descriptions
        })
        self.render_json(self.values)