def test_get_exploration_opportunity_summaries_by_ids_returns_list_of_objects(self):  # pylint: disable=line-too-long
        output = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                []))

        self.assertEqual(output, [])

        opportunities = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                ['0']))

        self.assertEqual(len(opportunities), 1)
        self.assertIsInstance(opportunities[0],
                              opportunity_domain.ExplorationOpportunitySummary)
        self.assertEqual(opportunities[0].id, '0')
    def test_get_exploration_opportunity_summaries_by_ids(self):
        output = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                []))

        self.assertEqual(output, {})

        opportunities = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                ['0']))

        self.assertEqual(len(opportunities), 1)
        self.assertIsInstance(opportunities['0'],
                              opportunity_domain.ExplorationOpportunitySummary)
        self.assertEqual(opportunities['0'].id, '0')
    def test_get_exploration_opportunity_summaries_by_ids_for_invalid_id(self):
        opportunities = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                ['badID']))

        self.assertEqual(len(opportunities), 1)
        self.assertEqual(opportunities['badID'], None)
Exemple #4
0
def reject_voiceover_application(voiceover_application_id, reviewer_id,
                                 rejection_message):
    """Rejects the voiceover application of given voiceover application id.

    Args:
        voiceover_application_id: str. The is of the voiceover application which
            need to be rejected.
        reviewer_id: str. The user ID of the reviewer.
        rejection_message: str. The plain text message submitted by the
            reviewer while rejecting the application.
    """
    voiceover_application = get_voiceover_application_by_id(
        voiceover_application_id)
    if reviewer_id == voiceover_application.author_id:
        raise Exception('Applicants are not allowed to review their own '
                        'voiceover application.')

    reviewer = user_services.UserActionsInfo(user_id=reviewer_id)

    voiceover_application.reject(reviewer.user_id, rejection_message)
    _save_voiceover_applications([voiceover_application])

    if voiceover_application.target_type == feconf.ENTITY_TYPE_EXPLORATION:
        opportunities = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                [voiceover_application.target_id]))
        email_manager.send_rejected_voiceover_application_email(
            voiceover_application.author_id, opportunities[0].chapter_title,
            voiceover_application.language_code, rejection_message)
def accept_voiceover_application(voiceover_application_id, reviewer_id):
    """Accept the voiceover application of given voiceover application id.

    Args:
        voiceover_application_id: str. The id of the voiceover application which
            need to be accepted.
        reviewer_id: str. The user ID of the reviewer.

    Raises:
        Exception. Reviewer ID is same as the author ID.
    """
    voiceover_application = get_voiceover_application_by_id(
        voiceover_application_id)
    if reviewer_id == voiceover_application.author_id:
        raise Exception(
            'Applicants are not allowed to review their own '
            'voiceover application.')

    reviewer = user_services.get_user_actions_info(reviewer_id)

    voiceover_application.accept(reviewer_id)

    _save_voiceover_applications([voiceover_application])

    if voiceover_application.target_type == feconf.ENTITY_TYPE_EXPLORATION:
        rights_manager.assign_role_for_exploration(
            reviewer, voiceover_application.target_id,
            voiceover_application.author_id, rights_domain.ROLE_VOICE_ARTIST)
        opportunity_services.update_exploration_voiceover_opportunities(
            voiceover_application.target_id,
            voiceover_application.language_code)
        opportunities = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids([
                voiceover_application.target_id]))
        email_manager.send_accepted_voiceover_application_email(
            voiceover_application.author_id,
            opportunities[voiceover_application.target_id].chapter_title,
            voiceover_application.language_code)
    # TODO(#7969): Add notification to the user's dashboard for the accepted
    # voiceover application.

    voiceover_application_models = (
        suggestion_models.GeneralVoiceoverApplicationModel
        .get_voiceover_applications(
            voiceover_application.target_type, voiceover_application.target_id,
            voiceover_application.language_code))
    rejected_voiceover_applications = []
    for model in voiceover_application_models:
        voiceover_application = _get_voiceover_application_from_model(
            model)
        if not voiceover_application.is_handled:
            voiceover_application.reject(
                reviewer_id, 'We have to reject your application as another '
                'application for the same opportunity got accepted.')
            rejected_voiceover_applications.append(voiceover_application)

    _save_voiceover_applications(rejected_voiceover_applications)
    def map(item):
        """Implements the map function (generator). Computes word counts of
        translations suggestions and outputs suggestion metadata.

        Args:
            item: GeneralSuggestionModel. An instance of GeneralSuggestionModel.

        Yields:
            tuple(key, recent_activity_commits). Where:
                key: str. The entity ID of the corresponding
                    TranslationContributionStatsModel.
                dict. Has the keys:
                    suggestion_status: str. The translation suggestion status.
                    edited_by_reviewer: bool. Whether the translation suggestion
                        was edited by a reviewer.
                    content_word_count: int. The word count of the translation
                        suggestion content HTML.
                    last_updated_date: date. The last updated date of the
                        translation suggestion.
        """
        if item.suggestion_type != feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT:
            return

        suggestion = suggestion_services.get_suggestion_from_model(item)

        # Try to extract the topic ID from the corresponding exploration
        # opportunity.
        topic_id = ''
        exp_id = suggestion.target_id
        exp_opportunity_dict = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                [exp_id]))
        exp_opportunity = exp_opportunity_dict[exp_id]
        if exp_opportunity is not None:
            topic_id = exp_opportunity.topic_id

        # Count the number of words in the original content, ignoring any HTML
        # tags and attributes.
        content_plain_text = html_cleaner.strip_html_tags(
            suggestion.change.content_html)
        content_word_count = len(content_plain_text.split())

        key = suggestion_models.TranslationContributionStatsModel.generate_id(
            suggestion.language_code, suggestion.author_id, topic_id)
        translation_contribution_stats_dict = {
            'suggestion_status': suggestion.status,
            'edited_by_reviewer': suggestion.edited_by_reviewer,
            'content_word_count': content_word_count,
            'last_updated_date': suggestion.last_updated.date().isoformat()
        }
        yield (key, translation_contribution_stats_dict)
