예제 #1
0
    def post(self):
        description = self.payload.get('description')
        linked_topic_ids = self.payload.get('linked_topic_ids')
        explanation_dict = self.payload.get('explanation_dict')
        rubrics = self.payload.get('rubrics')
        if not isinstance(rubrics, list):
            raise self.InvalidInputException('Rubrics should be a list.')

        if not isinstance(explanation_dict, dict):
            raise self.InvalidInputException('Explanation should be a dict.')

        try:
            state_domain.SubtitledHtml.from_dict(explanation_dict)
        except:
            raise self.InvalidInputException(
                'Explanation should be a valid SubtitledHtml dict.')

        rubrics = [skill_domain.Rubric.from_dict(rubric) for rubric in rubrics]
        new_skill_id = skill_services.get_new_skill_id()
        if linked_topic_ids is not None:
            topics = topic_fetchers.get_topics_by_ids(linked_topic_ids)
            for topic in topics:
                if topic is None:
                    raise self.InvalidInputException
                topic_services.add_uncategorized_skill(self.user_id, topic.id,
                                                       new_skill_id)

        skill_domain.Skill.require_valid_description(description)

        skill = skill_domain.Skill.create_default_skill(
            new_skill_id, description, rubrics)
        skill.update_explanation(explanation_dict)
        skill_services.save_new_skill(self.user_id, skill)

        self.render_json({'skillId': new_skill_id})
예제 #2
0
    def test_migration_job_does_not_convert_up_to_date_skill(self):
        """Tests that the skill migration job does not convert a
        skill that is already the latest schema version.
        """
        # Create a new skill that should not be affected by the
        # job.
        skill = skill_domain.Skill.create_default_skill(
            self.SKILL_ID, description='A description')
        skill_services.save_new_skill(self.albert_id, skill)
        self.assertEqual(skill.skill_contents_schema_version,
                         feconf.CURRENT_SKILL_CONTENTS_SCHEMA_VERSION)
        self.assertEqual(skill.misconceptions_schema_version,
                         feconf.CURRENT_MISCONCEPTIONS_SCHEMA_VERSION)

        # Start migration job.
        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_EDITORS', True):
            job_id = (skill_jobs_one_off.SkillMigrationOneOffJob.create_new())
            skill_jobs_one_off.SkillMigrationOneOffJob.enqueue(job_id)
            self.process_and_flush_pending_tasks()

        # Verify the skill is exactly the same after migration.
        updated_skill = (skill_services.get_skill_by_id(self.SKILL_ID))
        self.assertEqual(updated_skill.skill_contents_schema_version,
                         feconf.CURRENT_SKILL_CONTENTS_SCHEMA_VERSION)
        self.assertEqual(updated_skill.misconceptions_schema_version,
                         feconf.CURRENT_MISCONCEPTIONS_SCHEMA_VERSION)

        output = skill_jobs_one_off.SkillMigrationOneOffJob.get_output(job_id)  # pylint: disable=line-too-long
        expected = [[u'skill_migrated', [u'1 skills successfully migrated.']]]
        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
예제 #3
0
    def test_migration_job_skips_updated_skill_failing_validation(self):
        def _mock_get_skill_by_id(unused_skill_id):
            """Mocks get_skill_by_id()."""
            return 'invalid_skill'

        skill = skill_domain.Skill.create_default_skill(
            self.SKILL_ID, description='A description')
        skill_services.save_new_skill(self.albert_id, skill)

        get_skill_by_id_swap = self.swap(skill_services, 'get_skill_by_id',
                                         _mock_get_skill_by_id)

        with get_skill_by_id_swap:
            job_id = (skill_jobs_one_off.SkillMigrationOneOffJob.create_new())
            skill_jobs_one_off.SkillMigrationOneOffJob.enqueue(job_id)
            self.process_and_flush_pending_tasks()

        output = skill_jobs_one_off.SkillMigrationOneOffJob.get_output(job_id)

        # If the skill had been successfully migrated, this would include a
        # 'successfully migrated' message. Its absence means that the skill
        # could not be processed.
        expected = [[
            u'validation_error',
            [
                u'Skill %s failed validation: \'str\' object has '
                'no attribute \'validate\'' % (self.SKILL_ID)
            ]
        ]]
        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
