コード例 #1
0
def add_dimensions_to_image_tags(exp_id, html_string):
    """Adds dimensions to all oppia-noninteractive-image tags. Removes image
    tags that have no filepath.

    Args:
        exp_id: str. Exploration id.
        html_string: str. HTML string to modify.

    Returns:
        str. Updated HTML string with the dimensions for all
        oppia-noninteractive-image tags.
    """
    soup = bs4.BeautifulSoup(html_string.encode('utf-8'), 'html.parser')
    for image in soup.findAll(name='oppia-noninteractive-image'):
        if (not image.has_attr('filepath-with-value') or
                image['filepath-with-value'] == ''):
            image.decompose()
            continue

        try:
            filename = json.loads(unescape_html(image['filepath-with-value']))
            image['filepath-with-value'] = escape_html(json.dumps(
                get_filename_with_dimensions(filename, exp_id)))
        except Exception:
            logging.exception(
                'Exploration %s failed to load image: %s' %
                (exp_id, image['filepath-with-value'].encode('utf-8')))
            python_utils.reraise_exception()
    return python_utils.UNICODE(soup).replace('<br/>', '<br>')
コード例 #2
0
def apply_change_list(collection_id, change_list):
    """Applies a changelist to a pristine collection and returns the result.

    Args:
        collection_id: str. ID of the given collection.
        change_list: list(dict). A change list to be applied to the given
            collection. Each entry is a dict that represents a CollectionChange
            object.

    Returns:
        Collection. The resulting collection domain object.
    """
    collection = get_collection_by_id(collection_id)

    try:
        changes = [
            collection_domain.CollectionChange(change_dict)
            for change_dict in change_list
        ]
        for change in changes:
            if change.cmd == collection_domain.CMD_ADD_COLLECTION_NODE:
                collection.add_node(change.exploration_id)
            elif change.cmd == collection_domain.CMD_DELETE_COLLECTION_NODE:
                collection.delete_node(change.exploration_id)
            elif change.cmd == collection_domain.CMD_SWAP_COLLECTION_NODES:
                collection.swap_nodes(change.first_index, change.second_index)
            elif change.cmd == collection_domain.CMD_EDIT_COLLECTION_PROPERTY:
                if (change.property_name ==
                        collection_domain.COLLECTION_PROPERTY_TITLE):
                    collection.update_title(change.new_value)
                elif (change.property_name ==
                      collection_domain.COLLECTION_PROPERTY_CATEGORY):
                    collection.update_category(change.new_value)
                elif (change.property_name ==
                      collection_domain.COLLECTION_PROPERTY_OBJECTIVE):
                    collection.update_objective(change.new_value)
                elif (change.property_name ==
                      collection_domain.COLLECTION_PROPERTY_LANGUAGE_CODE):
                    collection.update_language_code(change.new_value)
                elif (change.property_name ==
                      collection_domain.COLLECTION_PROPERTY_TAGS):
                    collection.update_tags(change.new_value)
            elif (change.cmd ==
                  collection_domain.CMD_MIGRATE_SCHEMA_TO_LATEST_VERSION):
                # Loading the collection model from the datastore into an
                # Collection domain object automatically converts it to use the
                # latest schema version. As a result, simply resaving the
                # collection is sufficient to apply the schema migration.
                continue

        return collection

    except Exception as e:
        logging.error(
            '%s %s %s %s' % (
                e.__class__.__name__, e, collection_id, change_list)
        )
        python_utils.reraise_exception()
コード例 #3
0
def add_math_content_to_math_rte_components(html_string):
    """Replaces the attribute raw_latex-with-value in all Math component tags
    with a new attribute math_content-with-value. The new attribute has an
    additional field for storing SVG filenames. The field for SVG filename will
    be an empty string.

    Args:
        html_string: str. HTML string to modify.

    Returns:
        str. Updated HTML string with all Math component tags having the new
        attribute.
    """
    soup = bs4.BeautifulSoup(
        html_string.encode(encoding='utf-8'), 'html.parser')
    for math_tag in soup.findAll(name='oppia-noninteractive-math'):
        if math_tag.has_attr('raw_latex-with-value'):
            try:
                # The raw_latex attribute value should be enclosed in
                # double quotes(&amp;quot;) and should be a valid unicode
                # string.
                raw_latex = (
                    json.loads(unescape_html(math_tag['raw_latex-with-value'])))
                normalized_raw_latex = (
                    objects.UnicodeString.normalize(raw_latex))
            except Exception as e:
                logging.exception(
                    'Invalid raw_latex string found in the math tag : %s' % (
                        python_utils.UNICODE(e)
                    )
                )
                python_utils.reraise_exception()
            math_content_dict = {
                'raw_latex': normalized_raw_latex,
                'svg_filename': ''
            }
            # Normalize and validate the value before adding to the math
            # tag.
            normalized_math_content_dict = (
                objects.MathExpressionContent.normalize(math_content_dict))
            # Add the new attribute math_expression_contents-with-value.
            math_tag['math_content-with-value'] = (
                escape_html(
                    json.dumps(normalized_math_content_dict, sort_keys=True)))
            # Delete the attribute raw_latex-with-value.
            del math_tag['raw_latex-with-value']
        elif math_tag.has_attr('math_content-with-value'):
            pass
        else:
            raise Exception(
                'Invalid math tag with no proper attribute found.')
    # We need to replace the <br/> tags (if any) with  <br> because for passing
    # the textangular migration tests we need to have only <br> tags.
    return python_utils.UNICODE(soup).replace('<br/>', '<br>')
