Esempio n. 1
0
    def _record_node_completion(self, story_id, node_id, completed_node_ids,
                                ordered_nodes):
        """Records node completion."""
        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)

        next_exp_ids = []
        next_node_id = None
        if node_id not 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
        return (next_exp_ids, next_node_id, completed_node_ids)
Esempio n. 2
0
    def test_get_multi_users_progress_in_stories(self) -> None:
        all_users_stories_progress = (
            story_fetchers.get_multi_users_progress_in_stories(
                [self.USER_ID], [self.story_id, 'invalid_story_id']))
        all_stories = story_fetchers.get_stories_by_ids(
            [self.story_id, 'invalid_story_id'])

        # Should return None for invalid story ID.
        self.assertIsNone(all_stories[1])

        user_stories_progress = all_users_stories_progress[self.USER_ID]

        self.assertEqual(len(user_stories_progress), 1)
        assert all_stories[0] is not None
        self.assertEqual(user_stories_progress[0]['id'], self.story_id)
        self.assertEqual(user_stories_progress[0]['completed_node_titles'], [])
        self.assertEqual(len(user_stories_progress[0]['all_node_dicts']),
                         len(all_stories[0].story_contents.nodes))
        self.assertEqual(user_stories_progress[0]['topic_name'], 'Topic')

        story_services.record_completed_node_in_story_context(  # type: ignore[no-untyped-call]
            self.USER_ID, self.story_id, self.NODE_ID_1)

        all_users_stories_progress = (
            story_fetchers.get_multi_users_progress_in_stories(
                [self.USER_ID], [self.story_id, 'invalid_story_id']))
        user_stories_progress = all_users_stories_progress[self.USER_ID]

        self.assertEqual(len(user_stories_progress), 1)
        # Ruling out the possibility of None for mypy type checking.
        assert user_stories_progress[0] is not None
        self.assertEqual(user_stories_progress[0]['id'], self.story_id)
        self.assertEqual(user_stories_progress[0]['completed_node_titles'],
                         ['Title 1'])
        self.assertEqual(user_stories_progress[0]['topic_name'], 'Topic')
Esempio n. 3
0
    def test_get_fails_when_acquired_skills_dont_exist(self):
        story_id = 'story_id_3'
        node_id = 'node_1'
        node = {
            'id': node_id,
            'title': 'Title 1',
            'destination_node_ids': [],
            'acquired_skill_ids': ['skill_id_3'],
            'prerequisite_skill_ids': [],
            'outline': '',
            'outline_is_finalized': False,
            'exploration_id': self.exp_id
        }
        story = story_domain.Story.create_default_story(
            story_id, 'Public Story Title', self.topic_id)
        story.story_contents.nodes = [
            story_domain.StoryNode.from_dict(node)
        ]
        story.story_contents.initial_node_id = node_id
        story.story_contents.next_node_id = self.node_id_2
        story_services.save_new_story(self.admin_id, story)

        story_services.publish_story(story_id, self.admin_id)

        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
            story_services.record_completed_node_in_story_context(
                self.viewer_id, story_id, node_id)
            self.get_json(
                '%s/%s' % (
                    feconf.REVIEW_TEST_DATA_URL_PREFIX, story_id),
                expected_status_int=404)
Esempio n. 4
0
    def test_mark_story_and_topic_as_completed_and_learnt(self):
        csrf_token = self.get_new_csrf_token()
        learner_progress_services.validate_and_add_topic_to_learn_goal(
            self.viewer_id, self.TOPIC_ID)
        self.assertEqual(
            len(
                learner_goals_services.get_all_topic_ids_to_learn(
                    self.viewer_id)), 1)
        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.STORY_ID, self.NODE_ID_2)
        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.STORY_ID, self.NODE_ID_1)
        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True):
            self.post_json('%s/staging/topic/%s/%s' %
                           (feconf.STORY_PROGRESS_URL_PREFIX,
                            self.STORY_URL_FRAGMENT, self.NODE_ID_3), {},
                           csrf_token=csrf_token)

        self.assertEqual(
            len(
                learner_progress_services.get_all_learnt_topic_ids(
                    self.viewer_id)), 1)
        self.assertEqual(
            len(
                learner_goals_services.get_all_topic_ids_to_learn(
                    self.viewer_id)), 0)
        self.assertEqual(
            len(
                learner_progress_services.get_all_completed_story_ids(
                    self.viewer_id)), 1)
