예제 #1
0
    def get(self, topic_name):

        # Topic cannot be None as an exception will be thrown from its decorator
        # if so.
        topic = topic_fetchers.get_topic_by_name(topic_name)
        selected_subtopic_ids = (
            self.normalized_request.get('selected_subtopic_ids'))

        selected_skill_ids = []
        for subtopic in topic.subtopics:
            # An error is not thrown here, since it's fine to just ignore the
            # passed in subtopic IDs, if they don't exist, which would be the
            # case if the creator deletes subtopics after the learner has
            # loaded the topic viewer page.
            if subtopic.id in selected_subtopic_ids:
                selected_skill_ids.extend(subtopic.skill_ids)
        try:
            skills = skill_fetchers.get_multi_skills(selected_skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)
        skill_ids_to_descriptions_map = {}
        for skill in skills:
            skill_ids_to_descriptions_map[skill.id] = skill.description

        self.values.update({
            'topic_name':
            topic.name,
            'skill_ids_to_descriptions_map':
            skill_ids_to_descriptions_map
        })
        self.render_json(self.values)
예제 #2
0
    def get(self, topic_name):

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        # Topic cannot be None as an exception will be thrown from its decorator
        # if so.
        topic = topic_fetchers.get_topic_by_name(topic_name)
        comma_separated_subtopic_ids = self.request.get('selected_subtopic_ids')
        selected_subtopic_ids = comma_separated_subtopic_ids.split(',')

        selected_skill_ids = []
        for subtopic in topic.subtopics:
            # An error is not thrown here, since it's fine to just ignore the
            # passed in subtopic IDs, if they don't exist, which would be the
            # case if the creator deletes subtopics after the learner has
            # loaded the topic viewer page.
            if python_utils.UNICODE(subtopic.id) in selected_subtopic_ids:
                selected_skill_ids.extend(subtopic.skill_ids)
        try:
            skills = skill_fetchers.get_multi_skills(selected_skill_ids)
        except Exception as e:
            raise self.PageNotFoundException(e)
        skill_ids_to_descriptions_map = {}
        for skill in skills:
            skill_ids_to_descriptions_map[skill.id] = skill.description

        self.values.update({
            'topic_name': topic.name,
            'skill_ids_to_descriptions_map': skill_ids_to_descriptions_map
        })
        self.render_json(self.values)
예제 #3
0
    def get(self, topic_name, subtopic_id):
        """Handles GET requests.

        Args:
            topic_name: str. The name of the topic that the subtopic is present
                in.
            subtopic_id: str. The id of the subtopic, which is an integer in
                string form.
        """
        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        subtopic_id = int(subtopic_id)
        topic = topic_fetchers.get_topic_by_name(topic_name)
        next_subtopic_dict = None
        for index, subtopic in enumerate(topic.subtopics):
            if subtopic.id == subtopic_id:
                subtopic_title = subtopic.title
                if index != len(topic.subtopics) - 1:
                    next_subtopic_dict = topic.subtopics[index + 1].to_dict()
                break
        subtopic_page_contents = (
            subtopic_page_services.get_subtopic_page_contents_by_id(
                topic.id, subtopic_id))
        subtopic_page_contents_dict = subtopic_page_contents.to_dict()

        self.values.update({
            'topic_id': topic.id,
            'page_contents': subtopic_page_contents_dict,
            'subtopic_title': subtopic_title,
            'next_subtopic_dict': next_subtopic_dict
        })
        self.render_json(self.values)
예제 #4
0
    def get(self, topic_name):
        """Handles GET requests."""

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

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

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

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

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

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

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

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

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'canonical_story_dicts': canonical_story_dicts,
            'additional_story_dicts': additional_story_dicts,
            'uncategorized_skill_ids': uncategorized_skill_ids,
            'subtopics': subtopics,
            'degrees_of_mastery': degrees_of_mastery,
            'skill_descriptions': skill_descriptions
        })
        self.render_json(self.values)
예제 #5
0
    def test_initialize_topic_is_published(self):
        self.post_json('/initialize_android_test_data', {},
                       use_payload=False,
                       csrf_token=None)
        self.assertTrue(
            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)

        self.assertTrue(topic_rights.topic_is_published)