コード例 #4
0
def apply_change_list(question_id, change_list):
    """Applies a changelist to a pristine question and returns the result.

    Args:
        question_id: str. ID of the given question.
        change_list: list(QuestionChange). A change list to be applied to the
            given question. Each entry in change_list is a QuestionChange
            object.

    Returns:
        Question. The resulting question domain object.
    """
    question = get_question_by_id(question_id)
    question_property_inapplicable_skill_misconception_ids = (
        question_domain.QUESTION_PROPERTY_INAPPLICABLE_SKILL_MISCONCEPTION_IDS)
    try:
        for change in change_list:
            if change.cmd == question_domain.CMD_UPDATE_QUESTION_PROPERTY:
                if (change.property_name ==
                        question_domain.QUESTION_PROPERTY_LANGUAGE_CODE):
                    question.update_language_code(change.new_value)
                elif (change.property_name ==
                      question_domain.QUESTION_PROPERTY_QUESTION_STATE_DATA):
                    state_domain_object = state_domain.State.from_dict(
                        change.new_value)
                    question.update_question_state_data(state_domain_object)
                elif (change.property_name ==
                      question_domain.QUESTION_PROPERTY_LINKED_SKILL_IDS):
                    question.update_linked_skill_ids(change.new_value)
                elif (change.property_name ==
                      question_property_inapplicable_skill_misconception_ids):
                    question.update_inapplicable_skill_misconception_ids(
                        change.new_value)

        return question

    except Exception as e:
        logging.error('%s %s %s %s' %
                      (e.__class__.__name__, e, question_id, change_list))
        python_utils.reraise_exception()
