Пример #1
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()
Пример #2
0
    def test_get_multi_users_subtopic_pages_progress(self) -> None:
        degree_of_mastery = 0.5
        student_id_1 = 'student_1'
        student_id_2 = 'student_2'

        # Add some subtopic progress for the student.
        skill_services.create_user_skill_mastery(  # type: ignore[no-untyped-call]
            student_id_1, 'skill_id_1', degree_of_mastery)

        subtopic_page_id = '{}:{}'.format(self.TOPIC_ID_1, 1)
        progress = (
            subtopic_page_services.get_multi_users_subtopic_pages_progress(
                [student_id_1, student_id_2], [subtopic_page_id]))

        student_1_progress = progress[student_id_1]
        student_2_progress = progress[student_id_2]

        self.assertEqual(len(student_1_progress), 1)
        self.assertEqual(len(student_2_progress), 1)
        self.assertEqual(student_1_progress[0]['subtopic_id'], 1)
        self.assertEqual(student_1_progress[0]['subtopic_title'],
                         'Naming Numbers')
        self.assertEqual(student_1_progress[0]['parent_topic_id'],
                         self.TOPIC_ID_1)
        self.assertEqual(student_1_progress[0]['parent_topic_name'],
                         'Place Values')
        self.assertEqual(student_1_progress[0]['subtopic_mastery'],
                         degree_of_mastery)
        self.assertIsNone(student_2_progress[0]['subtopic_mastery'])
Пример #3
0
 def setUp(self):
     super(SkillMasteryServicesUnitTests, self).setUp()
     self.SKILL_ID_1 = skill_services.get_new_skill_id()
     self.SKILL_ID_2 = skill_services.get_new_skill_id()
     self.SKILL_IDS = [self.SKILL_ID_1, self.SKILL_ID_2]
     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)
Пример #4
0
    def setUp(self):
        """Completes the sign-up process for the various users."""
        super(BaseTopicViewerControllerTests, self).setUp()
        self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME)
        self.user_id = self.get_user_id_from_email(self.NEW_USER_EMAIL)
        self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME)
        self.admin_id = self.get_user_id_from_email(self.ADMIN_EMAIL)
        self.set_admins([self.ADMIN_USERNAME])
        self.admin = user_services.UserActionsInfo(self.admin_id)

        self.topic_id = 'topic'
        self.story_id = 'story'
        self.topic_id_1 = 'topic1'
        self.skill_id_1 = skill_services.get_new_skill_id()
        self.skill_id_2 = skill_services.get_new_skill_id()

        self.story = story_domain.Story.create_default_story(
            self.story_id, 'story_title', self.topic_id_1)
        self.story.description = 'story_description'

        self.topic = topic_domain.Topic.create_default_topic(
            self.topic_id, 'public_topic_name', 'abbrev')
        self.topic.uncategorized_skill_ids.append(self.skill_id_1)
        self.topic.subtopics.append(
            topic_domain.Subtopic(1, 'subtopic_name', [self.skill_id_2]))
        self.topic.next_subtopic_id = 2
        self.topic.thumbnail_filename = 'Image.png'
        self.topic.canonical_story_references.append(
            topic_domain.StoryReference.create_default_story_reference(
                self.story_id))
        topic_services.save_new_topic(self.admin_id, self.topic)
        story_services.save_new_story(self.admin_id, self.story)

        self.topic = topic_domain.Topic.create_default_topic(
            self.topic_id_1, 'private_topic_name', 'abbrev')
        self.topic.thumbnail_filename = 'Image.png'
        topic_services.save_new_topic(self.admin_id, self.topic)

        topic_services.publish_topic(self.topic_id, self.admin_id)
        topic_services.publish_story(self.topic_id, self.story_id,
                                     self.admin_id)

        self.save_new_skill(self.skill_id_1,
                            self.user_id,
                            description='Skill Description 1')
        self.save_new_skill(self.skill_id_2,
                            self.user_id,
                            description='Skill Description 2')
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_1,
                                                 0.3)
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_2,
                                                 0.5)