Esempio n. 5
0
 def test_get_latest_completed_node_ids(self) -> None:
     self.assertEqual(
         story_fetchers.get_latest_completed_node_ids(
             self.USER_ID, self.story_id), [])
     story_services.record_completed_node_in_story_context(  # type: ignore[no-untyped-call]
         self.USER_ID, self.story_id, self.NODE_ID_1)
     self.assertEqual(
         story_fetchers.get_latest_completed_node_ids(
             self.USER_ID, self.story_id), [self.NODE_ID_1])
Esempio n. 6
0
 def test_get_latest_completed_node_ids(self):
     self.assertEqual(
         story_fetchers.get_latest_completed_node_ids(
             self.USER_ID, self.STORY_ID), [])
     story_services.record_completed_node_in_story_context(
         self.USER_ID, self.STORY_ID, self.NODE_ID_1)
     self.assertEqual(
         story_fetchers.get_latest_completed_node_ids(
             self.USER_ID, self.STORY_ID), [self.NODE_ID_1])
Esempio n. 7
0
 def test_any_user_can_access_review_tests_data(self):
     with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
         story_services.record_completed_node_in_story_context(
             self.viewer_id, self.story_id_1, self.node_id)
         json_response = self.get_json(
             '%s/%s' %
             (feconf.REVIEW_TEST_DATA_URL_PREFIX, self.story_id_1))
         self.assertEqual(len(json_response['skill_ids']), 2)
         self.assertEqual(json_response['skill_ids'][0], 'skill_id_1')
         self.assertEqual(json_response['skill_ids'][1], 'skill_id_2')
Esempio n. 8
0
 def test_get_completed_node_id(self):
     self.assertEqual(
         story_fetchers.get_completed_node_ids('randomID', 'someID'), [])
     story_services.record_completed_node_in_story_context(
         self.USER_ID, self.STORY_ID, self.NODE_ID_1)
     story_services.record_completed_node_in_story_context(
         self.USER_ID, self.STORY_ID, self.NODE_ID_2)
     self.assertEqual(
         story_fetchers.get_completed_node_ids(self.USER_ID, self.STORY_ID),
         [self.NODE_ID_1, self.NODE_ID_2])
 def test_any_user_can_access_review_tests_data(self):
     story_services.record_completed_node_in_story_context(
         self.viewer_id, self.story_id_1, self.node_id)
     json_response = self.get_json(
         '%s/staging/topic/%s' %
         (feconf.REVIEW_TEST_DATA_URL_PREFIX, self.story_url_fragment_1))
     self.assertEqual(len(json_response['skill_descriptions']), 2)
     self.assertEqual(json_response['skill_descriptions']['skill_id_1'],
                      'Skill 1')
     self.assertEqual(json_response['skill_descriptions']['skill_id_2'],
                      'Skill 2')
Esempio n. 10
0
 def test_get_completed_nodes_in_story(self):
     story = story_fetchers.get_story_by_id(self.STORY_ID)
     story_services.record_completed_node_in_story_context(
         self.USER_ID, self.STORY_ID, self.NODE_ID_1)
     story_services.record_completed_node_in_story_context(
         self.USER_ID, self.STORY_ID, self.NODE_ID_2)
     for ind, completed_node in enumerate(
             story_fetchers.get_completed_nodes_in_story(
                 self.USER_ID, self.STORY_ID)):
         self.assertEqual(completed_node.to_dict(),
                          story.story_contents.nodes[ind].to_dict())
Esempio n. 11
0
 def test_get_completed_nodes_in_story(self) -> None:
     story = story_fetchers.get_story_by_id(self.story_id)
     story_services.record_completed_node_in_story_context(  # type: ignore[no-untyped-call]
         self.USER_ID, self.story_id, self.NODE_ID_1)
     story_services.record_completed_node_in_story_context(  # type: ignore[no-untyped-call]
         self.USER_ID, self.story_id, self.NODE_ID_2)
     for ind, completed_node in enumerate(
             story_fetchers.get_completed_nodes_in_story(
                 self.USER_ID, self.story_id)):
         self.assertEqual(completed_node.to_dict(),
                          story.story_contents.nodes[ind].to_dict())
Esempio n. 12
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 ((len(exp_summaries) != 0 and len(completed_nodes) %
             constants.NUM_EXPLORATIONS_PER_REVIEW_TEST == 0)
                or (len(completed_nodes) == len(ordered_nodes))):
            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
        })
Esempio n. 13
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_services.record_completed_node_in_story_context(
            self.user_id, story_id, node_id)
        return self.render_json({})