コード例 #5
0
def apply_change_list(topic_id, change_list):
    """Applies a changelist to a topic and returns the result. The incoming
    changelist should not have simultaneuous creations and deletion of
    subtopics.

    Args:
        topic_id: str. ID of the given topic.
        change_list: list(TopicChange). A change list to be applied to the given
            topic.

    Raises:
        Exception. The incoming changelist had simultaneuous creation and
            deletion of subtopics.

    Returns:
        tuple(Topic, dict, list(int), list(int), list(SubtopicPageChange)). The
        modified topic object, the modified subtopic pages dict keyed
        by subtopic page id containing the updated domain objects of
        each subtopic page, a list of ids of the deleted subtopics,
        a list of ids of the newly created subtopics and a list of changes
        applied to modified subtopic pages.
    """
    topic = topic_fetchers.get_topic_by_id(topic_id)
    newly_created_subtopic_ids = []
    existing_subtopic_page_ids_to_be_modified = []
    deleted_subtopic_ids = []
    modified_subtopic_pages_list = []
    modified_subtopic_pages = {}
    modified_subtopic_change_cmds = collections.defaultdict(list)

    for change in change_list:
        if (change.cmd ==
                subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY):
            if change.subtopic_id < topic.next_subtopic_id:
                existing_subtopic_page_ids_to_be_modified.append(
                    change.subtopic_id)
                subtopic_page_id = (
                    subtopic_page_domain.SubtopicPage.get_subtopic_page_id(
                        topic_id, change.subtopic_id))
                modified_subtopic_change_cmds[subtopic_page_id].append(change)
    modified_subtopic_pages_list = (
        subtopic_page_services.get_subtopic_pages_with_ids(
            topic_id, existing_subtopic_page_ids_to_be_modified))
    for subtopic_page in modified_subtopic_pages_list:
        modified_subtopic_pages[subtopic_page.id] = subtopic_page

    try:
        for change in change_list:
            if change.cmd == topic_domain.CMD_ADD_SUBTOPIC:
                topic.add_subtopic(change.subtopic_id, change.title)
                subtopic_page_id = (
                    subtopic_page_domain.SubtopicPage.get_subtopic_page_id(
                        topic_id, change.subtopic_id))
                modified_subtopic_pages[subtopic_page_id] = (
                    subtopic_page_domain.SubtopicPage.
                    create_default_subtopic_page(  # pylint: disable=line-too-long
                        change.subtopic_id, topic_id))
                modified_subtopic_change_cmds[subtopic_page_id].append(
                    subtopic_page_domain.SubtopicPageChange({
                        'cmd':
                        'create_new',
                        'topic_id':
                        topic_id,
                        'subtopic_id':
                        change.subtopic_id
                    }))
                newly_created_subtopic_ids.append(change.subtopic_id)
            elif change.cmd == topic_domain.CMD_DELETE_SUBTOPIC:
                topic.delete_subtopic(change.subtopic_id)
                if change.subtopic_id in newly_created_subtopic_ids:
                    raise Exception('The incoming changelist had simultaneous'
                                    ' creation and deletion of subtopics.')
                deleted_subtopic_ids.append(change.subtopic_id)
            elif change.cmd == topic_domain.CMD_ADD_CANONICAL_STORY:
                topic.add_canonical_story(change.story_id)
            elif change.cmd == topic_domain.CMD_DELETE_CANONICAL_STORY:
                topic.delete_canonical_story(change.story_id)
            elif change.cmd == topic_domain.CMD_REARRANGE_CANONICAL_STORY:
                topic.rearrange_canonical_story(change.from_index,
                                                change.to_index)
            elif change.cmd == topic_domain.CMD_ADD_ADDITIONAL_STORY:
                topic.add_additional_story(change.story_id)
            elif change.cmd == topic_domain.CMD_DELETE_ADDITIONAL_STORY:
                topic.delete_additional_story(change.story_id)
            elif change.cmd == topic_domain.CMD_ADD_UNCATEGORIZED_SKILL_ID:
                topic.add_uncategorized_skill_id(
                    change.new_uncategorized_skill_id)
            elif change.cmd == topic_domain.CMD_REMOVE_UNCATEGORIZED_SKILL_ID:
                topic.remove_uncategorized_skill_id(
                    change.uncategorized_skill_id)
            elif change.cmd == topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC:
                topic.move_skill_id_to_subtopic(change.old_subtopic_id,
                                                change.new_subtopic_id,
                                                change.skill_id)
            elif change.cmd == topic_domain.CMD_REARRANGE_SKILL_IN_SUBTOPIC:
                topic.rearrange_skill_in_subtopic(change.subtopic_id,
                                                  change.from_index,
                                                  change.to_index)
            elif change.cmd == topic_domain.CMD_REARRANGE_SUBTOPIC:
                topic.rearrange_subtopic(change.from_index, change.to_index)
            elif change.cmd == topic_domain.CMD_REMOVE_SKILL_ID_FROM_SUBTOPIC:
                topic.remove_skill_id_from_subtopic(change.subtopic_id,
                                                    change.skill_id)
            elif change.cmd == topic_domain.CMD_UPDATE_TOPIC_PROPERTY:
                if (change.property_name == topic_domain.TOPIC_PROPERTY_NAME):
                    topic.update_name(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_ABBREVIATED_NAME):
                    topic.update_abbreviated_name(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_URL_FRAGMENT):
                    topic.update_url_fragment(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_DESCRIPTION):
                    topic.update_description(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_LANGUAGE_CODE):
                    topic.update_language_code(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_THUMBNAIL_FILENAME):
                    topic.update_thumbnail_filename(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_THUMBNAIL_BG_COLOR):
                    topic.update_thumbnail_bg_color(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_META_TAG_CONTENT):
                    topic.update_meta_tag_content(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_PRACTICE_TAB_IS_DISPLAYED):
                    topic.update_practice_tab_is_displayed(change.new_value)
                elif (change.property_name ==
                      topic_domain.TOPIC_PROPERTY_PAGE_TITLE_FRAGMENT_FOR_WEB):
                    topic.update_page_title_fragment_for_web(change.new_value)
            elif (change.cmd ==
                  subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY):
                subtopic_page_id = (
                    subtopic_page_domain.SubtopicPage.get_subtopic_page_id(
                        topic_id, change.subtopic_id))
                if ((modified_subtopic_pages[subtopic_page_id] is None)
                        or (change.subtopic_id in deleted_subtopic_ids)):
                    raise Exception('The subtopic with id %s doesn\'t exist' %
                                    (change.subtopic_id))

                if (change.property_name == subtopic_page_domain.
                        SUBTOPIC_PAGE_PROPERTY_PAGE_CONTENTS_HTML):
                    page_contents = state_domain.SubtitledHtml.from_dict(
                        change.new_value)
                    page_contents.validate()
                    modified_subtopic_pages[
                        subtopic_page_id].update_page_contents_html(
                            page_contents)

                elif (change.property_name == subtopic_page_domain.
                      SUBTOPIC_PAGE_PROPERTY_PAGE_CONTENTS_AUDIO):
                    modified_subtopic_pages[
                        subtopic_page_id].update_page_contents_audio(
                            state_domain.RecordedVoiceovers.from_dict(
                                change.new_value))
            elif change.cmd == topic_domain.CMD_UPDATE_SUBTOPIC_PROPERTY:
                if (change.property_name ==
                        topic_domain.SUBTOPIC_PROPERTY_TITLE):
                    topic.update_subtopic_title(change.subtopic_id,
                                                change.new_value)
                if (change.property_name ==
                        topic_domain.SUBTOPIC_PROPERTY_THUMBNAIL_FILENAME):
                    topic.update_subtopic_thumbnail_filename(
                        change.subtopic_id, change.new_value)
                if (change.property_name ==
                        topic_domain.SUBTOPIC_PROPERTY_THUMBNAIL_BG_COLOR):
                    topic.update_subtopic_thumbnail_bg_color(
                        change.subtopic_id, change.new_value)
                if (change.property_name ==
                        topic_domain.SUBTOPIC_PROPERTY_URL_FRAGMENT):
                    topic.update_subtopic_url_fragment(change.subtopic_id,
                                                       change.new_value)

            elif (change.cmd ==
                  topic_domain.CMD_MIGRATE_SUBTOPIC_SCHEMA_TO_LATEST_VERSION):
                # Loading the topic model from the datastore into a
                # Topic domain object automatically converts it to use the
                # latest schema version. As a result, simply resaving the
                # topic is sufficient to apply the schema migration.
                continue
        return (topic, modified_subtopic_pages, deleted_subtopic_ids,
                newly_created_subtopic_ids, modified_subtopic_change_cmds)

    except Exception as e:
        logging.error('%s %s %s %s' %
                      (e.__class__.__name__, e, topic_id, change_list))
        python_utils.reraise_exception()