Пример #5
0
    def test_get_with_skill_without_skill_mastery(self):
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_1,
                                                 self.degree_of_mastery_1)

        skill_ids = [self.skill_id_1, self.skill_id_2]

        self.login(self.NEW_USER_EMAIL)
        response_json = self.get_json(
            '%s' % feconf.SKILL_MASTERY_DATA_URL,
            params={'comma_separated_skill_ids': ','.join(skill_ids)})
        degrees_of_mastery = {
            self.skill_id_1: self.degree_of_mastery_1,
            self.skill_id_2: None
        }
        self.assertEqual(response_json['degrees_of_mastery'],
                         degrees_of_mastery)

        self.logout()
Пример #6
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)
Пример #7
0
    def test_get_with_valid_skill_ids_list(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)

        skill_ids = [self.skill_id_1, self.skill_id_2]

        self.login(self.NEW_USER_EMAIL)
        response_json = self.get_json(
            '%s' % feconf.SKILL_MASTERY_DATA_URL,
            params={'selected_skill_ids': json.dumps(skill_ids)})
        degrees_of_mastery = {
            self.skill_id_1: self.degree_of_mastery_1,
            self.skill_id_2: self.degree_of_mastery_2
        }
        self.assertEqual(response_json['degrees_of_mastery'],
                         degrees_of_mastery)

        self.logout()
Пример #8
0
    def setUp(self):
        """Completes the sign-up process for the various users."""
        super(BaseTopicViewerControllerTests, self).setUp()
        self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME)
        self.user_id = self.get_user_id_from_email(self.NEW_USER_EMAIL)
        self.signup(self.CURRICULUM_ADMIN_EMAIL, self.CURRICULUM_ADMIN_USERNAME)
        self.admin_id = self.get_user_id_from_email(self.CURRICULUM_ADMIN_EMAIL)
        self.set_curriculum_admins([self.CURRICULUM_ADMIN_USERNAME])
        self.admin = user_services.get_user_actions_info(self.admin_id)

        self.topic_id = 'topic'
        self.story_id_1 = 'story_id_1'
        self.story_id_2 = 'story_id_2'
        self.topic_id_1 = 'topic1'
        self.topic_id_2 = 'topic2'
        self.skill_id_1 = skill_services.get_new_skill_id()
        self.skill_id_2 = skill_services.get_new_skill_id()

        self.story_1 = story_domain.Story.create_default_story(
            self.story_id_1, 'story_title', 'description', self.topic_id_1,
            'story-frag-one')
        self.story_1.description = 'story_description'
        self.story_1.node_titles = []

        self.story_2 = story_domain.Story.create_default_story(
            self.story_id_2, 'story_title', 'description', self.topic_id_2,
            'story-frag-two')
        self.story_2.description = 'story_description'
        self.story_2.node_titles = []

        self.topic = topic_domain.Topic.create_default_topic(
            self.topic_id, 'public_topic_name', 'public', 'description')
        self.topic.uncategorized_skill_ids.append(self.skill_id_1)
        self.topic.subtopics.append(topic_domain.Subtopic(
            1, 'subtopic_name', [self.skill_id_2], 'image.svg',
            constants.ALLOWED_THUMBNAIL_BG_COLORS['subtopic'][0], 21131,
            'subtopic-name'))
        self.topic.next_subtopic_id = 2
        self.topic.thumbnail_filename = 'Image.svg'
        self.topic.thumbnail_bg_color = (
            constants.ALLOWED_THUMBNAIL_BG_COLORS['topic'][0])
        self.topic.canonical_story_references.append(
            topic_domain.StoryReference.create_default_story_reference(
                self.story_id_1))
        self.topic.additional_story_references.append(
            topic_domain.StoryReference.create_default_story_reference(
                self.story_id_2))
        self.topic.meta_tag_content = 'topic meta content'
        self.topic.page_title_fragment_for_web = 'topic page title'

        topic_services.save_new_topic(self.admin_id, self.topic)
        story_services.save_new_story(self.admin_id, self.story_1)
        story_services.save_new_story(self.admin_id, self.story_2)

        self.topic = topic_domain.Topic.create_default_topic(
            self.topic_id_1, 'private_topic_name',
            'private_topic_name', 'description')
        self.topic.thumbnail_filename = 'Image.svg'
        self.topic.thumbnail_bg_color = (
            constants.ALLOWED_THUMBNAIL_BG_COLORS['topic'][0])
        self.topic.url_fragment = 'private'
        topic_services.save_new_topic(self.admin_id, self.topic)

        topic_services.publish_topic(self.topic_id, self.admin_id)
        topic_services.publish_story(
            self.topic_id, self.story_id_1, self.admin_id)
        topic_services.publish_story(
            self.topic_id, self.story_id_2, self.admin_id)

        self.save_new_skill(
            self.skill_id_1, self.user_id, description='Skill Description 1')
        self.save_new_skill(
            self.skill_id_2, self.user_id, description='Skill Description 2')
        skill_services.create_user_skill_mastery(
            self.user_id, self.skill_id_1, 0.3)
        skill_services.create_user_skill_mastery(
            self.user_id, self.skill_id_2, 0.5)