Esempio n. 14
0
 def test_post_returns_empty_list_when_user_completes_story(self):
     csrf_token = self.get_new_csrf_token()
     story_services.record_completed_node_in_story_context(
         self.viewer_id, self.STORY_ID, self.NODE_ID_2)
     story_services.record_completed_node_in_story_context(
         self.viewer_id, self.STORY_ID, self.NODE_ID_1)
     with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True):
         json_response = self.post_json(
             '%s/%s/%s' % (feconf.STORY_PROGRESS_URL_PREFIX, self.STORY_ID,
                           self.NODE_ID_3), {},
             csrf_token=csrf_token)
     self.assertEqual(len(json_response['summaries']), 0)
     self.assertIsNone(json_response['next_node_id'])
     self.assertTrue(json_response['ready_for_review_test'])
Esempio n. 15
0
 def test_redirect_for_returning_user_with_completed_nodes(self):
     self.NEW_USER_EMAIL = '*****@*****.**'
     self.NEW_USER_USERNAME = '******'
     self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME)
     self.login(self.NEW_USER_EMAIL)
     story_services.record_completed_node_in_story_context(
         self.viewer_id, self.STORY_ID, self.NODE_ID_2)
     story_services.record_completed_node_in_story_context(
         self.viewer_id, self.STORY_ID, self.NODE_ID_1)
     with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True):
         response = self.get_html_response(
             '%s/staging/topic/%s/%s' % (
                 feconf.STORY_PROGRESS_URL_PREFIX, self.STORY_URL_FRAGMENT,
                 self.NODE_ID_1), expected_status_int=302)
         self.assertEqual(
             'http://localhost/learn/staging/topic/story/title-one',
             response.headers['location'])
Esempio n. 16
0
    def test_get_fails_when_acquired_skills_dont_exist(self):
        node_id = 'node_1'
        node = {
            'id':
            node_id,
            'title':
            'Title 1',
            'description':
            'Description 1',
            'thumbnail_filename':
            'image.svg',
            'thumbnail_bg_color':
            constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0],
            'thumbnail_size_in_bytes':
            21131,
            'destination_node_ids': [],
            'acquired_skill_ids': ['skill_id_3'],
            'prerequisite_skill_ids': [],
            'outline':
            '',
            'outline_is_finalized':
            False,
            'exploration_id':
            self.exp_id
        }
        story = story_domain.Story.create_default_story(
            self.story_id_3, 'Public Story Title', 'Description',
            self.topic_id, 'public-story-title-two')
        story.story_contents.nodes = [story_domain.StoryNode.from_dict(node)]
        story.story_contents.initial_node_id = node_id
        story.story_contents.next_node_id = self.node_id_2
        story_services.save_new_story(self.admin_id, story)

        topic_services.publish_story(self.topic_id, self.story_id_3,
                                     self.admin_id)

        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.story_id_3, node_id)
        self.get_json(
            '%s/staging/topic/%s' %
            (feconf.REVIEW_TEST_DATA_URL_PREFIX, 'public-story-title-two'),
            expected_status_int=404)
Esempio n. 17
0
    def test_post_returns_ready_for_review_when_acquired_skills_exist(self):
        csrf_token = self.get_new_csrf_token()
        self.save_new_skill('skill_1',
                            self.admin_id,
                            description='Skill Description')
        self.save_new_question('question_1', self.admin_id,
                               self._create_valid_question_data('ABC'),
                               ['skill_1'])
        question_services.create_new_question_skill_link(
            self.admin_id, 'question_1', 'skill_1', 0.3)
        changelist = [
            story_domain.StoryChange({
                'cmd':
                story_domain.CMD_UPDATE_STORY_NODE_PROPERTY,
                'property_name':
                (story_domain.STORY_NODE_PROPERTY_ACQUIRED_SKILL_IDS),
                'node_id':
                self.NODE_ID_1,
                'old_value': [],
                'new_value': ['skill_1']
            })
        ]
        story_services.update_story(self.admin_id, self.STORY_ID, changelist,
                                    'Added acquired skill.')

        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.STORY_ID, self.NODE_ID_2)
        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.STORY_ID, self.NODE_ID_1)
        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True):
            json_response = self.post_json(
                '%s/staging/topic/%s/%s' %
                (feconf.STORY_PROGRESS_URL_PREFIX, self.STORY_URL_FRAGMENT,
                 self.NODE_ID_3), {},
                csrf_token=csrf_token)
        self.assertEqual(len(json_response['summaries']), 0)
        self.assertIsNone(json_response['next_node_id'])
        self.assertTrue(json_response['ready_for_review_test'])
Esempio n. 18
0
class StoryNodeCompletionHandler(base.BaseHandler):
    """Marks a story node as completed after completing."""
    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

    @acl_decorators.can_access_story_viewer_page
    def post(self, story_id, node_id):
        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

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

        story_services.record_completed_node_in_story_context(
            self.user_id, story_id, node_id)
        return self.render_json({})