예제 #4
0
    def test_migration_job_skips_updated_skill_failing_validation(self):
        skill = skill_domain.Skill.create_default_skill(
            self.SKILL_ID, 'A description', self.rubrics)
        skill_services.save_new_skill(self.albert_id, skill)
        skill.description = ''

        def _mock_get_skill_by_id(unused_skill_id):
            """Mocks get_skill_by_id()."""
            return skill

        get_skill_by_id_swap = self.swap(skill_fetchers, 'get_skill_by_id',
                                         _mock_get_skill_by_id)

        with get_skill_by_id_swap:
            job_id = (skill_jobs_one_off.SkillMigrationOneOffJob.create_new())
            skill_jobs_one_off.SkillMigrationOneOffJob.enqueue(job_id)
            self.process_and_flush_pending_mapreduce_tasks()

        output = skill_jobs_one_off.SkillMigrationOneOffJob.get_output(job_id)

        # If the skill had been successfully migrated, this would include a
        # 'successfully migrated' message. Its absence means that the skill
        # could not be processed.
        expected = [[
            'validation_error',
            [
                'Skill %s failed validation: Description '
                'field should not be empty' % (self.SKILL_ID)
            ]
        ]]
        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
예제 #5
0
    def setUp(self):
        super(MissingSkillMigrationOneOffJobTests, self).setUp()

        self.signup(self.ALBERT_EMAIL, self.ALBERT_NAME)
        self.albert_id = self.get_user_id_from_email(self.ALBERT_EMAIL)
        self.set_admins([self.ALBERT_NAME])

        rubrics = [
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[0],
                                ['Explanation 1']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[1],
                                ['Explanation 2']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[2],
                                ['Explanation 3'])
        ]

        skill = skill_domain.Skill.create_default_skill(
            self.SKILL_ID, 'A description', rubrics)
        skill_services.save_new_skill(self.albert_id, skill)

        self.commit_model_instance = (skill_models.SkillCommitLogEntryModel.
                                      get_by_id('skill-skill_id-1'))
        self.metadata_model_instance = (
            skill_models.SkillSnapshotMetadataModel.get_by_id('skill_id-1'))
        self.content_model_instance = (
            skill_models.SkillSnapshotContentModel.get_by_id('skill_id-1'))

        self.process_and_flush_pending_mapreduce_tasks()
예제 #6
0
    def test_migration_job_does_not_convert_up_to_date_skill(self):
        """Tests that the skill migration job does not convert a
        skill that is already the latest schema version.
        """
        # Create a new skill that should not be affected by the
        # job.
        skill = skill_domain.Skill.create_default_skill(
            self.SKILL_ID, 'A description', self.rubrics)
        skill_services.save_new_skill(self.albert_id, skill)
        self.assertEqual(skill.skill_contents_schema_version,
                         feconf.CURRENT_SKILL_CONTENTS_SCHEMA_VERSION)
        self.assertEqual(skill.misconceptions_schema_version,
                         feconf.CURRENT_MISCONCEPTIONS_SCHEMA_VERSION)
        self.assertEqual(skill.rubric_schema_version,
                         feconf.CURRENT_RUBRIC_SCHEMA_VERSION)

        # Start migration job.
        job_id = (skill_jobs_one_off.SkillMigrationOneOffJob.create_new())
        skill_jobs_one_off.SkillMigrationOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_mapreduce_tasks()

        # Verify the skill is exactly the same after migration.
        updated_skill = (skill_fetchers.get_skill_by_id(self.SKILL_ID))
        self.assertEqual(updated_skill.skill_contents_schema_version,
                         feconf.CURRENT_SKILL_CONTENTS_SCHEMA_VERSION)
        self.assertEqual(updated_skill.misconceptions_schema_version,
                         feconf.CURRENT_MISCONCEPTIONS_SCHEMA_VERSION)
        self.assertEqual(updated_skill.rubric_schema_version,
                         feconf.CURRENT_RUBRIC_SCHEMA_VERSION)

        output = skill_jobs_one_off.SkillMigrationOneOffJob.get_output(job_id)
        expected = [[u'skill_migrated', [u'1 skills successfully migrated.']]]
        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
    def test_job_skips_deleted_skills(self):
        rubrics = [
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[0], ['<p>Explanation 1</p>']),
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[1], ['<p>Explanation 2</p>']),
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[2], ['<p>Explanation 3</p>'])]

        skill = skill_domain.Skill.create_default_skill(
            'valid_skill', 'A description', rubrics)
        skill_services.save_new_skill(self.albert_id, skill)

        skill_services.delete_skill(
            self.albert_id, skill.id)
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            skill_fetchers.get_skill_by_id(skill.id)

        job_id = (
            skill_jobs_one_off.SkillMathRteAuditOneOffJob.create_new())
        skill_jobs_one_off.SkillMathRteAuditOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        output = skill_jobs_one_off.SkillMathRteAuditOneOffJob.get_output(
            job_id)
        self.assertEqual(output, [])
