Ejemplo n.º 1
0
 def post(self):
     """Handles POST requests."""
     if not self.payload.get('question'):
         raise self.PageNotFoundException
     if not self.payload.get('skill_id'):
         raise self.PageNotFoundException
     question = question_domain.Question.from_dict(
         self.payload.get('question'))
     skill_id = self.payload.get('skill_id')
     question_id = question_services.add_question(self.user_id, question)
     question_services.add_question_id_to_skill(question_id,
                                                question.collection_id,
                                                skill_id, self.user_id)
     return self.render_json({'question_id': question_id})
Ejemplo n.º 2
0
    def accept(self, unused_commit_message):
        """Accepts the suggestion.

        Args:
            unused_commit_message: str. This parameter is passed in for
                consistency with the existing suggestions. As a default commit
                message is used in the add_question function, the arg is unused.
        """
        question_dict = self.change.question_dict
        question_dict['version'] = 1
        question_dict['id'] = (question_services.get_new_question_id())
        question_dict['linked_skill_ids'] = [self.change.skill_id]
        question = question_domain.Question.from_dict(question_dict)
        question.validate()
        question_services.add_question(self.author_id, question)
        skill = skill_services.get_skill_by_id(self.change.skill_id,
                                               strict=False)
        if skill is None:
            raise utils.ValidationError(
                'The skill with the given id doesn\'t exist.')
        question_services.create_new_question_skill_link(
            self.author_id, question_dict['id'], self.change.skill_id,
            constants.DEFAULT_SKILL_DIFFICULTY)
Ejemplo n.º 3
0
    def test_get_question_summaries_for_collection(self):
        """Tests to verify get_question_summaries_for_collection method."""
        coll_id_0 = '0_collection_id'
        exp_id_0 = '0_exploration_id'
        self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)

        # Create a new collection and exploration.
        self.save_new_valid_collection(coll_id_0,
                                       self.owner_id,
                                       exploration_id=exp_id_0)

        # Add a skill.
        collection_services.update_collection(
            self.owner_id, coll_id_0, [{
                'cmd': collection_domain.CMD_ADD_COLLECTION_SKILL,
                'name': 'skill0'
            }], 'Add a new skill')
        collection = collection_services.get_collection_by_id(coll_id_0)
        skill_id = collection.get_skill_id_from_skill_name('skill0')
        collection_node = collection.get_node(exp_id_0)
        collection_node.update_acquired_skill_ids([skill_id])
        # Update the acquired skill IDs for the exploration.
        collection_services.update_collection(
            self.owner_id,
            coll_id_0,
            [{
                'cmd': collection_domain.CMD_EDIT_COLLECTION_NODE_PROPERTY,
                'property_name':
                collection_domain.COLLECTION_NODE_PROPERTY_ACQUIRED_SKILL_IDS,  # pylint: disable=line-too-long
                'exploration_id': exp_id_0,
                'new_value': [skill_id]
            }],
            'Update skill')

        question = question_domain.Question(
            'dummy', 'A Question',
            exp_domain.State.create_default_state('ABC').to_dict(), 1,
            coll_id_0, 'en')

        question_id = question_services.add_question(self.owner_id, question)
        question = question_services.get_question_by_id(question_id)
        question_services.add_question_id_to_skill(question.question_id,
                                                   coll_id_0, skill_id,
                                                   self.owner_id)
        question_summaries = (
            question_services.get_question_summaries_for_collection(coll_id_0))
        self.assertEqual(question_summaries[0].question_id, question_id)
        self.assertEqual(question_summaries[0].question_title, question.title)
        self.assertEqual(question_summaries[0].skill_names, ['skill0'])