コード例 #6
0
def apply_change_list(story_id, change_list):
    """Applies a changelist to a story and returns the result.

    Args:
        story_id: str. ID of the given story.
        change_list: list(StoryChange). A change list to be applied to the given
            story.

    Returns:
        Story, list(str), list(str). The resulting story domain object, the
        exploration IDs removed from story and the exploration IDs added to
        the story.
    """
    story = story_fetchers.get_story_by_id(story_id)
    exp_ids_in_old_story = story.story_contents.get_all_linked_exp_ids()
    try:
        for change in change_list:
            if not isinstance(change, story_domain.StoryChange):
                raise Exception('Expected change to be of type StoryChange')
            if change.cmd == story_domain.CMD_ADD_STORY_NODE:
                story.add_node(change.node_id, change.title)
            elif change.cmd == story_domain.CMD_DELETE_STORY_NODE:
                story.delete_node(change.node_id)
            elif (change.cmd ==
                  story_domain.CMD_UPDATE_STORY_NODE_OUTLINE_STATUS):
                if change.new_value:
                    story.mark_node_outline_as_finalized(change.node_id)
                else:
                    story.mark_node_outline_as_unfinalized(change.node_id)
            elif change.cmd == story_domain.CMD_UPDATE_STORY_NODE_PROPERTY:
                if (change.property_name ==
                        story_domain.STORY_NODE_PROPERTY_OUTLINE):
                    story.update_node_outline(change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_TITLE):
                    story.update_node_title(change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_DESCRIPTION):
                    story.update_node_description(
                        change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_THUMBNAIL_FILENAME):
                    story.update_node_thumbnail_filename(
                        change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_THUMBNAIL_BG_COLOR):
                    story.update_node_thumbnail_bg_color(
                        change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_ACQUIRED_SKILL_IDS):
                    story.update_node_acquired_skill_ids(
                        change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_PREREQUISITE_SKILL_IDS):
                    story.update_node_prerequisite_skill_ids(
                        change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_DESTINATION_NODE_IDS):
                    story.update_node_destination_node_ids(
                        change.node_id, change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_NODE_PROPERTY_EXPLORATION_ID):
                    story.update_node_exploration_id(
                        change.node_id, change.new_value)
            elif change.cmd == story_domain.CMD_UPDATE_STORY_PROPERTY:
                if (change.property_name ==
                        story_domain.STORY_PROPERTY_TITLE):
                    story.update_title(change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_PROPERTY_THUMBNAIL_FILENAME):
                    story.update_thumbnail_filename(change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_PROPERTY_THUMBNAIL_BG_COLOR):
                    story.update_thumbnail_bg_color(change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_PROPERTY_DESCRIPTION):
                    story.update_description(change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_PROPERTY_NOTES):
                    story.update_notes(change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_PROPERTY_LANGUAGE_CODE):
                    story.update_language_code(change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_PROPERTY_URL_FRAGMENT):
                    story.update_url_fragment(change.new_value)
                elif (change.property_name ==
                      story_domain.STORY_PROPERTY_META_TAG_CONTENT):
                    story.update_meta_tag_content(change.new_value)
            elif change.cmd == story_domain.CMD_UPDATE_STORY_CONTENTS_PROPERTY:
                if (change.property_name ==
                        story_domain.INITIAL_NODE_ID):
                    story.update_initial_node(change.new_value)
                if change.property_name == story_domain.NODE:
                    story.rearrange_node_in_story(
                        change.old_value, change.new_value)
            elif (
                    change.cmd ==
                    story_domain.CMD_MIGRATE_SCHEMA_TO_LATEST_VERSION):
                # Loading the story model from the datastore into a
                # Story domain object automatically converts it to use the
                # latest schema version. As a result, simply resaving the
                # story is sufficient to apply the schema migration.
                continue

        exp_ids_in_modified_story = (
            story.story_contents.get_all_linked_exp_ids())
        exp_ids_removed_from_story = list(
            set(exp_ids_in_old_story).difference(exp_ids_in_modified_story))
        exp_ids_added_to_story = list(
            set(exp_ids_in_modified_story).difference(exp_ids_in_old_story))
        return story, exp_ids_removed_from_story, exp_ids_added_to_story

    except Exception as e:
        logging.error(
            '%s %s %s %s' % (
                e.__class__.__name__, e, story_id, change_list)
        )
        python_utils.reraise_exception()