예제 #8
0
    def test_migration_job_skips_deleted_skill(self):
        """Tests that the skill migration job skips deleted skill
        and does not attempt to migrate.
        """
        skill = skill_domain.Skill.create_default_skill(
            self.SKILL_ID, 'A description', self.rubrics)
        skill_services.save_new_skill(self.albert_id, skill)

        # Delete the skill before migration occurs.
        skill_services.delete_skill(self.albert_id, self.SKILL_ID)

        # Ensure the skill is deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            skill_fetchers.get_skill_by_id(self.SKILL_ID)

        # Start migration job on sample skill.
        job_id = (skill_jobs_one_off.SkillMigrationOneOffJob.create_new())
        skill_jobs_one_off.SkillMigrationOneOffJob.enqueue(job_id)

        # This running without errors indicates the deleted skill is
        # being ignored.
        self.process_and_flush_pending_mapreduce_tasks()

        # Ensure the skill is still deleted.
        with self.assertRaisesRegexp(Exception, 'Entity .* not found'):
            skill_fetchers.get_skill_by_id(self.SKILL_ID)

        output = skill_jobs_one_off.SkillMigrationOneOffJob.get_output(job_id)
        expected = [[u'skill_deleted', [u'Encountered 1 deleted skills.']]]
        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
예제 #9
0
파일: admin.py 프로젝트: pengcheng95/oppia
    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 = list(
                    constants.SKILL_DIFFICULTY_LABEL_TO_FLOAT.values())
                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.')
예제 #10
0
    def post(self):
        if not feconf.ENABLE_NEW_STRUCTURES:
            raise self.PageNotFoundException
        topic_id = self.payload.get('topic_id')

        if topic_id is not None:
            topic = topic_services.get_topic_by_id(topic_id, strict=False)
            if topic is None:
                raise self.InvalidInputException

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

        skill_domain.Skill.require_valid_description(description)

        new_skill_id = skill_services.get_new_skill_id()
        skill = skill_domain.Skill.create_default_skill(
            new_skill_id, description)
        skill_services.save_new_skill(self.user_id, skill)

        if topic_id is not None:
            topic_services.add_uncategorized_skill(
                self.user_id, topic_id, new_skill_id)

        self.render_json({
            'skillId': new_skill_id
        })
예제 #11
0
    def test_skill_description_handler_when_unique(self):
        self.login(self.CURRICULUM_ADMIN_EMAIL)
        json_response = self.get_json(self.url)
        self.assertEqual(json_response['skill_description_exists'], False)

        # Publish a skill.
        new_skill_id = skill_services.get_new_skill_id()
        rubrics = [
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[0],
                                ['Explanation 1']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[1],
                                ['Explanation 2']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[2],
                                ['Explanation 3'])
        ]
        skill = skill_domain.Skill.create_default_skill(
            new_skill_id, self.skill_description, rubrics)
        skill_services.save_new_skill(self.admin_id, skill)

        # Unique skill description does not exist.
        skill_description_2 = 'Subtracting Fractions'
        url_2 = '%s/%s' % (feconf.SKILL_DESCRIPTION_HANDLER,
                           skill_description_2)
        json_response = self.get_json(url_2)
        self.assertEqual(json_response['skill_description_exists'], False)
예제 #12
0
    def test_validate_score_category_for_question_suggestion(self):
        rubrics = [
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[0],
                                ['Explanation 1']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[1],
                                ['Explanation 2']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[2],
                                ['Explanation 3'])
        ]
        skill = skill_domain.Skill.create_default_skill(
            '0', 'skill_description', rubrics)
        skill_services.save_new_skill(self.owner_id, skill)

        change = {
            'cmd': question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION,
            'question_dict': {
                'question_state_data':
                self._create_valid_question_data('default_state').to_dict(),
                'language_code':
                'en',
                'question_state_data_schema_version':
                (feconf.CURRENT_STATE_SCHEMA_VERSION),
                'linked_skill_ids': ['0'],
                'inapplicable_skill_misconception_ids': ['skillid12345-0']
            },
            'skill_id': '0',
            'skill_difficulty': 0.3,
        }

        score_category = (suggestion_models.SCORE_TYPE_QUESTION +
                          suggestion_models.SCORE_CATEGORY_DELIMITER +
                          'invalid_sub_category')

        thread_id = feedback_services.create_thread('skill',
                                                    '0',
                                                    self.owner_id,
                                                    'description',
                                                    'suggestion',
                                                    has_suggestion=True)

        suggestion_models.GeneralSuggestionModel.create(
            suggestion_models.SUGGESTION_TYPE_ADD_QUESTION,
            suggestion_models.TARGET_TYPE_SKILL, '0', 1,
            suggestion_models.STATUS_ACCEPTED, self.owner_id, self.admin_id,
            change, score_category, thread_id, 'en')
        model_instance = (
            suggestion_models.GeneralSuggestionModel.get_by_id(thread_id))
        expected_output = [
            (u'[u\'failed validation check for score category check of '
             'GeneralSuggestionModel\', [u\'Entity id %s: Score category'
             ' question.invalid_sub_category is invalid\']]') %
            (model_instance.id),
            u'[u\'fully-validated GeneralSuggestionModel\', 1]'
        ]
        self.run_job_and_check_output(expected_output,
                                      sort=True,
                                      literal_eval=False)
