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