コード例 #7
0
ファイル: admin.py プロジェクト: atpug22/oppia
    def post(self):
        """Handles POST requests."""
        action = self.normalized_payload.get('action')
        try:
            result = {}
            if action == 'reload_exploration':
                exploration_id = self.normalized_payload.get('exploration_id')
                self._reload_exploration(exploration_id)
            elif action == 'reload_collection':
                collection_id = self.normalized_payload.get('collection_id')
                self._reload_collection(collection_id)
            elif action == 'generate_dummy_explorations':
                num_dummy_exps_to_generate = self.normalized_payload.get(
                    'num_dummy_exps_to_generate')
                num_dummy_exps_to_publish = self.normalized_payload.get(
                    'num_dummy_exps_to_publish')

                if num_dummy_exps_to_generate < num_dummy_exps_to_publish:
                    raise self.InvalidInputException(
                        'Generate count cannot be less than publish count')
                else:
                    self._generate_dummy_explorations(
                        num_dummy_exps_to_generate, num_dummy_exps_to_publish)
            elif action == 'clear_search_index':
                search_services.clear_collection_search_index()
                search_services.clear_exploration_search_index()
            elif action == 'generate_dummy_new_structures_data':
                self._load_dummy_new_structures_data()
            elif action == 'generate_dummy_new_skill_data':
                self._generate_dummy_skill_and_questions()
            elif action == 'save_config_properties':
                new_config_property_values = self.normalized_payload.get(
                    'new_config_property_values')
                logging.info('[ADMIN] %s saved config property values: %s' %
                             (self.user_id, new_config_property_values))
                for (name, value) in new_config_property_values.items():
                    config_services.set_property(self.user_id, name, value)
            elif action == 'revert_config_property':
                config_property_id = self.normalized_payload.get(
                    'config_property_id')
                logging.info('[ADMIN] %s reverted config property: %s' %
                             (self.user_id, config_property_id))
                config_services.revert_property(self.user_id,
                                                config_property_id)
            elif action == 'upload_topic_similarities':
                data = self.normalized_payload.get('data')
                recommendations_services.update_topic_similarities(data)
            elif action == 'regenerate_topic_related_opportunities':
                topic_id = self.normalized_payload.get('topic_id')
                opportunities_count = (
                    opportunity_services.
                    regenerate_opportunities_related_to_topic(
                        topic_id, delete_existing_opportunities=True))
                result = {'opportunities_count': opportunities_count}
            elif action == 'update_feature_flag_rules':
                feature_name = self.normalized_payload.get('feature_name')
                new_rule_dicts = self.normalized_payload.get('new_rules')
                commit_message = self.normalized_payload.get('commit_message')

                try:
                    feature_services.update_feature_flag_rules(
                        feature_name, self.user_id, commit_message,
                        new_rule_dicts)
                except (utils.ValidationError,
                        feature_services.FeatureFlagNotFoundException) as e:
                    raise self.InvalidInputException(e)
                logging.info('[ADMIN] %s updated feature %s with new rules: '
                             '%s.' %
                             (self.user_id, feature_name, new_rule_dicts))
            self.render_json(result)
        except Exception as e:
            logging.exception('[ADMIN] %s', e)
            self.render_json({'error': python_utils.UNICODE(e)})
            python_utils.reraise_exception()