예제 #13
0
    def setUp(self):
        super(SkillCommitCmdMigrationOneOffJobTests, self).setUp()

        self.signup(self.ALBERT_EMAIL, self.ALBERT_NAME)
        self.albert_id = self.get_user_id_from_email(self.ALBERT_EMAIL)
        self.set_admins([self.ALBERT_NAME])

        rubrics = [
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[0],
                                ['Explanation 1']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[1],
                                ['Explanation 2']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[2],
                                ['Explanation 3'])
        ]

        skill = skill_domain.Skill.create_default_skill(
            self.SKILL_ID, 'A description', rubrics)
        skill_services.save_new_skill(self.albert_id, skill)

        skill_services.update_skill(self.albert_id, 'skill_id', [
            skill_domain.SkillChange(
                {
                    'cmd': skill_domain.CMD_UPDATE_RUBRICS,
                    'difficulty': constants.SKILL_DIFFICULTIES[0],
                    'explanations': ['New explanation'],
                }),
            skill_domain.SkillChange({
                'cmd': skill_domain.CMD_UPDATE_SKILL_PROPERTY,
                'property_name': 'description',
                'old_value': '',
                'new_value': 'Test description'
            })
        ], 'Changes.')

        self.commit_model_instance_0 = (skill_models.SkillCommitLogEntryModel.
                                        get_by_id('skill-skill_id-1'))
        self.commit_model_instance_1 = (skill_models.SkillCommitLogEntryModel.
                                        get_by_id('skill-skill_id-2'))

        self.metadata_model_instance_0 = (
            skill_models.SkillSnapshotMetadataModel.get_by_id('skill_id-1'))
        self.metadata_model_instance_1 = (
            skill_models.SkillSnapshotMetadataModel.get_by_id('skill_id-2'))

        self.process_and_flush_pending_mapreduce_tasks()
    def post(self):
        description = self.payload.get('description')
        linked_topic_ids = self.payload.get('linked_topic_ids')
        new_skill_id = skill_services.get_new_skill_id()
        if linked_topic_ids is not None:
            topics = topic_fetchers.get_topics_by_ids(linked_topic_ids)
            for topic in topics:
                if topic is None:
                    raise self.InvalidInputException
                topic_services.add_uncategorized_skill(self.user_id, topic.id,
                                                       new_skill_id)

        skill_domain.Skill.require_valid_description(description)

        skill = skill_domain.Skill.create_default_skill(
            new_skill_id, description)
        skill_services.save_new_skill(self.user_id, skill)

        self.render_json({'skillId': new_skill_id})
예제 #15
0
    def post(self):
        description = self.normalized_payload.get('description')
        linked_topic_ids = self.normalized_payload.get('linked_topic_ids')
        explanation_dict = self.normalized_payload.get('explanation_dict')
        rubrics = self.normalized_payload.get('rubrics')
        files = self.normalized_payload.get('files')

        new_skill_id = skill_services.get_new_skill_id()
        if linked_topic_ids is not None:
            topics = topic_fetchers.get_topics_by_ids(linked_topic_ids)
            for topic in topics:
                if topic is None:
                    raise self.InvalidInputException
                topic_services.add_uncategorized_skill(self.user_id, topic.id,
                                                       new_skill_id)

        if skill_services.does_skill_with_description_exist(description):
            raise self.InvalidInputException(
                'Skill description should not be a duplicate.')

        skill = skill_domain.Skill.create_default_skill(
            new_skill_id, description, rubrics)

        skill.update_explanation(explanation_dict)

        image_filenames = skill_services.get_image_filenames_from_skill(skill)

        skill_services.save_new_skill(self.user_id, skill)

        for filename in image_filenames:
            base64_image = files.get(filename)
            bytes_image = base64.decodebytes(base64_image.encode('utf-8'))
            file_format = (
                image_validation_services.validate_image_and_filename(
                    bytes_image, filename))
            image_is_compressible = (file_format
                                     in feconf.COMPRESSIBLE_IMAGE_FORMATS)
            fs_services.save_original_and_compressed_versions_of_image(
                filename, feconf.ENTITY_TYPE_SKILL, skill.id, bytes_image,
                'image', image_is_compressible)

        self.render_json({'skillId': new_skill_id})
    def test_job_when_skills_do_not_have_math_rich_text_components(self):
        rubrics = [
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[0], ['<p>Explanation 1</p>']),
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[1], ['<p>Explanation 2</p>']),
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[2], ['<p>Explanation 3</p>'])]

        skill = skill_domain.Skill.create_default_skill(
            'valid_skill', 'A description', rubrics)
        skill_services.save_new_skill(self.albert_id, skill)
        job_id = (
            skill_jobs_one_off.SkillMathRteAuditOneOffJob.create_new())
        skill_jobs_one_off.SkillMathRteAuditOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        output = skill_jobs_one_off.SkillMathRteAuditOneOffJob.get_output(
            job_id)
        self.assertEqual(output, [])