Ejemplo n.º 4
0
    def _generate_dummy_skill_and_questions(self):
        """Generate and loads the database with a skill and 15 questions
        linked to the skill.

        Raises:
            Exception: Cannot load new structures data in production mode.
            Exception: User does not have enough rights to generate data.
        """
        if constants.DEV_MODE:
            if self.user.role != feconf.ROLE_ID_ADMIN:
                raise Exception(
                    'User does not have enough rights to generate data.')
            skill_id = skill_services.get_new_skill_id()
            skill_name = 'Dummy Skill %s' % python_utils.UNICODE(
                random.getrandbits(32))
            skill = self._create_dummy_skill(skill_id, skill_name,
                                             '<p>Dummy Explanation 1</p>')
            skill_services.save_new_skill(self.user_id, skill)
            for i in python_utils.RANGE(15):
                question_id = question_services.get_new_question_id()
                question_name = 'Question number %s %s' % (
                    python_utils.UNICODE(i), skill_name)
                question = self._create_dummy_question(question_id,
                                                       question_name,
                                                       [skill_id])
                question_services.add_question(self.user_id, question)
                question_difficulty = [
                    feconf.EASY_SKILL_DIFFICULTY,
                    feconf.MEDIUM_SKILL_DIFFICULTY,
                    feconf.HARD_SKILL_DIFFICULTY
                ]
                random_difficulty = random.choice(question_difficulty)
                question_services.create_new_question_skill_link(
                    self.user_id, question_id, skill_id, random_difficulty)
        else:
            raise Exception('Cannot generate dummy skills in production.')
Ejemplo n.º 5
0
    def test_add_question(self):
        question_data = self._create_valid_question_data('ABC')
        question_id = 'dummy'
        question_data_schema_version = 1
        language_code = 'en'
        question = question_domain.Question(question_id, question_data,
                                            question_data_schema_version,
                                            language_code)

        question_id = question_services.add_question(self.owner_id, question)
        model = question_models.QuestionModel.get(question_id)

        self.assertEqual(model.question_data, question_data)
        self.assertEqual(model.question_data_schema_version,
                         question_data_schema_version)
        self.assertEqual(model.language_code, language_code)
Ejemplo n.º 6
0
    def test_add_question(self):
        state = exp_domain.State.create_default_state('ABC')
        question_data = state.to_dict()
        question_id = 'dummy'
        title = 'A Question'
        question_data_schema_version = 1
        language_code = 'en'
        question = question_domain.Question(question_id, title, question_data,
                                            question_data_schema_version,
                                            language_code)

        question_id = question_services.add_question(self.owner_id, question)
        model = question_models.QuestionModel.get(question_id)

        self.assertEqual(model.title, title)
        self.assertEqual(model.question_data, question_data)
        self.assertEqual(model.question_data_schema_version,
                         question_data_schema_version)
        self.assertEqual(model.language_code, language_code)
Ejemplo n.º 7
0
    def test_batch_get(self):
        """Tests get method of questions batch handler."""
        question_id = question_services.add_question(
            self.owner_id, self.question)
        self.question = question_services.get_question_by_id(question_id)
        question_services.add_question_id_to_skill(
            self.question.question_id, self.question.collection_id,
            self.skill_id, self.owner_id)

        collection_services.record_played_exploration_in_collection_context(
            self.new_user_id, self.collection_id, self.exp_id)

        payload = {}
        payload['collection_id'] = self.collection_id
        payload['stringified_skill_ids'] = json.dumps(
            [self.skill_id, 'test'])

        self.login(self.NEW_USER_EMAIL)
        response_json = self.get_json(
            '%s/batch' % feconf.QUESTION_DATA_URL, payload,
            expect_errors=False)
        self.assertIn(self.question.to_dict(), response_json['questions_dict'])
        self.assertEqual(len(response_json['questions_dict']), 1)

        response = self.testapp.get(
            '%s/batch' % feconf.QUESTION_DATA_URL,
            expect_errors=True)
        self.assertEqual(response.status_int, 404)

        del payload['stringified_skill_ids']
        response = self.testapp.get(
            '%s/batch' % feconf.QUESTION_DATA_URL, payload,
            expect_errors=True)
        self.assertEqual(response.status_int, 404)

        self.logout()
        self.login(self.random_email)
        response_json = self.testapp.get(
            '%s/batch' % feconf.QUESTION_DATA_URL, payload,
            expect_errors=True)
        self.assertEqual(response.status_int, 404)