Exemple #7
0
def _get_target_id_to_exploration_opportunity_dict(suggestions):
    """Returns a dict of target_id to exploration opportunity summary dict.

    Args:
        suggestions: list(BaseSuggestion). A list of suggestions to retrieve
            opportunity dicts.

    Returns:
        dict. Dict mapping target_id to corresponding exploration opportunity
        summary dict.
    """
    target_ids = set([s.target_id for s in suggestions])
    opportunities = (
        opportunity_services.get_exploration_opportunity_summaries_by_ids(
            list(target_ids)))
    return {opp.id: opp.to_dict() for opp in opportunities}
    def test_get_exploration_opportunity_summary_from_model_populates_new_lang(
            self):
        observed_log_messages = []

        def _mock_logging_function(msg, *args):
            """Mocks logging.info()."""
            observed_log_messages.append(msg % args)

        opportunities = (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                ['0']))
        self.assertEqual(len(opportunities), 1)

        opportunity = opportunities[0]

        self.assertFalse(
            'new_lang' in opportunity.incomplete_translation_language_codes)

        mock_supported_languages = constants.SUPPORTED_AUDIO_LANGUAGES + [{
            'id':
            'new_lang',
            'description':
            'New language',
            'relatedLanguages': ['new_lang']
        }]

        self.assertEqual(len(observed_log_messages), 0)

        with self.swap(logging, 'info', _mock_logging_function), self.swap(
                constants, 'SUPPORTED_AUDIO_LANGUAGES',
                mock_supported_languages):
            opportunities = (opportunity_services.
                             get_exploration_opportunity_summaries_by_ids(
                                 ['0']))
            self.assertEqual(len(opportunities), 1)

            opportunity = opportunities[0]

            self.assertTrue('new_lang' in
                            opportunity.incomplete_translation_language_codes)
            self.assertEqual(len(observed_log_messages), 1)
            self.assertEqual(
                observed_log_messages[0],
                'Missing language codes [u\'new_lang\'] in exploration '
                'opportunity model with id 0')
Exemple #9
0
def _get_target_id_to_exploration_opportunity_dict(suggestions):
    """Returns a dict of target_id to exploration opportunity summary dict.

    Args:
        suggestions: list(BaseSuggestion). A list of suggestions to retrieve
            opportunity dicts.

    Returns:
        dict. Dict mapping target_id to corresponding exploration opportunity
        summary dict.
    """
    target_ids = set([s.target_id for s in suggestions])
    opportunity_id_to_opportunity_dict = {
        opp_id: (opp.to_dict() if opp is not None else None)
        for opp_id, opp in (
            opportunity_services.get_exploration_opportunity_summaries_by_ids(
                list(target_ids)).items())
    }
    return opportunity_id_to_opportunity_dict
Exemple #10
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())