Пример #9
0
    def test_get_with_valid_topic_ids(self):
        topic_id_1 = topic_fetchers.get_new_topic_id()
        topic_id_2 = topic_fetchers.get_new_topic_id()

        self.save_new_topic(topic_id_1,
                            self.user_id,
                            name='Name 1',
                            abbreviated_name='topic-one',
                            url_fragment='topic-one',
                            description='Description 1',
                            canonical_story_ids=[],
                            additional_story_ids=[],
                            uncategorized_skill_ids=[
                                self.skill_id_1, self.skill_id_2,
                                self.skill_id_3
                            ],
                            subtopics=[],
                            next_subtopic_id=1)
        self.save_new_topic(topic_id_2,
                            self.user_id,
                            name='Name 2',
                            abbreviated_name='topic-two',
                            url_fragment='topic-two',
                            description='Description 2',
                            canonical_story_ids=[],
                            additional_story_ids=[],
                            uncategorized_skill_ids=[
                                self.skill_id_3, self.skill_id_4,
                                self.skill_id_5
                            ],
                            subtopics=[],
                            next_subtopic_id=1)

        # Update Topic 1.
        changelist = [
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                'title': 'Title 1',
                'subtopic_id': 1
            }),
            topic_domain.TopicChange({
                'cmd':
                topic_domain.CMD_UPDATE_SUBTOPIC_PROPERTY,
                'property_name': (topic_domain.SUBTOPIC_PROPERTY_URL_FRAGMENT),
                'old_value':
                '',
                'new_value':
                'subtopic-one-one',
                'subtopic_id':
                1
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC,
                'old_subtopic_id': None,
                'new_subtopic_id': 1,
                'skill_id': self.skill_id_1
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC,
                'old_subtopic_id': None,
                'new_subtopic_id': 1,
                'skill_id': self.skill_id_2
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                'title': 'Title 2',
                'subtopic_id': 2
            }),
            topic_domain.TopicChange({
                'cmd':
                topic_domain.CMD_UPDATE_SUBTOPIC_PROPERTY,
                'property_name': (topic_domain.SUBTOPIC_PROPERTY_URL_FRAGMENT),
                'old_value':
                '',
                'new_value':
                'subtopic-one-two',
                'subtopic_id':
                2
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC,
                'old_subtopic_id': None,
                'new_subtopic_id': 2,
                'skill_id': self.skill_id_3
            })
        ]
        topic_services.update_topic_and_subtopic_pages(self.user_id,
                                                       topic_id_1, changelist,
                                                       'Added subtopics.')

        # Update Topic 2.
        changelist = [
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                'title': 'Title 1',
                'subtopic_id': 1
            }),
            topic_domain.TopicChange({
                'cmd':
                topic_domain.CMD_UPDATE_SUBTOPIC_PROPERTY,
                'property_name': (topic_domain.SUBTOPIC_PROPERTY_URL_FRAGMENT),
                'old_value':
                '',
                'new_value':
                'subtopic-two-one',
                'subtopic_id':
                1
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC,
                'old_subtopic_id': None,
                'new_subtopic_id': 1,
                'skill_id': self.skill_id_3
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC,
                'old_subtopic_id': None,
                'new_subtopic_id': 1,
                'skill_id': self.skill_id_4
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                'title': 'Title 2',
                'subtopic_id': 2
            }),
            topic_domain.TopicChange({
                'cmd':
                topic_domain.CMD_UPDATE_SUBTOPIC_PROPERTY,
                'property_name': (topic_domain.SUBTOPIC_PROPERTY_URL_FRAGMENT),
                'old_value':
                '',
                'new_value':
                'subtopic-two-two',
                'subtopic_id':
                2
            }),
            topic_domain.TopicChange({
                'cmd': topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC,
                'old_subtopic_id': None,
                'new_subtopic_id': 2,
                'skill_id': self.skill_id_5
            })
        ]
        topic_services.update_topic_and_subtopic_pages(self.user_id,
                                                       topic_id_2, changelist,
                                                       'Added subtopics.')
        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)
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_5,
                                                 self.degree_of_mastery_5)
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_6,
                                                 self.degree_of_mastery_6)

        self.login(self.NEW_USER_EMAIL)

        # First case: One subtopic mastery doesn't exist.
        response_json = self.get_json('%s' % feconf.SUBTOPIC_MASTERY_DATA_URL,
                                      params={
                                          'comma_separated_topic_ids':
                                          ','.join([topic_id_1, topic_id_2])
                                      })
        degrees_of_mastery_1 = {
            u'1':
            python_utils.divide(
                self.degree_of_mastery_1 + self.degree_of_mastery_2, 2)
        }
        degrees_of_mastery_2 = {u'2': self.degree_of_mastery_5}
        self.assertEqual(response_json['subtopic_mastery_dict'], {
            topic_id_1: degrees_of_mastery_1,
            topic_id_2: degrees_of_mastery_2
        })

        # Second case: One skill mastery doesn't exist.
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_3,
                                                 self.degree_of_mastery_3)
        response_json = self.get_json('%s' % feconf.SUBTOPIC_MASTERY_DATA_URL,
                                      params={
                                          'comma_separated_topic_ids':
                                          ','.join([topic_id_1, topic_id_2])
                                      })
        degrees_of_mastery_1 = {
            u'1':
            python_utils.divide(
                self.degree_of_mastery_1 + self.degree_of_mastery_2, 2),
            u'2':
            self.degree_of_mastery_3
        }
        degrees_of_mastery_2 = {
            u'1': self.degree_of_mastery_3,
            u'2': self.degree_of_mastery_5
        }
        self.assertEqual(response_json['subtopic_mastery_dict'], {
            topic_id_1: degrees_of_mastery_1,
            topic_id_2: degrees_of_mastery_2
        })

        # Third case: All masteries exist.
        skill_services.create_user_skill_mastery(self.user_id, self.skill_id_4,
                                                 self.degree_of_mastery_4)
        response_json = self.get_json('%s' % feconf.SUBTOPIC_MASTERY_DATA_URL,
                                      params={
                                          'comma_separated_topic_ids':
                                          ','.join([topic_id_1, topic_id_2])
                                      })
        degrees_of_mastery_1 = {
            u'1':
            python_utils.divide(
                self.degree_of_mastery_1 + self.degree_of_mastery_2, 2),
            u'2':
            self.degree_of_mastery_3
        }
        degrees_of_mastery_2 = {
            u'1':
            python_utils.divide(
                self.degree_of_mastery_3 + self.degree_of_mastery_4, 2),
            u'2':
            self.degree_of_mastery_5
        }
        self.assertEqual(response_json['subtopic_mastery_dict'], {
            topic_id_1: degrees_of_mastery_1,
            topic_id_2: degrees_of_mastery_2
        })
        self.logout()