Ejemplo n.º 8
0
class QuestionCreationHandler(base.BaseHandler):
    """A handler that creates the question model given a question dict."""
    @acl_decorators.can_manage_question_skill_status
    def post(self, comma_separated_skill_ids):
        """Handles POST requests."""
        skill_ids = comma_separated_skill_ids.split(',')
        try:
            for skill_id in skill_ids:
                skill_domain.Skill.require_valid_skill_id(skill_id)
        except Exception:
            raise self.InvalidInputException

        try:
            skill_services.get_multi_skills(skill_ids)
        except Exception, e:
            raise self.PageNotFoundException(e)

        question_dict = self.payload.get('question_dict')
        if ((question_dict['id'] is not None)
                or ('question_state_data' not in question_dict)
                or ('language_code' not in question_dict)
                or (question_dict['version'] != 1)):
            raise self.InvalidInputException

        question_dict['question_state_data_schema_version'] = (
            feconf.CURRENT_STATE_SCHEMA_VERSION)
        question_dict['id'] = question_services.get_new_question_id()
        question_dict['linked_skill_ids'] = skill_ids
        try:
            question = question_domain.Question.from_dict(question_dict)
        except Exception:
            raise self.InvalidInputException
        question_services.add_question(self.user_id, question)
        # TODO(vinitamurthi): Replace DEFAULT_SKILL_DIFFICULTY
        # with a value passed from the frontend.
        question_services.link_multiple_skills_for_question(
            self.user_id, question.id, skill_ids,
            [constants.DEFAULT_SKILL_DIFFICULTY] * len(skill_ids))

        self.values.update({'question_id': question.id})
        self.render_json(self.values)
Ejemplo n.º 9
0
    def test_manager_get(self):
        """Tests get method of question manager handler."""
        question_id = question_services.add_question(
            self.owner_id, self.question)
        self.question = question_services.get_question_by_id(question_id)
        question_services.add_question_id_to_skill(
            self.question.question_id, self.question.collection_id,
            self.skill_id, self.owner_id)

        self.login(self.NEW_USER_EMAIL)
        payload = {}
        payload['collection_id'] = self.collection_id
        response_json = self.get_json(
            '%s' % feconf.QUESTION_MANAGER_URL, payload,
            expect_errors=False)
        expected_question_summary = question_domain.QuestionSummary(
            self.question.question_id, self.question.title, ['test'])
        self.assertIn(
            expected_question_summary.to_dict(),
            response_json['question_summary_dicts'])

        response = self.testapp.get(
            '%s/batch' % feconf.QUESTION_MANAGER_URL,
            expect_errors=True)
        self.assertEqual(response.status_int, 404)

        del payload['collection_id']
        response = self.testapp.get(
            '%s/batch' % feconf.QUESTION_MANAGER_URL, payload,
            expect_errors=True)
        self.assertEqual(response.status_int, 404)

        self.logout()
        self.login(self.random_email)
        payload['collection_id'] = self.collection_id
        response = self.testapp.get(
            '%s' % feconf.QUESTION_MANAGER_URL, payload,
            expect_errors=True)
        self.assertEqual(response.status_int, 401)
Ejemplo n.º 10
0
    def test_update_question(self):
        state = exp_domain.State.create_default_state('ABC')
        question_data = state.to_dict()
        question_id = 'dummy'
        title = 'A Question'
        question_data_schema_version = 1
        collection_id = 'col1'
        language_code = 'en'
        question = question_domain.Question(question_id, title, question_data,
                                            question_data_schema_version,
                                            collection_id, language_code)

        question_id = question_services.add_question(self.owner_id, question)
        change_dict = {
            'cmd': 'update_question_property',
            'property_name': 'title',
            'new_value': 'ABC',
            'old_value': 'A Question'
        }
        change_list = [question_domain.QuestionChange(change_dict)]
        with self.assertRaisesRegexp(
                Exception, ('The question with ID %s is not present'
                            ' in the given collection' % question_id)):
            question_services.update_question(self.owner_id, 'random',
                                              question_id, change_list,
                                              'updated')

        question_services.update_question(self.owner_id, collection_id,
                                          question_id, change_list,
                                          ('updated title'))

        model = question_models.QuestionModel.get(question_id)
        self.assertEqual(model.title, 'ABC')
        self.assertEqual(model.question_data, question_data)
        self.assertEqual(model.question_data_schema_version,
                         question_data_schema_version)
        self.assertEqual(model.collection_id, collection_id)
        self.assertEqual(model.language_code, language_code)