예제 #17
0
    def test_skill_description_handler_when_duplicate(self):
        self.login(self.CURRICULUM_ADMIN_EMAIL)
        json_response = self.get_json(self.url)
        self.assertEqual(json_response['skill_description_exists'], False)

        # Publish a skill.
        new_skill_id = skill_services.get_new_skill_id()
        rubrics = [
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[0],
                                ['Explanation 1']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[1],
                                ['Explanation 2']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[2],
                                ['Explanation 3'])
        ]
        skill = skill_domain.Skill.create_default_skill(
            new_skill_id, self.skill_description, rubrics)
        skill_services.save_new_skill(self.admin_id, skill)

        # Skill description exists since we've already published it.
        json_response = self.get_json(self.url)
        self.assertEqual(json_response['skill_description_exists'], True)
예제 #18
0
    def post(self):
        description = self.payload.get('description')
        linked_topic_ids = self.payload.get('linked_topic_ids')
        explanation_dict = self.payload.get('explanation_dict')
        rubrics = self.payload.get('rubrics')

        if not isinstance(rubrics, list):
            raise self.InvalidInputException('Rubrics should be a list.')

        if not isinstance(explanation_dict, dict):
            raise self.InvalidInputException('Explanation should be a dict.')

        try:
            subtitled_html = (
                state_domain.SubtitledHtml.from_dict(explanation_dict))
            subtitled_html.validate()
        except Exception as e:
            raise self.InvalidInputException(
                'Explanation should be a valid SubtitledHtml dict.') from e

        rubrics = [skill_domain.Rubric.from_dict(rubric) for rubric in rubrics]
        new_skill_id = skill_services.get_new_skill_id()
        if linked_topic_ids is not None:
            topics = topic_fetchers.get_topics_by_ids(linked_topic_ids)
            for topic in topics:
                if topic is None:
                    raise self.InvalidInputException
                topic_services.add_uncategorized_skill(self.user_id, topic.id,
                                                       new_skill_id)

        skill_domain.Skill.require_valid_description(description)

        if skill_services.does_skill_with_description_exist(description):
            raise self.InvalidInputException(
                'Skill description should not be a duplicate.')

        skill = skill_domain.Skill.create_default_skill(
            new_skill_id, description, rubrics)

        skill.update_explanation(
            state_domain.SubtitledHtml.from_dict(explanation_dict))

        image_filenames = skill_services.get_image_filenames_from_skill(skill)

        skill_services.save_new_skill(self.user_id, skill)

        image_validation_error_message_suffix = (
            'Please go to oppia.org/skill_editor/%s to edit '
            'the image.' % skill.id)
        for filename in image_filenames:
            image = self.request.get(filename)
            if not image:
                logging.exception(
                    'Image not provided for file with name %s when the skill '
                    'with id %s was created.' % (filename, skill.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_SKILL, skill.id, image, 'image',
                image_is_compressible)

        self.render_json({'skillId': new_skill_id})
예제 #19
0
파일: admin.py 프로젝트: tyleri/oppia
    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.')
예제 #20
0
파일: admin.py 프로젝트: pengcheng95/oppia
    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.')
예제 #21
0
    def test_interactions_are_reported_correctly(self):
        topic_id_1 = 'topicid1'
        topic_id_2 = 'topicid2'
        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()

        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>')

        topic_1 = topic_domain.Topic.create_default_topic(
            topic_id_1, 'Dummy Topic 1', 'dummy-topic-one', 'description')
        topic_2 = topic_domain.Topic.create_default_topic(
            topic_id_2, 'Empty Topic', 'empty-topic', '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)

        # These explorations were chosen since they pass the validations
        # for published stories.
        exp_services.load_demo('15')
        exp_services.load_demo('25')
        exp_services.load_demo('13')
        exp_services.update_exploration(
            self.user_id, '15', [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                    'property_name': 'correctness_feedback_enabled',
                    'new_value': True
                })
            ], 'Changed correctness_feedback_enabled.')
        exp_services.update_exploration(
            self.user_id, '25', [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                    'property_name': 'correctness_feedback_enabled',
                    'new_value': True
                })
            ], 'Changed correctness_feedback_enabled.')
        exp_services.update_exploration(
            self.user_id, '13', [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                    'property_name': 'correctness_feedback_enabled',
                    'new_value': True
                })
            ], 'Changed correctness_feedback_enabled.')

        story = story_domain.Story.create_default_story(
            story_id, 'Help Jaime win the Arcade', 'Description', topic_id_1,
            'help-jamie-win-arcade')

        story_node_dicts = [{
            'exp_id': '15',
            'title': 'What are the place values?',
            'description': 'a'
        }, {
            'exp_id': '25',
            'title': 'Finding the value of a number',
            'description': 'b'
        }, {
            'exp_id': '13',
            'title': 'Comparing Numbers',
            'description': 'c'
        }]

        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)

        topic_services.publish_story(topic_id_1, story_id, self.user_id)
        job_id = (topic_jobs_one_off.InteractionsInStoriesAuditOneOffJob.
                  create_new())
        topic_jobs_one_off.InteractionsInStoriesAuditOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_mapreduce_tasks()

        output = (topic_jobs_one_off.InteractionsInStoriesAuditOneOffJob.
                  get_output(job_id))

        expected = [[
            '%s (%s)' % ('Dummy Topic 1', topic_id_1),
            [
                u'[u\'EndExploration\', u\'ImageClickInput\', u\'Continue\', '
                u'u\'MultipleChoiceInput\', u\'TextInput\']'
            ]
        ], ['%s (%s)' % ('Empty Topic', topic_id_2), [u'[]']]]
        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