コード例 #8
0
def add_math_content_to_math_rte_components(html_string):
    """Replaces the attribute raw_latex-with-value in all Math component tags
    with a new attribute math_content-with-value. The new attribute has an
    additional field for storing SVG filenames. The field for SVG filename will
    be an empty string.

    Args:
        html_string: str. HTML string to modify.

    Returns:
        str. Updated HTML string with all Math component tags having the new
        attribute.
    """
    soup = bs4.BeautifulSoup(
        html_string.encode(encoding='utf-8'), 'html.parser')
    for math_tag in soup.findAll(name='oppia-noninteractive-math'):
        if math_tag.has_attr('raw_latex-with-value'):
            # There was a case in prod where the attr value was empty. This was
            # dealt with manually in an earlier migration (states schema v34),
            # but we are not sure how it arose. We can't migrate those snapshots
            # manually, hence the addition of the logic here. After all
            # snapshots are migrated to states schema v42 (or above), this
            # 'if' branch will no longer be needed.
            if not math_tag['raw_latex-with-value']:
                math_tag.decompose()
                continue

            try:
                # The raw_latex attribute value should be enclosed in
                # double quotes(&amp;quot;) and should be a valid unicode
                # string.
                raw_latex = (
                    json.loads(unescape_html(math_tag['raw_latex-with-value'])))
                normalized_raw_latex = (
                    objects.UnicodeString.normalize(raw_latex))
            except Exception as e:
                logging.exception(
                    'Invalid raw_latex string found in the math tag : %s' % (
                        python_utils.UNICODE(e)
                    )
                )
                python_utils.reraise_exception()
            math_content_dict = {
                'raw_latex': normalized_raw_latex,
                'svg_filename': ''
            }
            # Normalize and validate the value before adding to the math
            # tag.
            normalized_math_content_dict = (
                objects.MathExpressionContent.normalize(math_content_dict))
            # Add the new attribute math_expression_contents-with-value.
            math_tag['math_content-with-value'] = (
                escape_html(
                    json.dumps(normalized_math_content_dict, sort_keys=True)))
            # Delete the attribute raw_latex-with-value.
            del math_tag['raw_latex-with-value']
        elif math_tag.has_attr('math_content-with-value'):
            pass
        else:
            # Invalid math tag with no proper attribute found.
            math_tag.decompose()

    # We need to replace the <br/> tags (if any) with  <br> because for passing
    # the textangular migration tests we need to have only <br> tags.
    return python_utils.UNICODE(soup).replace('<br/>', '<br>')