Ejemplo n.º 11
0
    def test_delete_question(self):
        collection_id = 'col1'
        exp_id = '0_exploration_id'
        owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)

        # Create a new collection and exploration.
        self.save_new_valid_collection(collection_id,
                                       owner_id,
                                       exploration_id=exp_id)

        # Add a skill.
        collection_services.update_collection(
            owner_id, collection_id, [{
                'cmd': collection_domain.CMD_ADD_COLLECTION_SKILL,
                'name': 'skill0'
            }], 'Add a new skill')

        question = question_domain.Question(
            'dummy', 'A Question',
            exp_domain.State.create_default_state('ABC').to_dict(), 1,
            collection_id, 'en')

        question_id = question_services.add_question(self.owner_id, question)
        with self.assertRaisesRegexp(
                Exception, ('The question with ID %s is not present'
                            ' in the given collection' % question_id)):
            question_services.delete_question(self.owner_id, 'random',
                                              question_id)
        question_services.delete_question(self.owner_id, collection_id,
                                          question_id)

        with self.assertRaisesRegexp(
                Exception,
            ('Entity for class QuestionModel with id %s not found' %
             (question_id))):
            question_models.QuestionModel.get(question_id)
Ejemplo n.º 12
0
    def _load_dummy_new_structures_data(self):
        """Loads the database with two topics (one of which is empty), a story
        and three skills in the topic (two of them in a subtopic) and a question
        attached to each skill.

        Raises:
            Exception: Cannot load new structures data in production mode.
            Exception: User does not have enough rights to generate data.
        """
        if constants.DEV_MODE:
            if self.user.role != feconf.ROLE_ID_ADMIN:
                raise Exception(
                    'User does not have enough rights to generate data.')
            topic_id_1 = topic_services.get_new_topic_id()
            topic_id_2 = topic_services.get_new_topic_id()
            story_id = story_services.get_new_story_id()
            skill_id_1 = skill_services.get_new_skill_id()
            skill_id_2 = skill_services.get_new_skill_id()
            skill_id_3 = skill_services.get_new_skill_id()
            question_id_1 = question_services.get_new_question_id()
            question_id_2 = question_services.get_new_question_id()
            question_id_3 = question_services.get_new_question_id()

            skill_1 = self._create_dummy_skill(skill_id_1, 'Dummy Skill 1',
                                               '<p>Dummy Explanation 1</p>')
            skill_2 = self._create_dummy_skill(skill_id_2, 'Dummy Skill 2',
                                               '<p>Dummy Explanation 2</p>')
            skill_3 = self._create_dummy_skill(skill_id_3, 'Dummy Skill 3',
                                               '<p>Dummy Explanation 3</p>')

            question_1 = self._create_dummy_question(question_id_1,
                                                     'Question 1',
                                                     [skill_id_1])
            question_2 = self._create_dummy_question(question_id_2,
                                                     'Question 2',
                                                     [skill_id_2])
            question_3 = self._create_dummy_question(question_id_3,
                                                     'Question 3',
                                                     [skill_id_3])
            question_services.add_question(self.user_id, question_1)
            question_services.add_question(self.user_id, question_2)
            question_services.add_question(self.user_id, question_3)

            question_services.create_new_question_skill_link(
                self.user_id, question_id_1, skill_id_1, 0.3)
            question_services.create_new_question_skill_link(
                self.user_id, question_id_2, skill_id_2, 0.5)
            question_services.create_new_question_skill_link(
                self.user_id, question_id_3, skill_id_3, 0.7)

            topic_1 = topic_domain.Topic.create_default_topic(
                topic_id_1, 'Dummy Topic 1', 'abbrev')
            topic_2 = topic_domain.Topic.create_default_topic(
                topic_id_2, 'Empty Topic', 'abbrev')

            topic_1.add_canonical_story(story_id)
            topic_1.add_uncategorized_skill_id(skill_id_1)
            topic_1.add_uncategorized_skill_id(skill_id_2)
            topic_1.add_uncategorized_skill_id(skill_id_3)
            topic_1.add_subtopic(1, 'Dummy Subtopic Title')
            topic_1.move_skill_id_to_subtopic(None, 1, skill_id_2)
            topic_1.move_skill_id_to_subtopic(None, 1, skill_id_3)

            subtopic_page = (
                subtopic_page_domain.SubtopicPage.create_default_subtopic_page(
                    1, topic_id_1))
            self._reload_exploration('0')
            self._reload_exploration('16')
            story = story_domain.Story.create_default_story(
                story_id, 'Dummy Story 1', topic_id_1)
            story.add_node('%s%d' % (story_domain.NODE_ID_PREFIX, 1),
                           'Dummy Chapter 1')
            story.update_node_destination_node_ids(
                '%s%d' % (story_domain.NODE_ID_PREFIX, 1),
                ['%s%d' % (story_domain.NODE_ID_PREFIX, 2)])
            story.update_node_exploration_id(
                '%s%d' % (story_domain.NODE_ID_PREFIX, 1), '0')

            story.add_node('%s%d' % (story_domain.NODE_ID_PREFIX, 2),
                           'Dummy Chapter 2')
            story.update_node_exploration_id(
                '%s%d' % (story_domain.NODE_ID_PREFIX, 2), '16')

            skill_services.save_new_skill(self.user_id, skill_1)
            skill_services.save_new_skill(self.user_id, skill_2)
            skill_services.save_new_skill(self.user_id, skill_3)
            story_services.save_new_story(self.user_id, story)
            topic_services.save_new_topic(self.user_id, topic_1)
            topic_services.save_new_topic(self.user_id, topic_2)
            subtopic_page_services.save_subtopic_page(
                self.user_id, subtopic_page, 'Added subtopic', [
                    topic_domain.TopicChange({
                        'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                        'subtopic_id': 1,
                        'title': 'Dummy Subtopic Title'
                    })
                ])

            topic_services.publish_story(topic_id_1, story_id, self.user_id)
            topic_services.publish_topic(topic_id_1, self.user_id)
        else:
            raise Exception('Cannot load new structures data in production.')