예제 #22
0
    def test_job_removes_deleted_uncategorized_skill_ids(self):
        """Tests that the RemoveDeletedSkillsFromTopicOneOffJob job removes
        deleted uncategorized skills ids from the topic.
        """
        valid_skill_1 = skill_domain.Skill.create_default_skill(
            'valid_skill_1', 'A description', self.rubrics)
        valid_skill_2 = skill_domain.Skill.create_default_skill(
            'valid_skill_2', 'A description', self.rubrics)
        valid_skill_3 = skill_domain.Skill.create_default_skill(
            'valid_skill_3', 'A description', self.rubrics)
        skill_services.save_new_skill(self.albert_id, valid_skill_1)
        skill_services.save_new_skill(self.albert_id, valid_skill_2)
        skill_services.save_new_skill(self.albert_id, valid_skill_3)
        # Create a new topic that should not be affected by the
        # job.
        topic = topic_domain.Topic.create_default_topic(
            self.TOPIC_ID, 'A name', 'abbrev', 'description')
        topic.add_subtopic(1, 'A subtitle')
        topic.add_uncategorized_skill_id('valid_skill_1')
        topic.add_uncategorized_skill_id('valid_skill_2')
        topic.add_uncategorized_skill_id('valid_skill_3')
        topic.add_uncategorized_skill_id('deleted_skill_1')
        topic.add_uncategorized_skill_id('deleted_skill_2')
        topic.add_uncategorized_skill_id('deleted_skill_3')
        topic.move_skill_id_to_subtopic(None, 1, 'valid_skill_3')
        topic.move_skill_id_to_subtopic(None, 1, 'deleted_skill_3')
        topic_services.save_new_topic(self.albert_id, topic)
        # Pre-assert that all skills are added correctly.
        self.assertEqual(
            set(topic.uncategorized_skill_ids),
            set([
                'valid_skill_1', 'valid_skill_2', 'deleted_skill_1',
                'deleted_skill_2'
            ]))
        self.assertEqual(set(topic.subtopics[0].skill_ids),
                         set(['valid_skill_3', 'deleted_skill_3']))

        # Start RemoveDeletedSkillsFromTopicOneOffJob.
        job_id = (topic_jobs_one_off.RemoveDeletedSkillsFromTopicOneOffJob.
                  create_new())
        topic_jobs_one_off.RemoveDeletedSkillsFromTopicOneOffJob.enqueue(
            job_id)
        self.process_and_flush_pending_mapreduce_tasks()

        # Assert that only valid skills remain after
        # RemoveDeletedSkillsFromTopicOneOffJob.
        updated_topic = topic_fetchers.get_topic_by_id(self.TOPIC_ID)
        self.assertEqual(updated_topic.uncategorized_skill_ids,
                         ['valid_skill_1', 'valid_skill_2'])
        self.assertEqual(updated_topic.subtopics[0].skill_ids,
                         ['valid_skill_3'])
        output = (topic_jobs_one_off.RemoveDeletedSkillsFromTopicOneOffJob.
                  get_output(job_id))
        expected = [[
            u'Skill IDs deleted for topic topic_id:',
            [
                u'[u\'deleted_skill_1\', u\'deleted_skill_2\','
                ' u\'deleted_skill_3\']'
            ]
        ], [u'topic_processed', [u'Processed 1 topics.']]]

        self.assertEqual(expected, [ast.literal_eval(x) for x in output])
    def test_job_when_skills_have_math_rich_text_components_with_svgs(self):
        valid_html_1 = (
            '<oppia-noninteractive-math math_content-with-value="{&amp;q'
            'uot;raw_latex&amp;quot;: &amp;quot;(x - a_1)(x - a_2)(x - a'
            '_3)...(x - a_n-1)(x - a_n)&amp;quot;, &amp;quot;svg_filenam'
            'e&amp;quot;: &amp;quot;file1.svg&amp;quot;}"></oppia-noninte'
            'ractive-math>'
        )
        valid_html_2 = (
            '<oppia-noninteractive-math math_content-with-value="{&amp;'
            'quot;raw_latex&amp;quot;: &amp;quot;+,+,+,+&amp;quot;, &amp;'
            'quot;svg_filename&amp;quot;: &amp;quot;file2.svg&amp;quot;}">'
            '</oppia-noninteractive-math>'
        )

        example_1 = skill_domain.WorkedExample(
            state_domain.SubtitledHtml('2', valid_html_1),
            state_domain.SubtitledHtml('3', '<p>Example Explanation 1</p>')
        )
        skill_contents1 = skill_domain.SkillContents(
            state_domain.SubtitledHtml(
                '1', '<p>Explanation</p>'), [example_1],
            state_domain.RecordedVoiceovers.from_dict({
                'voiceovers_mapping': {
                    '1': {}, '2': {}, '3': {}
                }
            }),
            state_domain.WrittenTranslations.from_dict({
                'translations_mapping': {
                    '1': {}, '2': {}, '3': {}
                }
            })
        )
        misconceptions1 = [skill_domain.Misconception(
            0, 'name', '<p>misconception html1</p>',
            '<p>misconception html2</p>', True)]
        rubrics1 = [
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[0], [valid_html_2]),
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[1], ['<p>Explanation 2</p>']),
            skill_domain.Rubric(
                constants.SKILL_DIFFICULTIES[2], ['<p>Explanation 3</p>'])]
        skill1 = skill_domain.Skill(
            'skill_id1', 'Description', misconceptions1, rubrics1,
            skill_contents1, feconf.CURRENT_MISCONCEPTIONS_SCHEMA_VERSION,
            feconf.CURRENT_RUBRIC_SCHEMA_VERSION,
            feconf.CURRENT_SKILL_CONTENTS_SCHEMA_VERSION, 'en', 0, 1,
            None, False, ['skill_id_2']
        )
        skill_services.save_new_skill(self.albert_id, skill1)
        job_id = (
            skill_jobs_one_off.SkillMathRteAuditOneOffJob.create_new())
        skill_jobs_one_off.SkillMathRteAuditOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        output = skill_jobs_one_off.SkillMathRteAuditOneOffJob.get_output(
            job_id)
        overall_result = ast.literal_eval(output[0])
        expected_skill1_info = {
            'skill_id': 'skill_id1',
            'latex_strings_with_svg': [
                '(x - a_1)(x - a_2)(x - a_3)...(x - a_n-1)(x - a_n)', '+,+,+,+']
        }
        skill_latex_info = overall_result[1]
        self.assertEqual(skill_latex_info[0], expected_skill1_info)