Esempio n. 19
0
 def _record_completion(self, user_id, STORY_ID, node_id):
     story_services.record_completed_node_in_story_context(
         user_id, STORY_ID, node_id)
Esempio n. 20
0
 def _record_completion(self, user_id, STORY_ID, node_id):
     """Records the completion of a node in the context of a story."""
     story_services.record_completed_node_in_story_context(
         user_id, STORY_ID, node_id)
Esempio n. 21
0
    def test_can_get_completed_chapters_count(self):
        self.save_new_topic(self.TOPIC_ID_1,
                            self.owner_id,
                            name=self.TOPIC_NAME_1,
                            url_fragment='topic-one',
                            description='A new topic',
                            canonical_story_ids=[],
                            additional_story_ids=[],
                            uncategorized_skill_ids=[],
                            subtopics=[self.subtopic_0],
                            next_subtopic_id=1)
        self.save_new_story(self.STORY_ID_1, self.owner_id, self.TOPIC_ID_1)
        topic_services.add_canonical_story(self.owner_id, self.TOPIC_ID_1,
                                           self.STORY_ID_1)
        self.save_new_default_exploration(self.EXP_ID_1, self.owner_id,
                                          'Title 1')
        self.publish_exploration(self.owner_id, self.EXP_ID_1)
        changelist = [
            story_domain.StoryChange({
                'cmd': story_domain.CMD_ADD_STORY_NODE,
                'node_id': 'node_1',
                'title': 'Title 1'
            }),
            story_domain.StoryChange({
                'cmd':
                story_domain.CMD_UPDATE_STORY_NODE_PROPERTY,
                'property_name':
                (story_domain.STORY_NODE_PROPERTY_EXPLORATION_ID),
                'node_id':
                'node_1',
                'old_value':
                None,
                'new_value':
                self.EXP_ID_1
            })
        ]
        story_services.update_story(self.owner_id, self.STORY_ID_1, changelist,
                                    'Added first node.')
        topic_services.publish_story(self.TOPIC_ID_1, self.STORY_ID_1,
                                     self.admin_id)
        topic_services.publish_topic(self.TOPIC_ID_1, self.admin_id)

        self.login(self.CURRICULUM_ADMIN_EMAIL, is_super_admin=True)

        csrf_token = self.get_new_csrf_token()
        new_config_value = [{
            'name': 'math',
            'url_fragment': 'math',
            'topic_ids': [self.TOPIC_ID_1],
            'course_details': '',
            'topic_list_intro': ''
        }]
        payload = {
            'action': 'save_config_properties',
            'new_config_property_values': {
                config_domain.CLASSROOM_PAGES_DATA.name: (new_config_value),
            }
        }
        self.post_json('/adminhandler', payload, csrf_token=csrf_token)
        self.logout()

        self.login(self.VIEWER_EMAIL)

        self.assertEqual(
            self.get_json(feconf.LEARNER_COMPLETED_CHAPTERS_COUNT_DATA_URL)
            ['completed_chapters_count'], 0)

        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.STORY_ID_1, 'node_1')

        self.assertEqual(
            self.get_json(feconf.LEARNER_COMPLETED_CHAPTERS_COUNT_DATA_URL)
            ['completed_chapters_count'], 1)
        self.logout()