Ejemplo n.º 13
0
    def _load_dummy_new_structures_data(self):
        """Loads the database with two topics (one of which is empty), a story
        and three skills in the topic (two of them in a subtopic) and a question
        attached to each skill.

        Raises:
            Exception: Cannot load new structures data in production mode.
            Exception: User does not have enough rights to generate data.
        """
        if constants.DEV_MODE:
            if self.user.role != feconf.ROLE_ID_ADMIN:
                raise Exception(
                    'User does not have enough rights to generate data.')
            topic_id_1 = topic_services.get_new_topic_id()
            topic_id_2 = topic_services.get_new_topic_id()
            story_id = story_services.get_new_story_id()
            skill_id_1 = skill_services.get_new_skill_id()
            skill_id_2 = skill_services.get_new_skill_id()
            skill_id_3 = skill_services.get_new_skill_id()
            question_id_1 = question_services.get_new_question_id()
            question_id_2 = question_services.get_new_question_id()
            question_id_3 = question_services.get_new_question_id()

            skill_1 = self._create_dummy_skill(skill_id_1, 'Dummy Skill 1',
                                               '<p>Dummy Explanation 1</p>')
            skill_2 = self._create_dummy_skill(skill_id_2, 'Dummy Skill 2',
                                               '<p>Dummy Explanation 2</p>')
            skill_3 = self._create_dummy_skill(skill_id_3, 'Dummy Skill 3',
                                               '<p>Dummy Explanation 3</p>')

            question_1 = self._create_dummy_question(question_id_1,
                                                     'Question 1',
                                                     [skill_id_1])
            question_2 = self._create_dummy_question(question_id_2,
                                                     'Question 2',
                                                     [skill_id_2])
            question_3 = self._create_dummy_question(question_id_3,
                                                     'Question 3',
                                                     [skill_id_3])
            question_services.add_question(self.user_id, question_1)
            question_services.add_question(self.user_id, question_2)
            question_services.add_question(self.user_id, question_3)

            question_services.create_new_question_skill_link(
                self.user_id, question_id_1, skill_id_1, 0.3)
            question_services.create_new_question_skill_link(
                self.user_id, question_id_2, skill_id_2, 0.5)
            question_services.create_new_question_skill_link(
                self.user_id, question_id_3, skill_id_3, 0.7)

            topic_1 = topic_domain.Topic.create_default_topic(
                topic_id_1, 'Dummy Topic 1', 'abbrev', 'description')
            topic_2 = topic_domain.Topic.create_default_topic(
                topic_id_2, 'Empty Topic', 'abbrev', 'description')

            topic_1.add_canonical_story(story_id)
            topic_1.add_uncategorized_skill_id(skill_id_1)
            topic_1.add_uncategorized_skill_id(skill_id_2)
            topic_1.add_uncategorized_skill_id(skill_id_3)
            topic_1.add_subtopic(1, 'Dummy Subtopic Title')
            topic_1.move_skill_id_to_subtopic(None, 1, skill_id_2)
            topic_1.move_skill_id_to_subtopic(None, 1, skill_id_3)

            subtopic_page = (
                subtopic_page_domain.SubtopicPage.create_default_subtopic_page(
                    1, topic_id_1))
            # These explorations were chosen since they pass the validations
            # for published stories.
            self._reload_exploration('15')
            self._reload_exploration('25')
            self._reload_exploration('13')

            story = story_domain.Story.create_default_story(
                story_id, 'Help Jaime win the Arcade', topic_id_1)

            story_node_dicts = [{
                'exp_id':
                '15',
                'title':
                'What are the place values?',
                'description':
                'Jaime learns the place value of each digit ' +
                'in a big number.'
            }, {
                'exp_id':
                '25',
                'title':
                'Finding the value of a number',
                'description':
                'Jaime understands the value of his ' + 'arcade score.'
            }, {
                'exp_id':
                '13',
                'title':
                'Comparing Numbers',
                'description':
                'Jaime learns if a number is smaller or ' +
                'greater than another number.'
            }]

            def generate_dummy_story_nodes(node_id, exp_id, title,
                                           description):
                """Generates and connects sequential story nodes.

                Args:
                    node_id: int. The node id.
                    exp_id: str. The exploration id.
                    title: str. The title of the story node.
                    description: str. The description of the story node.
                """

                story.add_node('%s%d' % (story_domain.NODE_ID_PREFIX, node_id),
                               title)
                story.update_node_description(
                    '%s%d' % (story_domain.NODE_ID_PREFIX, node_id),
                    description)
                story.update_node_exploration_id(
                    '%s%d' % (story_domain.NODE_ID_PREFIX, node_id), exp_id)

                if node_id != len(story_node_dicts):
                    story.update_node_destination_node_ids(
                        '%s%d' % (story_domain.NODE_ID_PREFIX, node_id),
                        ['%s%d' % (story_domain.NODE_ID_PREFIX, node_id + 1)])

                exp_services.update_exploration(self.user_id, exp_id, [
                    exp_domain.ExplorationChange({
                        'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                        'property_name': 'category',
                        'new_value': 'Astronomy'
                    })
                ], 'Change category')

            for i, story_node_dict in enumerate(story_node_dicts):
                generate_dummy_story_nodes(i + 1, **story_node_dict)

            skill_services.save_new_skill(self.user_id, skill_1)
            skill_services.save_new_skill(self.user_id, skill_2)
            skill_services.save_new_skill(self.user_id, skill_3)
            story_services.save_new_story(self.user_id, story)
            topic_services.save_new_topic(self.user_id, topic_1)
            topic_services.save_new_topic(self.user_id, topic_2)
            subtopic_page_services.save_subtopic_page(
                self.user_id, subtopic_page, 'Added subtopic', [
                    topic_domain.TopicChange({
                        'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                        'subtopic_id': 1,
                        'title': 'Dummy Subtopic Title'
                    })
                ])

            topic_services.publish_story(topic_id_1, story_id, self.user_id)
        else:
            raise Exception('Cannot load new structures data in production.')