예제 #6
0
 def test_initialize_twice_raises_unpublished_topic_exception(self):
     self.post_json('/initialize_android_test_data', {},
                    use_payload=False,
                    csrf_token=None)
     topic = topic_fetchers.get_topic_by_name('Android test')
     topic_services.unpublish_topic(topic.id, feconf.SYSTEM_COMMITTER_ID)
     response = self.post_json('/initialize_android_test_data', {},
                               use_payload=False,
                               csrf_token=None,
                               expected_status_int=400)
     self.assertEqual(response['error'],
                      'The topic exists but is not published.')
예제 #7
0
    def get(self, page_context, page_identifier, asset_type, encoded_filename):
        """Returns an asset file.

        Args:
            page_context: str. The context of the page where the asset is
                required.
            page_identifier: str. The unique identifier for the particular
                context. Valid page_context: page_identifier pairs:
                exploration: exp_id
                story: story_id
                topic: topic_id
                skill: skill_id
                subtopic: topic_name of the topic that it is part of.
            asset_type: str. Type of the asset, either image or audio.
            encoded_filename: str. The asset filename. This
              string is encoded in the frontend using encodeURIComponent().
        """
        if not constants.DEV_MODE:
            raise self.PageNotFoundException

        try:
            filename = python_utils.urllib_unquote(encoded_filename)
            file_format = filename[(filename.rfind('.') + 1):]

            # If the following is not cast to str, an error occurs in the wsgi
            # library because unicode gets used.
            self.response.headers[
                'Content-Type'] = python_utils.convert_to_bytes(
                    '%s/%s' % (asset_type, file_format))

            if page_context == feconf.ENTITY_TYPE_SUBTOPIC:
                entity_type = feconf.ENTITY_TYPE_TOPIC
                topic = topic_fetchers.get_topic_by_name(page_identifier)
                entity_id = topic.id
            elif (page_context == feconf.ENTITY_TYPE_EXPLORATION
                  or page_context == feconf.ENTITY_TYPE_SKILL
                  or page_context == feconf.ENTITY_TYPE_TOPIC
                  or page_context == feconf.ENTITY_TYPE_STORY):
                entity_type = page_context
                entity_id = page_identifier
            else:
                raise self.InvalidInputException

            fs = fs_domain.AbstractFileSystem(
                fs_domain.DatastoreBackedFileSystem(entity_type, entity_id))
            raw = fs.get('%s/%s' % (asset_type, filename))

            self.response.cache_control.no_cache = None
            self.response.cache_control.public = True
            self.response.cache_control.max_age = 600
            self.response.write(raw)
        except:
            raise self.PageNotFoundException
예제 #8
0
    def get(self, topic_name):

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        # Topic cannot be None as an exception will be thrown from its decorator
        # if so.
        topic = topic_fetchers.get_topic_by_name(topic_name)
        try:
            skills = skill_services.get_multi_skills(topic.get_all_skill_ids())
        except Exception, e:
            raise self.PageNotFoundException(e)
예제 #9
0
 def test_initialize_structure_thumbnails_exist(self):
     # To validate the thumbnails for topics ans stories can be fetched
     # using AssetsDevHandler.
     self.post_json('/initialize_android_test_data', {},
                    use_payload=False,
                    csrf_token=None)
     topic = topic_fetchers.get_topic_by_name('Android test')
     story = story_fetchers.get_story_by_url_fragment(
         'android-end-to-end-testing')
     self.get_custom_response(
         '/assetsdevhandler/topic/%s/assets/thumbnail/test_svg.svg' %
         topic.id, 'image/svg+xml')
     self.get_custom_response(
         '/assetsdevhandler/story/%s/assets/thumbnail/test_svg.svg' %
         story.id, 'image/svg+xml')
예제 #10
0
def does_topic_with_name_exist(topic_name):
    """Checks if the topic with provided name exists.

    Args:
        topic_name: str. The topic name.

    Returns:
        bool. Whether the the topic name exists.

    Raises:
        Exception. Topic name is not a string.
    """
    if not isinstance(topic_name, python_utils.BASESTRING):
        raise utils.ValidationError('Name should be a string.')
    existing_topic = topic_fetchers.get_topic_by_name(topic_name)
    return existing_topic is not None
예제 #11
0
 def test_initialize_structures_are_valid(self):
     self.post_json('/initialize_android_test_data', {},
                    use_payload=False,
                    csrf_token=None)
     exp_id = '26'
     topic = topic_fetchers.get_topic_by_name('Android test')
     exploration = exp_fetchers.get_exploration_by_id(exp_id)
     story = story_fetchers.get_story_by_url_fragment(
         'android-end-to-end-testing')
     skill = skill_fetchers.get_skill_by_description(
         'Dummy Skill for Android')
     skill.validate()
     story.validate()
     topic.validate(strict=True)
     exploration.validate(strict=True)
     for node in story.story_contents.nodes:
         self.assertEqual(node.exploration_id, exp_id)