Пример #10
0
    def setUp(self):
        super(LearnerGroupStudentProgressHandlerTests, self).setUp()
        self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME)
        self.signup(self.CURRICULUM_ADMIN_EMAIL,
                    self.CURRICULUM_ADMIN_USERNAME)
        self.signup(self.STUDENT_1_EMAIL, self.STUDENT_1_USERNAME)
        self.signup(self.STUDENT_2_EMAIL, self.STUDENT_2_USERNAME)

        self.STUDENT_ID_1 = self.get_user_id_from_email(self.STUDENT_1_EMAIL)
        self.STUDENT_ID_2 = self.get_user_id_from_email(self.STUDENT_2_EMAIL)
        self.facilitator_id = self.get_user_id_from_email(self.NEW_USER_EMAIL)
        self.admin_id = self.get_user_id_from_email(
            self.CURRICULUM_ADMIN_EMAIL)
        self.set_curriculum_admins([self.CURRICULUM_ADMIN_USERNAME])
        self.admin = user_services.get_user_actions_info(self.admin_id)

        self.LEARNER_GROUP_ID = (
            learner_group_fetchers.get_new_learner_group_id())

        learner_group_services.create_learner_group(
            self.LEARNER_GROUP_ID, 'Learner Group Name', 'Description',
            [self.facilitator_id], [self.STUDENT_ID_1, self.STUDENT_ID_2],
            [self.SUBTOPIC_PAGE_ID_1], [self.STORY_ID_1])

        # Set up topics, subtopics and stories for learner group syllabus.
        self.save_new_valid_exploration(self.EXP_ID_0,
                                        self.admin_id,
                                        title='Title 1',
                                        end_state_name='End',
                                        correctness_feedback_enabled=True)
        self.save_new_valid_exploration(self.EXP_ID_1,
                                        self.admin_id,
                                        title='Title 2',
                                        end_state_name='End',
                                        correctness_feedback_enabled=True)
        self.save_new_valid_exploration(self.EXP_ID_7,
                                        self.admin_id,
                                        title='Title 3',
                                        end_state_name='End',
                                        correctness_feedback_enabled=True)
        self.publish_exploration(self.admin_id, self.EXP_ID_0)
        self.publish_exploration(self.admin_id, self.EXP_ID_1)
        self.publish_exploration(self.admin_id, self.EXP_ID_7)

        story = story_domain.Story.create_default_story(
            self.STORY_ID_1, 'Title', 'Description', self.TOPIC_ID_1,
            self.STORY_URL_FRAGMENT)
        story.meta_tag_content = 'story meta content'

        exp_summary_dicts = (
            summary_services.get_displayable_exp_summary_dicts_matching_ids(
                [self.EXP_ID_0, self.EXP_ID_1, self.EXP_ID_7],
                user=self.admin))
        self.node_1 = {
            'id':
            self.NODE_ID_1,
            'title':
            'Title 1',
            'description':
            'Description 1',
            'thumbnail_filename':
            'image_1.svg',
            'thumbnail_bg_color':
            constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0],
            'thumbnail_size_in_bytes':
            21131,
            'destination_node_ids': ['node_3'],
            'acquired_skill_ids': [],
            'prerequisite_skill_ids': [],
            'outline':
            '',
            'outline_is_finalized':
            False,
            'exploration_id':
            self.EXP_ID_1,
            'exp_summary_dict':
            exp_summary_dicts[1],
            'completed':
            False
        }
        self.node_2 = {
            'id':
            self.NODE_ID_2,
            'title':
            'Title 2',
            'description':
            'Description 2',
            'thumbnail_filename':
            'image_2.svg',
            'thumbnail_bg_color':
            constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0],
            'thumbnail_size_in_bytes':
            21131,
            'destination_node_ids': ['node_1'],
            'acquired_skill_ids': [],
            'prerequisite_skill_ids': [],
            'outline':
            '',
            'outline_is_finalized':
            False,
            'exploration_id':
            self.EXP_ID_0,
            'exp_summary_dict':
            exp_summary_dicts[0],
            'completed':
            True
        }
        self.node_3 = {
            'id':
            self.NODE_ID_3,
            'title':
            'Title 3',
            'description':
            'Description 3',
            'thumbnail_filename':
            'image_3.svg',
            'thumbnail_bg_color':
            constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0],
            'thumbnail_size_in_bytes':
            21131,
            'destination_node_ids': [],
            'acquired_skill_ids': [],
            'prerequisite_skill_ids': [],
            'outline':
            '',
            'outline_is_finalized':
            False,
            'exploration_id':
            self.EXP_ID_7,
            'exp_summary_dict':
            exp_summary_dicts[2],
            'completed':
            False
        }
        story.story_contents.nodes = [
            story_domain.StoryNode.from_dict(self.node_1),
            story_domain.StoryNode.from_dict(self.node_2),
            story_domain.StoryNode.from_dict(self.node_3)
        ]
        self.nodes = story.story_contents.nodes
        story.story_contents.initial_node_id = 'node_2'
        story.story_contents.next_node_id = 'node_4'
        story_services.save_new_story(self.admin_id, story)
        self.subtopic_1 = topic_domain.Subtopic.create_default_subtopic(
            1, 'Subtopic Title 1', 'sub-one-frag')
        self.subtopic_2 = topic_domain.Subtopic.create_default_subtopic(
            2, 'Subtopic Title 2', 'sub-two-frag')
        self.SKILL_ID_1 = skill_services.get_new_skill_id()
        self.SKILL_ID_2 = skill_services.get_new_skill_id()
        self.subtopic_1.skill_ids = [self.SKILL_ID_1]
        self.subtopic_2.skill_ids = [self.SKILL_ID_2]
        self.save_new_topic(self.TOPIC_ID_1,
                            'user',
                            name='Topic',
                            description='A new topic',
                            canonical_story_ids=[story.id],
                            additional_story_ids=[],
                            uncategorized_skill_ids=[],
                            subtopics=[self.subtopic_1, self.subtopic_2],
                            next_subtopic_id=3)
        topic_services.publish_topic(self.TOPIC_ID_1, self.admin_id)
        topic_services.publish_story(self.TOPIC_ID_1, self.STORY_ID_1,
                                     self.admin_id)

        # Add the invited students to the learner group.
        learner_group_services.add_student_to_learner_group(
            self.LEARNER_GROUP_ID, self.STUDENT_ID_1, True)

        learner_group_services.add_student_to_learner_group(
            self.LEARNER_GROUP_ID, self.STUDENT_ID_2, False)

        # Add some progress for the students.
        story_services.record_completed_node_in_story_context(
            self.STUDENT_ID_1, self.STORY_ID_1, self.NODE_ID_1)
        story_services.record_completed_node_in_story_context(
            self.STUDENT_ID_1, self.STORY_ID_1, self.NODE_ID_2)
        story_services.record_completed_node_in_story_context(
            self.STUDENT_ID_2, self.STORY_ID_1, self.NODE_ID_3)

        self.SKILL_IDS = [self.SKILL_ID_1, self.SKILL_ID_2]
        skill_services.create_user_skill_mastery(self.STUDENT_ID_1,
                                                 self.SKILL_ID_1,
                                                 self.DEGREE_OF_MASTERY_1)
        skill_services.create_user_skill_mastery(self.STUDENT_ID_2,
                                                 self.SKILL_ID_2,
                                                 self.DEGREE_OF_MASTERY_2)