Esempio n. 22
0
    def test_record_completed_node_in_story_context(self):
        # Ensure that node completed within the context of a story are
        # recorded correctly. This test actually validates both
        # test_get_completed_node_ids and
        # test_get_next_node_id_to_be_completed_by_user.

        # By default, no completion model should exist for a given user and
        # story.
        completion_model = self._get_progress_model(self.owner_id,
                                                    self.STORY_1_ID)
        self.assertIsNone(completion_model)

        # If the user 'completes an node', the model should record it.
        story_services.record_completed_node_in_story_context(
            self.owner_id, self.STORY_1_ID, self.NODE_ID_1)

        completion_model = self._get_progress_model(self.owner_id,
                                                    self.STORY_1_ID)
        self.assertIsNotNone(completion_model)
        self.assertEqual(completion_model.completed_node_ids, [self.NODE_ID_1])

        # If the same node is completed again within the context of this
        # story, it should not be duplicated.
        story_services.record_completed_node_in_story_context(
            self.owner_id, self.STORY_1_ID, self.NODE_ID_1)
        completion_model = self._get_progress_model(self.owner_id,
                                                    self.STORY_1_ID)
        self.assertEqual(completion_model.completed_node_ids, [self.NODE_ID_1])

        # If the same node and another are completed within the context
        # of a different story, it shouldn't affect this one.
        self.story = self.save_new_story(self.STORY_ID_1, self.USER_ID,
                                         'Title', 'Description', 'Notes',
                                         self.TOPIC_ID)
        topic_services.add_canonical_story(self.USER_ID, self.TOPIC_ID,
                                           self.STORY_ID_1)
        story_services.record_completed_node_in_story_context(
            self.owner_id, self.STORY_ID_1, self.NODE_ID_1)
        story_services.record_completed_node_in_story_context(
            self.owner_id, self.STORY_ID_1, self.NODE_ID_2)
        completion_model = self._get_progress_model(self.owner_id,
                                                    self.STORY_1_ID)
        self.assertEqual(completion_model.completed_node_ids, [self.NODE_ID_1])

        # If two more nodes are completed, they are recorded.
        story_services.record_completed_node_in_story_context(
            self.owner_id, self.STORY_1_ID, self.NODE_ID_2)
        story_services.record_completed_node_in_story_context(
            self.owner_id, self.STORY_1_ID, self.NODE_ID_3)
        completion_model = self._get_progress_model(self.owner_id,
                                                    self.STORY_1_ID)
        self.assertEqual(completion_model.completed_node_ids,
                         [self.NODE_ID_1, self.NODE_ID_2, self.NODE_ID_3])
Esempio n. 23
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)
Esempio n. 24
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
        })
Esempio n. 25
0
    def test_mark_topic_as_learnt_and_story_as_completed(self):
        self.NEW_USER_EMAIL = '*****@*****.**'
        self.NEW_USER_USERNAME = '******'

        self.save_new_valid_exploration(self.EXP_ID_3,
                                        self.admin_id,
                                        title='Title 3',
                                        end_state_name='End',
                                        correctness_feedback_enabled=True)
        self.publish_exploration(self.admin_id, self.EXP_ID_3)

        story = story_domain.Story.create_default_story(
            self.NEW_STORY_ID, 'Title', 'Description', self.TOPIC_ID,
            self.STORY_URL_FRAGMENT_TWO)
        story.meta_tag_content = 'story meta content'

        exp_summary_dicts = (
            summary_services.get_displayable_exp_summary_dicts_matching_ids(
                [self.EXP_ID_3], 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': [],
            'acquired_skill_ids': [],
            'prerequisite_skill_ids': [],
            'outline':
            '',
            'outline_is_finalized':
            False,
            'exploration_id':
            self.EXP_ID_3,
            'exp_summary_dict':
            exp_summary_dicts[0],
            'completed':
            False
        }
        story.story_contents.nodes = [
            story_domain.StoryNode.from_dict(self.node_1)
        ]
        self.nodes = story.story_contents.nodes
        story.story_contents.initial_node_id = 'node_1'
        story.story_contents.next_node_id = 'node_2'
        story_services.save_new_story(self.admin_id, story)
        topic_services.add_canonical_story(self.admin_id, self.TOPIC_ID,
                                           self.NEW_STORY_ID)
        topic_services.publish_story(self.TOPIC_ID, self.NEW_STORY_ID,
                                     self.admin_id)

        csrf_token = self.get_new_csrf_token()
        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.STORY_ID, self.NODE_ID_2)
        story_services.record_completed_node_in_story_context(
            self.viewer_id, self.STORY_ID, self.NODE_ID_1)
        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True):
            self.post_json('%s/staging/topic/%s/%s' %
                           (feconf.STORY_PROGRESS_URL_PREFIX,
                            self.STORY_URL_FRAGMENT, self.NODE_ID_3), {},
                           csrf_token=csrf_token)

        self.assertEqual(
            len(
                learner_progress_services.get_all_learnt_topic_ids(
                    self.viewer_id)), 1)
        self.assertEqual(
            len(
                learner_progress_services.get_all_completed_story_ids(
                    self.viewer_id)), 1)

        def _mock_none_function(_):
            """Mocks None."""
            return None

        story_fetchers_swap = self.swap(story_fetchers, 'get_story_by_id',
                                        _mock_none_function)

        with story_fetchers_swap:
            with self.capture_logging(
                    min_level=logging.ERROR) as captured_logs:
                self.post_json('%s/staging/topic/%s/%s' %
                               (feconf.STORY_PROGRESS_URL_PREFIX,
                                self.STORY_URL_FRAGMENT, self.NODE_ID_3), {},
                               csrf_token=csrf_token)
                self.assertEqual(captured_logs, [
                    'Could not find a story corresponding to %s '
                    'id.' % self.STORY_ID
                ])