예제 #12
0
    def get(self, target_type, suggestion_type):
        """Handles GET requests.

        Args:
            target_type: str. The type of the suggestion target.
            suggestion_type: str. The type of the suggestion.
        """
        self._require_valid_suggestion_and_target_types(
            target_type, suggestion_type)
        limit = self.normalized_request.get('limit')
        offset = self.normalized_request.get('offset')
        topic_name = self.normalized_request.get('topic_name')

        opportunity_summary_exp_ids_specific_to_topic = None
        if (topic_name
                is not None) and (topic_name != feconf.ALL_LITERAL_CONSTANT):
            topic = topic_fetchers.get_topic_by_name(topic_name)
            if topic is None:
                raise self.InvalidInputException(
                    'The supplied input topic: %s is not valid' % topic_name)

            exploration_opportunity_summaries = (
                opportunity_services.
                get_exploration_opportunity_summaries_by_topic_id(topic.id))

            opportunity_summary_exp_ids_specific_to_topic = [
                opportunity.id
                for opportunity in exploration_opportunity_summaries
            ]

        suggestions = []
        next_offset = 0
        if suggestion_type == feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT:
            suggestions, next_offset = (
                suggestion_services.
                get_reviewable_translation_suggestions_by_offset(
                    self.user_id,
                    opportunity_summary_exp_ids_specific_to_topic, limit,
                    offset))
        elif suggestion_type == feconf.SUGGESTION_TYPE_ADD_QUESTION:
            suggestions, next_offset = (
                suggestion_services.
                get_reviewable_question_suggestions_by_offset(
                    self.user_id, limit, offset))
        self._render_suggestions(target_type, suggestions, next_offset)
예제 #13
0
    def get(self, topic_name):
        """Handles GET requests."""

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

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

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

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

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

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

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'canonical_story_dicts': canonical_story_dicts,
            'additional_story_dicts': additional_story_dicts,
            'uncategorized_skill_ids': uncategorized_skill_ids,
            'subtopics': subtopics
        })
        self.render_json(self.values)
예제 #14
0
    def get(self, topic_name):

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

        # Topic cannot be None as an exception will be thrown from its decorator
        # if so.
        topic = topic_fetchers.get_topic_by_name(topic_name)
        try:
            skills = skill_services.get_multi_skills(topic.get_all_skill_ids())
        except Exception as e:
            raise self.PageNotFoundException(e)
        skill_descriptions = {}
        for skill in skills:
            skill_descriptions[skill.id] = skill.description

        self.values.update({
            'topic_name': topic.name,
            'skill_descriptions': skill_descriptions
        })
        self.render_json(self.values)
예제 #15
0
def save_new_topic(committer_id, topic):
    """Saves a new topic.

    Args:
        committer_id: str. ID of the committer.
        topic: Topic. Topic to be saved.

    Raises:
        Exception. Topic with same name already exists.
    """
    existing_topic = topic_fetchers.get_topic_by_name(topic.name)
    if existing_topic is not None:
        raise Exception('Topic with name \'%s\' already exists' % topic.name)

    commit_message = ('New topic created with name \'%s\'.' % topic.name)
    _create_topic(committer_id, topic, commit_message, [
        topic_domain.TopicChange({
            'cmd': topic_domain.CMD_CREATE_NEW,
            'name': topic.name
        })
    ])
예제 #16
0
    def get(self, topic_name, subtopic_id):
        """Handles GET requests.

        Args:
            topic_name: str. The name of the topic that the subtopic is present
                in.
            subtopic_id: str. The id of the subtopic, which is an integer in
                string form.
        """

        subtopic_id = int(subtopic_id)
        topic = topic_fetchers.get_topic_by_name(topic_name)
        next_subtopic_dict = None
        prev_subtopic_dict = None
        for index, subtopic in enumerate(topic.subtopics):
            if subtopic.id == subtopic_id:
                subtopic_title = subtopic.title
                if index != len(topic.subtopics) - 1:
                    next_subtopic_dict = topic.subtopics[index + 1].to_dict()
                # Checking greater than 1 here, since otherwise the only
                # subtopic page of the topic would always link to itself at the
                # bottom of the subtopic page which isn't expected.
                elif len(topic.subtopics) > 1:
                    prev_subtopic_dict = topic.subtopics[index - 1].to_dict()
                break
        subtopic_page_contents = (
            subtopic_page_services.get_subtopic_page_contents_by_id(
                topic.id, subtopic_id))
        subtopic_page_contents_dict = subtopic_page_contents.to_dict()

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'page_contents': subtopic_page_contents_dict,
            'subtopic_title': subtopic_title,
            'next_subtopic_dict': next_subtopic_dict,
            'prev_subtopic_dict': prev_subtopic_dict,
        })
        self.render_json(self.values)