コード例 #9
0
ファイル: admin.py プロジェクト: testMrinal/oppia
 def post(self):
     """Handles POST requests."""
     try:
         result = {}
         if self.payload.get('action') == 'reload_exploration':
             exploration_id = self.payload.get('exploration_id')
             self._reload_exploration(exploration_id)
         elif self.payload.get('action') == 'reload_collection':
             collection_id = self.payload.get('collection_id')
             self._reload_collection(collection_id)
         elif self.payload.get('action') == 'generate_dummy_explorations':
             num_dummy_exps_to_generate = self.payload.get(
                 'num_dummy_exps_to_generate')
             num_dummy_exps_to_publish = self.payload.get(
                 'num_dummy_exps_to_publish')
             if not isinstance(num_dummy_exps_to_generate, int):
                 raise self.InvalidInputException(
                     '%s is not a number' % num_dummy_exps_to_generate)
             elif not isinstance(num_dummy_exps_to_publish, int):
                 raise self.InvalidInputException('%s is not a number' %
                                                  num_dummy_exps_to_publish)
             elif num_dummy_exps_to_generate < num_dummy_exps_to_publish:
                 raise self.InvalidInputException(
                     'Generate count cannot be less than publish count')
             else:
                 self._generate_dummy_explorations(
                     num_dummy_exps_to_generate, num_dummy_exps_to_publish)
         elif self.payload.get('action') == 'clear_search_index':
             search_services.clear_collection_search_index()
             search_services.clear_exploration_search_index()
         elif (self.payload.get('action') ==
               'generate_dummy_new_structures_data'):
             self._load_dummy_new_structures_data()
         elif (self.payload.get('action') == 'generate_dummy_new_skill_data'
               ):
             self._generate_dummy_skill_and_questions()
         elif self.payload.get('action') == 'save_config_properties':
             new_config_property_values = self.payload.get(
                 'new_config_property_values')
             logging.info('[ADMIN] %s saved config property values: %s' %
                          (self.user_id, new_config_property_values))
             for (name, value) in new_config_property_values.items():
                 config_services.set_property(self.user_id, name, value)
         elif self.payload.get('action') == 'revert_config_property':
             config_property_id = self.payload.get('config_property_id')
             logging.info('[ADMIN] %s reverted config property: %s' %
                          (self.user_id, config_property_id))
             config_services.revert_property(self.user_id,
                                             config_property_id)
         elif self.payload.get('action') == 'start_new_job':
             for klass in (jobs_registry.ONE_OFF_JOB_MANAGERS +
                           (jobs_registry.AUDIT_JOB_MANAGERS)):
                 if klass.__name__ == self.payload.get('job_type'):
                     klass.enqueue(klass.create_new())
                     break
         elif self.payload.get('action') == 'cancel_job':
             job_id = self.payload.get('job_id')
             job_type = self.payload.get('job_type')
             for klass in (jobs_registry.ONE_OFF_JOB_MANAGERS +
                           (jobs_registry.AUDIT_JOB_MANAGERS)):
                 if klass.__name__ == job_type:
                     klass.cancel(job_id, self.user_id)
                     break
         elif self.payload.get('action') == 'start_computation':
             computation_type = self.payload.get('computation_type')
             for klass in jobs_registry.ALL_CONTINUOUS_COMPUTATION_MANAGERS:
                 if klass.__name__ == computation_type:
                     klass.start_computation()
                     break
         elif self.payload.get('action') == 'stop_computation':
             computation_type = self.payload.get('computation_type')
             for klass in jobs_registry.ALL_CONTINUOUS_COMPUTATION_MANAGERS:
                 if klass.__name__ == computation_type:
                     klass.stop_computation(self.user_id)
                     break
         elif self.payload.get('action') == 'upload_topic_similarities':
             data = self.payload.get('data')
             recommendations_services.update_topic_similarities(data)
         elif self.payload.get('action') == (
                 'regenerate_topic_related_opportunities'):
             topic_id = self.payload.get('topic_id')
             opportunities_count = (
                 opportunity_services.
                 regenerate_opportunities_related_to_topic(
                     topic_id, delete_existing_opportunities=True))
             result = {'opportunities_count': opportunities_count}
         elif self.payload.get('action') == 'update_feature_flag_rules':
             feature_name = self.payload.get('feature_name')
             new_rule_dicts = self.payload.get('new_rules')
             commit_message = self.payload.get('commit_message')
             if not isinstance(feature_name, python_utils.BASESTRING):
                 raise self.InvalidInputException(
                     'feature_name should be string, received \'%s\'.' %
                     (feature_name))
             elif not isinstance(commit_message, python_utils.BASESTRING):
                 raise self.InvalidInputException(
                     'commit_message should be string, received \'%s\'.' %
                     (commit_message))
             elif (not isinstance(new_rule_dicts, list) or not all([
                     isinstance(rule_dict, dict)
                     for rule_dict in new_rule_dicts
             ])):
                 raise self.InvalidInputException(
                     'new_rules should be a list of dicts, received'
                     ' \'%s\'.' % new_rule_dicts)
             try:
                 feature_services.update_feature_flag_rules(
                     feature_name, self.user_id, commit_message,
                     new_rule_dicts)
             except (utils.ValidationError,
                     feature_services.FeatureFlagNotFoundException) as e:
                 raise self.InvalidInputException(e)
             logging.info('[ADMIN] %s updated feature %s with new rules: '
                          '%s.' %
                          (self.user_id, feature_name, new_rule_dicts))
         self.render_json(result)
     except Exception as e:
         logging.error('[ADMIN] %s', e)
         self.render_json({'error': python_utils.UNICODE(e)})
         python_utils.reraise_exception()