Ejemplo n.º 14
0
    def post(self):
        """Handles POST requests."""
        skill_ids = self.payload.get('skill_ids')

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

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

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

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

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

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

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

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

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

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

        self.values.update({'question_id': question.id})
        self.render_json(self.values)
Ejemplo n.º 15
0
    def post(self):
        """Handles POST requests."""
        skill_ids = self.payload.get('skill_ids')

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

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

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

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

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

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

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

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

        try:
            skill_difficulties = [
                float(difficulty) for difficulty in skill_difficulties
            ]
        except (ValueError, TypeError):
            raise self.InvalidInputException(
                'Skill difficulties must be a float value')

        if any((difficulty < 0 or difficulty > 1)
               for difficulty in skill_difficulties):
            raise self.InvalidInputException(
                'Skill difficulties must be between 0 and 1')

        question_services.add_question(self.user_id, question)
        question_services.link_multiple_skills_for_question(
            self.user_id, question.id, skill_ids, skill_difficulties)

        self.values.update({'question_id': question.id})
        self.render_json(self.values)
Ejemplo n.º 16
0
    def post(self):
        """Generates structures for Android end-to-end tests.

        This handler generates structures for Android end-to-end tests in
        order to evaluate the integration of network requests from the
        Android client to the backend. This handler should only be called
        once (or otherwise raises an exception), and can only be used in
        development mode (this handler is unavailable in production).

        Note that the handler outputs an empty JSON dict when the request is
        successful.

        The specific structures that are generated:
            Topic: A topic with both a test story and a subtopic.
            Story: A story with 'android_interactions' as a exploration
                node.
            Exploration: 'android_interactions' from the local assets.
            Subtopic: A dummy subtopic to validate the topic.
            Skill: A dummy skill to validate the subtopic.

        Raises:
            Exception. When used in production mode.
            InvalidInputException. The topic is already
                created but not published.
            InvalidInputException. The topic is already published.
        """

        if not constants.DEV_MODE:
            raise Exception('Cannot load new structures data in production.')
        if topic_services.does_topic_with_name_exist('Android test'):
            topic = topic_fetchers.get_topic_by_name('Android test')
            topic_rights = topic_fetchers.get_topic_rights(topic.id,
                                                           strict=False)
            if topic_rights.topic_is_published:
                raise self.InvalidInputException(
                    'The topic is already published.')

            raise self.InvalidInputException(
                'The topic exists but is not published.')
        exp_id = '26'
        user_id = feconf.SYSTEM_COMMITTER_ID
        # Generate new Structure id for topic, story, skill and question.
        topic_id = topic_fetchers.get_new_topic_id()
        story_id = story_services.get_new_story_id()
        skill_id = skill_services.get_new_skill_id()
        question_id = question_services.get_new_question_id()

        # Create dummy skill and question.
        skill = self._create_dummy_skill(skill_id, 'Dummy Skill for Android',
                                         '<p>Dummy Explanation 1</p>')
        question = self._create_dummy_question(question_id, 'Question 1',
                                               [skill_id])
        question_services.add_question(user_id, question)
        question_services.create_new_question_skill_link(
            user_id, question_id, skill_id, 0.3)

        # Create and update topic to validate before publishing.
        topic = topic_domain.Topic.create_default_topic(
            topic_id, 'Android test', 'test-topic-one', 'description', 'fragm')
        topic.update_url_fragment('test-topic')
        topic.update_meta_tag_content('tag')
        topic.update_page_title_fragment_for_web('page title for topic')
        # Save the dummy image to the filesystem to be used as thumbnail.
        with utils.open_file(os.path.join(feconf.TESTS_DATA_DIR,
                                          'test_svg.svg'),
                             'rb',
                             encoding=None) as f:
            raw_image = f.read()
        fs = fs_services.GcsFileSystem(feconf.ENTITY_TYPE_TOPIC, topic_id)
        fs.commit('%s/test_svg.svg' % (constants.ASSET_TYPE_THUMBNAIL),
                  raw_image,
                  mimetype='image/svg+xml')
        # Update thumbnail properties.
        topic_services.update_thumbnail_filename(topic, 'test_svg.svg')
        topic.update_thumbnail_bg_color('#C6DCDA')

        # Add other structures to the topic.
        topic.add_canonical_story(story_id)
        topic.add_uncategorized_skill_id(skill_id)
        topic.add_subtopic(1, 'Test Subtopic Title', 'testsubtop')

        # Update and validate subtopic.
        topic_services.update_subtopic_thumbnail_filename(
            topic, 1, 'test_svg.svg')
        topic.update_subtopic_thumbnail_bg_color(1, '#FFFFFF')
        topic.update_subtopic_url_fragment(1, 'suburl')
        topic.move_skill_id_to_subtopic(None, 1, skill_id)
        subtopic_page = (
            subtopic_page_domain.SubtopicPage.create_default_subtopic_page(
                1, topic_id))

        # Upload local exploration to the datastore and enable feedback.
        exp_services.load_demo(exp_id)
        rights_manager.release_ownership_of_exploration(
            user_services.get_system_user(), exp_id)
        exp_services.update_exploration(
            user_id, exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                    'property_name': 'correctness_feedback_enabled',
                    'new_value': True
                })
            ], 'Changed correctness_feedback_enabled.')

        # Add and update the exploration/node to the story.
        story = story_domain.Story.create_default_story(
            story_id, 'Android End to End testing', 'Description', topic_id,
            'android-end-to-end-testing')

        story.add_node('%s%d' % (story_domain.NODE_ID_PREFIX, 1),
                       'Testing with UI Automator')

        story.update_node_description(
            '%s%d' % (story_domain.NODE_ID_PREFIX, 1),
            'To test all Android interactions')
        story.update_node_exploration_id(
            '%s%d' % (story_domain.NODE_ID_PREFIX, 1), exp_id)

        # Save the dummy image to the filesystem to be used as thumbnail.
        with utils.open_file(os.path.join(feconf.TESTS_DATA_DIR,
                                          'test_svg.svg'),
                             'rb',
                             encoding=None) as f:
            raw_image = f.read()
        fs = fs_services.GcsFileSystem(feconf.ENTITY_TYPE_STORY, story_id)
        fs.commit('%s/test_svg.svg' % (constants.ASSET_TYPE_THUMBNAIL),
                  raw_image,
                  mimetype='image/svg+xml')

        story.update_node_thumbnail_filename(
            '%s%d' % (story_domain.NODE_ID_PREFIX, 1), 'test_svg.svg')
        story.update_node_thumbnail_bg_color(
            '%s%d' % (story_domain.NODE_ID_PREFIX, 1), '#F8BF74')

        # Update and validate the story.
        story.update_meta_tag_content('tag')
        story.update_thumbnail_filename('test_svg.svg')
        story.update_thumbnail_bg_color(
            constants.ALLOWED_THUMBNAIL_BG_COLORS['story'][0])

        # Save the previously created structures
        # (skill, story, topic, subtopic).
        skill_services.save_new_skill(user_id, skill)
        story_services.save_new_story(user_id, story)
        topic_services.save_new_topic(user_id, topic)
        subtopic_page_services.save_subtopic_page(
            user_id, subtopic_page, 'Added subtopic', [
                topic_domain.TopicChange({
                    'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                    'subtopic_id': 1,
                    'title': 'Dummy Subtopic Title',
                    'url_fragment': 'dummy-fragment'
                })
            ])

        # Generates translation opportunities for the Contributor Dashboard.
        exp_ids_in_story = story.story_contents.get_all_linked_exp_ids()
        opportunity_services.add_new_exploration_opportunities(
            story_id, exp_ids_in_story)

        # Publish the story and topic.
        topic_services.publish_story(topic_id, story_id, user_id)
        topic_services.publish_topic(topic_id, user_id)

        # Upload thumbnails to be accessible through AssetsDevHandler.
        self._upload_thumbnail(topic_id, feconf.ENTITY_TYPE_TOPIC)
        self._upload_thumbnail(story_id, feconf.ENTITY_TYPE_STORY)
        self.render_json({})