예제 #17
0
    def _get_reviewable_exploration_opportunity_summaries(
        self, user_id, topic_name
    ):
        """Returns exploration opportunity summaries that have translation
        suggestions that are reviewable by the supplied user. The result is
        sorted in descending order by topic, story, and story node order.

        Args:
            user_id: str. The user ID of the user for which to filter
                translation suggestions.
            topic_name: str. A topic name for which to filter the exploration
                opportunity summaries. If 'All' is supplied, all available
                exploration opportunity summaries will be returned.

        Returns:
            list(ExplorationOpportunitySummary). A list of the matching
            exploration opportunity summaries.
        """
        # 1. Fetch the eligible topics.
        # 2. Fetch the stories for the topics.
        # 3. Get the reviewable translation suggestion target IDs for the user.
        # 4. Get story exploration nodes in order, filtering for explorations
        # that have in review translation suggestions.
        if topic_name is None:
            topics = topic_fetchers.get_all_topics()
        else:
            topic = topic_fetchers.get_topic_by_name(topic_name)
            if topic is None:
                raise self.InvalidInputException(
                    'The supplied input topic: %s is not valid' % topic_name)
            topics = [topic]
        topic_stories = story_fetchers.get_stories_by_ids([
            reference.story_id
            for topic in topics
            for reference in topic.get_all_story_references()
            if reference.story_is_published])
        topic_exp_ids = [
            node.exploration_id
            for story in topic_stories
            for node in story.story_contents.get_ordered_nodes()
        ]
        in_review_suggestions, _ = (
            suggestion_services
            .get_reviewable_translation_suggestions_by_offset(
                user_id, topic_exp_ids, None, 0))
        # Filter out suggestions that should not be shown to the user.
        # This is defined as a set as we only care about the unique IDs.
        in_review_suggestion_target_ids = {
            suggestion.target_id
            for suggestion in
            suggestion_services.get_suggestions_with_translatable_explorations(
                in_review_suggestions)
        }
        exp_ids = [
            exp_id
            for exp_id in topic_exp_ids
            if exp_id in in_review_suggestion_target_ids
        ]
        return (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                exp_ids).values())
예제 #18
0
    def get(self, topic_name):
        """Handles GET requests."""

        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            raise self.PageNotFoundException

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

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

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

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

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

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

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

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

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'canonical_story_dicts': canonical_story_dicts,
            'additional_story_dicts': additional_story_dicts,
            'uncategorized_skill_ids': uncategorized_skill_ids,
            'subtopics': subtopics,
            'degrees_of_mastery': degrees_of_mastery,
            'skill_descriptions': skill_descriptions
        })
        self.render_json(self.values)
예제 #19
0
 def test_get_topic_by_name(self) -> None:
     topic: Optional[topic_domain.Topic] = (
         topic_fetchers.get_topic_by_name('Name'))
     # Ruling out the possibility of None for mypy type checking.
     assert topic is not None
     self.assertEqual(topic.name, 'Name')
예제 #20
0
    def get(self, topic_name):
        """Handles GET requests."""

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

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

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

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

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

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

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

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

        self.values.update({
            'topic_id': topic.id,
            'topic_name': topic.name,
            'topic_description': topic.description,
            'canonical_story_dicts': canonical_story_dicts,
            'additional_story_dicts': additional_story_dicts,
            'uncategorized_skill_ids': uncategorized_skill_ids,
            'subtopics': subtopics,
            'degrees_of_mastery': degrees_of_mastery,
            'skill_descriptions': skill_descriptions,
            'practice_tab_is_displayed': topic.practice_tab_is_displayed
        })
        self.render_json(self.values)
예제 #21
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({})
예제 #22
0
 def test_get_topic_by_name(self):
     topic = topic_fetchers.get_topic_by_name('Name')
     self.assertEqual(topic.name, 'Name')