예제 #24
0
    def setUp(self):
        super(SkillSummaryModelValidatorTests, self).setUp()

        self.signup(self.OWNER_EMAIL, self.OWNER_USERNAME)
        self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME)

        self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)
        self.admin_id = self.get_user_id_from_email(self.ADMIN_EMAIL)
        self.set_admins([self.ADMIN_USERNAME])
        rubrics = [
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[0],
                                ['Explanation 1']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[1],
                                ['Explanation 2']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[2],
                                ['Explanation 3'])
        ]
        language_codes = ['ar', 'en', 'en']
        skills = [
            skill_domain.Skill.create_default_skill('%s' % i,
                                                    'description %d' % i,
                                                    rubrics)
            for i in python_utils.RANGE(3)
        ]

        example_1 = skill_domain.WorkedExample(
            state_domain.SubtitledHtml('2', '<p>Example Question 1</p>'),
            state_domain.SubtitledHtml('3', '<p>Example Explanation 1</p>'))
        skill_contents = skill_domain.SkillContents(
            state_domain.SubtitledHtml('1', '<p>Explanation</p>'), [example_1],
            state_domain.RecordedVoiceovers.from_dict(
                {'voiceovers_mapping': {
                    '1': {},
                    '2': {},
                    '3': {}
                }}),
            state_domain.WrittenTranslations.from_dict(
                {'translations_mapping': {
                    '1': {},
                    '2': {},
                    '3': {}
                }}))

        misconception_dict = {
            'id': 0,
            'name': 'name',
            'notes': '<p>notes</p>',
            'feedback': '<p>default_feedback</p>',
            'must_be_addressed': True
        }

        misconception = skill_domain.Misconception.from_dict(
            misconception_dict)

        for index, skill in enumerate(skills):
            skill.language_code = language_codes[index]
            skill.skill_contents = skill_contents
            skill.add_misconception(misconception)
            skill_services.save_new_skill(self.owner_id, skill)

        self.model_instance_0 = skill_models.SkillSummaryModel.get_by_id('0')
        self.model_instance_1 = skill_models.SkillSummaryModel.get_by_id('1')
        self.model_instance_2 = skill_models.SkillSummaryModel.get_by_id('2')

        self.job_class = (
            prod_validation_jobs_one_off.SkillSummaryModelAuditOneOffJob)