コード例 #10
0
def apply_change_list(skill_id, change_list, committer_id):
    """Applies a changelist to a skill and returns the result.

    Args:
        skill_id: str. ID of the given skill.
        change_list: list(SkillChange). A change list to be applied to the given
            skill.
        committer_id: str. The ID of the committer of this change list.

    Returns:
        Skill. The resulting skill domain object.
    """
    skill = skill_fetchers.get_skill_by_id(skill_id)
    user = user_services.UserActionsInfo(committer_id)
    try:
        for change in change_list:
            if change.cmd == skill_domain.CMD_UPDATE_SKILL_PROPERTY:
                if (change.property_name ==
                        skill_domain.SKILL_PROPERTY_DESCRIPTION):
                    if role_services.ACTION_EDIT_SKILL_DESCRIPTION not in (
                            user.actions):
                        raise Exception(
                            'The user does not have enough rights to edit the '
                            'skill description.')
                    skill.update_description(change.new_value)
                    (opportunity_services.
                     update_skill_opportunity_skill_description(
                         skill.id, change.new_value))
                elif (change.property_name ==
                      skill_domain.SKILL_PROPERTY_LANGUAGE_CODE):
                    skill.update_language_code(change.new_value)
                elif (change.property_name ==
                      skill_domain.SKILL_PROPERTY_SUPERSEDING_SKILL_ID):
                    skill.update_superseding_skill_id(change.new_value)
                elif (change.property_name ==
                      skill_domain.SKILL_PROPERTY_ALL_QUESTIONS_MERGED):
                    skill.record_that_all_questions_are_merged(
                        change.new_value)
            elif change.cmd == skill_domain.CMD_UPDATE_SKILL_CONTENTS_PROPERTY:
                if (change.property_name ==
                        skill_domain.SKILL_CONTENTS_PROPERTY_EXPLANATION):
                    explanation = (state_domain.SubtitledHtml.from_dict(
                        change.new_value))
                    explanation.validate()
                    skill.update_explanation(explanation)
                elif (change.property_name ==
                      skill_domain.SKILL_CONTENTS_PROPERTY_WORKED_EXAMPLES):
                    worked_examples_list = [
                        skill_domain.WorkedExample.from_dict(worked_example)
                        for worked_example in change.new_value
                    ]
                    skill.update_worked_examples(worked_examples_list)
            elif change.cmd == skill_domain.CMD_ADD_SKILL_MISCONCEPTION:
                misconception = skill_domain.Misconception.from_dict(
                    change.new_misconception_dict)
                skill.add_misconception(misconception)
            elif change.cmd == skill_domain.CMD_DELETE_SKILL_MISCONCEPTION:
                skill.delete_misconception(change.misconception_id)
            elif change.cmd == skill_domain.CMD_ADD_PREREQUISITE_SKILL:
                skill.add_prerequisite_skill(change.skill_id)
            elif change.cmd == skill_domain.CMD_DELETE_PREREQUISITE_SKILL:
                skill.delete_prerequisite_skill(change.skill_id)
            elif change.cmd == skill_domain.CMD_UPDATE_RUBRICS:
                skill.update_rubric(change.difficulty, change.explanations)
            elif (change.cmd ==
                  skill_domain.CMD_UPDATE_SKILL_MISCONCEPTIONS_PROPERTY):
                if (change.property_name ==
                        skill_domain.SKILL_MISCONCEPTIONS_PROPERTY_NAME):
                    skill.update_misconception_name(change.misconception_id,
                                                    change.new_value)
                elif (change.property_name ==
                      skill_domain.SKILL_MISCONCEPTIONS_PROPERTY_NOTES):
                    skill.update_misconception_notes(change.misconception_id,
                                                     change.new_value)
                elif (change.property_name ==
                      skill_domain.SKILL_MISCONCEPTIONS_PROPERTY_FEEDBACK):
                    skill.update_misconception_feedback(
                        change.misconception_id, change.new_value)
                elif (change.property_name ==
                      skill_domain.SKILL_MISCONCEPTIONS_PROPERTY_MUST_BE_ADDRESSED):  # pylint: disable=line-too-long
                    skill.update_misconception_must_be_addressed(
                        change.misconception_id, change.new_value)
                else:
                    raise Exception('Invalid change dict.')
            elif (change.cmd
                  == skill_domain.CMD_MIGRATE_CONTENTS_SCHEMA_TO_LATEST_VERSION
                  or change.cmd == skill_domain.
                  CMD_MIGRATE_MISCONCEPTIONS_SCHEMA_TO_LATEST_VERSION  # pylint: disable=line-too-long
                  or change.cmd ==
                  skill_domain.CMD_MIGRATE_RUBRICS_SCHEMA_TO_LATEST_VERSION):
                # Loading the skill model from the datastore into a
                # skill domain object automatically converts it to use the
                # latest schema version. As a result, simply resaving the
                # skill is sufficient to apply the schema migration.
                continue

        return skill

    except Exception as e:
        logging.error('%s %s %s %s' %
                      (e.__class__.__name__, e, skill_id, change_list))
        python_utils.reraise_exception()