예제 #25
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({})
예제 #26
0
    def setUp(self):
        super(SubtopicPageCommitLogEntryModelValidatorTests, self).setUp()

        self.signup(self.OWNER_EMAIL, self.OWNER_USERNAME)
        self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)

        self.signup(USER_EMAIL, USER_NAME)
        self.user_id = self.get_user_id_from_email(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])

        topics = [
            topic_domain.Topic.create_default_topic('%s' % i, 'topic%s' % i,
                                                    'abbrev-%s' % chr(120 + i),
                                                    'description%s' % i)
            for i in python_utils.RANGE(3)
        ]
        rubrics = [
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[0],
                                ['Explanation 1']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[1],
                                ['Explanation 2']),
            skill_domain.Rubric(constants.SKILL_DIFFICULTIES[2],
                                ['Explanation 3'])
        ]
        skills = [
            skill_domain.Skill.create_default_skill('%s' % i, 'skill%s' % i,
                                                    rubrics)
            for i in python_utils.RANGE(9)
        ]

        for skill in skills:
            skill_services.save_new_skill(self.owner_id, skill)

        stories = [
            story_domain.Story.create_default_story(
                '%s' % i, 'title %d', 'description %d' % i,
                '%s' % (python_utils.divide(i, 2)), 'title-%s' % chr(97 + i))
            for i in python_utils.RANGE(6)
        ]

        for story in stories:
            story_services.save_new_story(self.owner_id, story)

        language_codes = ['ar', 'en', 'en']
        for index, topic in enumerate(topics):
            topic.language_code = language_codes[index]
            topic.add_additional_story('%s' % (index * 2))
            topic.add_canonical_story('%s' % (index * 2 + 1))
            topic.add_uncategorized_skill_id('%s' % (index * 3))
            topic.add_uncategorized_skill_id('%s' % (index * 3 + 1))
            topic.add_uncategorized_skill_id('%s' % (index * 3 + 2))
            topic_services.save_new_topic(self.owner_id, topic)
            if index == 0:
                committer_id = self.user_id
            else:
                committer_id = self.owner_id
            topic_services.update_topic_and_subtopic_pages(
                committer_id, '%s' % index, [
                    topic_domain.TopicChange({
                        'cmd': 'add_subtopic',
                        'title': 'subtopic1',
                        'subtopic_id': 1
                    }),
                    topic_domain.TopicChange({
                        'cmd': 'move_skill_id_to_subtopic',
                        'old_subtopic_id': None,
                        'new_subtopic_id': 1,
                        'skill_id': '%s' % (index * 3)
                    }),
                    topic_domain.TopicChange({
                        'cmd': 'move_skill_id_to_subtopic',
                        'old_subtopic_id': None,
                        'new_subtopic_id': 1,
                        'skill_id': '%s' % (index * 3 + 1)
                    })
                ], 'Changes.')

        self.model_instance_0 = (
            subtopic_models.SubtopicPageCommitLogEntryModel.get_by_id(
                'subtopicpage-0-1-1'))
        self.model_instance_1 = (
            subtopic_models.SubtopicPageCommitLogEntryModel.get_by_id(
                'subtopicpage-1-1-1'))
        self.model_instance_2 = (
            subtopic_models.SubtopicPageCommitLogEntryModel.get_by_id(
                'subtopicpage-2-1-1'))

        self.job_class = (prod_validation_jobs_one_off.
                          SubtopicPageCommitLogEntryModelAuditOneOffJob)