def put(self, topic_id): """Updates properties of the given topic. Also, each change_dict given for editing should have an additional property called is_topic_change, which would be a boolean. If True, it means that change is for a topic (includes adding and removing subtopics), while False would mean it is for a Subtopic Page (this includes editing its html data as of now). """ topic_domain.Topic.require_valid_topic_id(topic_id) topic = topic_fetchers.get_topic_by_id(topic_id, strict=False) version = self.payload.get('version') self._require_valid_version(version, topic.version) commit_message = self.payload.get('commit_message') topic_and_subtopic_page_change_dicts = self.payload.get( 'topic_and_subtopic_page_change_dicts') topic_and_subtopic_page_change_list = [] for change in topic_and_subtopic_page_change_dicts: if change['cmd'] == ( subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY): topic_and_subtopic_page_change_list.append( subtopic_page_domain.SubtopicPageChange(change)) else: topic_and_subtopic_page_change_list.append( topic_domain.TopicChange(change)) try: topic_services.update_topic_and_subtopic_pages( self.user_id, topic_id, topic_and_subtopic_page_change_list, commit_message) except utils.ValidationError as e: raise self.InvalidInputException(e) topic = topic_fetchers.get_topic_by_id(topic_id, strict=False) skill_id_to_description_dict, deleted_skill_ids = ( skill_services.get_descriptions_of_skills( topic.get_all_skill_ids())) skill_id_to_rubrics_dict, deleted_skill_ids = ( skill_services.get_rubrics_of_skills(topic.get_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)) self.values.update({ 'topic_dict': topic.to_dict(), 'skill_id_to_description_dict': skill_id_to_description_dict, 'skill_id_to_rubrics_dict': skill_id_to_rubrics_dict }) self.render_json(self.values)
def setUp(self): super(BaseSubtopicViewerControllerTests, self).setUp() self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME) self.admin_id = self.get_user_id_from_email(self.ADMIN_EMAIL) self.set_admins([self.ADMIN_USERNAME]) self.admin = user_services.UserActionsInfo(self.admin_id) self.topic_id = 'topic_id' self.subtopic_id = 1 self.subtopic_page = ( subtopic_page_domain.SubtopicPage.create_default_subtopic_page( self.subtopic_id, self.topic_id)) subtopic_page_services.save_subtopic_page( self.user_id, self.subtopic_page, 'Added subtopic', [ topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'subtopic_id': self.subtopic_id, 'title': 'Sample' }) ]) self.content_ids_to_audio_translations_dict = { 'content': { 'en': { 'filename': 'test.mp3', 'file_size_bytes': 100, 'needs_update': False } } } self.written_translations_dict = { 'translations_mapping': { 'content': {} } } self.expected_page_contents_dict = { 'content_ids_to_audio_translations': self.content_ids_to_audio_translations_dict, 'subtitled_html': { 'content_id': 'content', 'html': 'hello world' } } self.subtopic_page.update_page_contents_html({ 'html': 'hello world', 'content_id': 'content' }) self.subtopic_page.update_page_contents_audio( self.content_ids_to_audio_translations_dict) subtopic_page_services.save_subtopic_page( self.user_id, self.subtopic_page, 'Updated page contents', [ subtopic_page_domain.SubtopicPageChange( { 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'subtopic_id': self.subtopic_id, 'property_name': 'page_contents_html', 'new_value': 'a', 'old_value': 'b' }) ])
def test_subtopic_page_change_object_with_missing_attribute_in_cmd(self): with self.assertRaisesRegexp( utils.ValidationError, ( 'The following required attributes are missing: ' 'new_value, old_value')): subtopic_page_domain.SubtopicPageChange({ 'cmd': 'update_subtopic_page_property', 'property_name': '<p>page_contents_html</p>', })
def test_subtopic_page_change_object_with_extra_attribute_in_cmd(self): with self.assertRaisesRegexp( utils.ValidationError, ('The following extra attributes are present: invalid')): subtopic_page_domain.SubtopicPageChange({ 'cmd': 'create_new', 'topic_id': 'topic_id', 'subtopic_id': 'subtopic_id', 'invalid': 'invalid' })
def test_to_dict(self): subtopic_page_change_dict = { 'cmd': 'create_new', 'topic_id': 'topic_id', 'subtopic_id': 'subtopic_id' } subtopic_page_change_object = subtopic_page_domain.SubtopicPageChange( subtopic_page_change_dict) self.assertEqual(subtopic_page_change_object.to_dict(), subtopic_page_change_dict)
def test_get_subtopic_page_contents_by_id(self): self.subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 1) recorded_voiceovers = { 'voiceovers_mapping': { 'content': { 'en': { 'filename': 'test.mp3', 'file_size_bytes': 100, 'needs_update': False, 'duration_secs': 7.213 } } } } expected_page_contents_dict = { 'subtitled_html': { 'content_id': 'content', 'html': '<p>hello world</p>' }, 'recorded_voiceovers': recorded_voiceovers, 'written_translations': { 'translations_mapping': { 'content': {} } } } self.subtopic_page.update_page_contents_html( state_domain.SubtitledHtml.from_dict({ 'html': '<p>hello world</p>', 'content_id': 'content' })) self.subtopic_page.update_page_contents_audio( state_domain.RecordedVoiceovers.from_dict(recorded_voiceovers)) subtopic_page_services.save_subtopic_page( self.user_id, self.subtopic_page, 'Updated page contents', [ subtopic_page_domain.SubtopicPageChange( { 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'subtopic_id': 1, 'property_name': 'page_contents_html', 'new_value': 'a', 'old_value': 'b' }) ]) subtopic_page_contents = ( subtopic_page_services.get_subtopic_page_contents_by_id( self.TOPIC_ID, 1)) self.assertEqual(subtopic_page_contents.to_dict(), expected_page_contents_dict) subtopic_page_contents = ( subtopic_page_services.get_subtopic_page_contents_by_id( self.TOPIC_ID, 2, strict=False)) self.assertEqual(subtopic_page_contents, None)
def test_subtopic_page_change_object_with_create_new(self): subtopic_page_change_object = ( subtopic_page_domain.SubtopicPageChange({ 'cmd': 'create_new', 'topic_id': 'topic_id', 'subtopic_id': 'subtopic_id' })) self.assertEqual(subtopic_page_change_object.cmd, 'create_new') self.assertEqual(subtopic_page_change_object.topic_id, 'topic_id') self.assertEqual(subtopic_page_change_object.subtopic_id, 'subtopic_id')
def put(self, topic_id): """Updates properties of the given topic. Also, each change_dict given for editing should have an additional property called is_topic_change, which would be a boolean. If True, it means that change is for a topic (includes adding and removing subtopics), while False would mean it is for a Subtopic Page (this includes editing its html data as of now). """ if not feconf.ENABLE_NEW_STRUCTURES: raise self.PageNotFoundException topic_domain.Topic.require_valid_topic_id(topic_id) topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None: raise self.PageNotFoundException( Exception('The topic with the given id doesn\'t exist.')) version = self.payload.get('version') self._require_valid_version(version, topic.version) commit_message = self.payload.get('commit_message') topic_and_subtopic_page_change_dicts = self.payload.get( 'topic_and_subtopic_page_change_dicts') topic_and_subtopic_page_change_list = [] for change in topic_and_subtopic_page_change_dicts: if change['change_affects_subtopic_page']: topic_and_subtopic_page_change_list.append( subtopic_page_domain.SubtopicPageChange(change)) else: topic_and_subtopic_page_change_list.append( topic_domain.TopicChange(change)) try: topic_services.update_topic_and_subtopic_pages( self.user_id, topic_id, topic_and_subtopic_page_change_list, commit_message) except utils.ValidationError as e: raise self.InvalidInputException(e) topic = topic_services.get_topic_by_id(topic_id, strict=False) skill_ids = topic.get_all_skill_ids() skill_id_to_description_dict = ( skill_services.get_skill_descriptions_by_ids(topic_id, skill_ids)) self.values.update({ 'topic_dict': topic.to_dict(), 'skill_id_to_description_dict': skill_id_to_description_dict }) self.render_json(self.values)
def test_get_subtopic_page_contents_by_id(self): self.subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 1) content_ids_to_audio_translations_dict = { 'content': { 'en': { 'filename': 'test.mp3', 'file_size_bytes': 100, 'needs_update': False } } } expected_page_contents_dict = { 'content_ids_to_audio_translations': content_ids_to_audio_translations_dict, 'subtitled_html': { 'content_id': 'content', 'html': 'hello world' }, 'written_translations': { 'translations_mapping': { 'content': {} } } } self.subtopic_page.update_page_contents_html({ 'html': 'hello world', 'content_id': 'content' }) self.subtopic_page.update_page_contents_audio( content_ids_to_audio_translations_dict) subtopic_page_services.save_subtopic_page( self.user_id, self.subtopic_page, 'Updated page contents', [ subtopic_page_domain.SubtopicPageChange( { 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'subtopic_id': 1, 'property_name': 'page_contents_html', 'new_value': 'a', 'old_value': 'b' }) ]) subtopic_page_contents = ( subtopic_page_services.get_subtopic_page_contents_by_id( self.TOPIC_ID, 1)) self.assertEqual(subtopic_page_contents.to_dict(), expected_page_contents_dict) subtopic_page_contents = ( subtopic_page_services.get_subtopic_page_contents_by_id( self.TOPIC_ID, 2, strict=False)) self.assertEqual(subtopic_page_contents, None)
def test_subtopic_page_change_object_with_invalid_subtopic_page_property( self): with self.assertRaisesRegexp( utils.ValidationError, ('Value for property_name in cmd update_subtopic_page_property: ' 'invalid is not allowed')): subtopic_page_domain.SubtopicPageChange({ 'cmd': 'update_subtopic_page_property', 'subtopic_id': 'subtopic_id', 'property_name': 'invalid', 'old_value': 'old_value', 'new_value': 'new_value', })
def test_create_subtopic_page_change(self): subtopic_page_change_object = subtopic_page_domain.SubtopicPageChange({ 'cmd': subtopic_page_domain.CMD_CREATE_NEW, 'topic_id': self.topic_id, 'subtopic_id': 'subtopic_id' }) self.assertEqual( subtopic_page_change_object.to_dict(), { 'cmd': subtopic_page_domain.CMD_CREATE_NEW, 'topic_id': self.topic_id, 'subtopic_id': 'subtopic_id' })
def test_standard_operation(self): topic_services.update_topic_and_subtopic_pages( self.owner_id, '0', [subtopic_page_domain.SubtopicPageChange({ 'cmd': 'update_subtopic_page_property', 'property_name': 'page_contents_html', 'subtopic_id': 1, 'new_value': { 'html': '<p>html</p>', 'content_id': 'content' }, 'old_value': {} })], 'Changes.') expected_output = [ u'[u\'fully-validated SubtopicPageCommitLogEntryModel\', 4]'] self.run_job_and_check_output( expected_output, sort=False, literal_eval=False)
def put(self, topic_id): """Updates properties of the given topic. Also, each change_dict given for editing should have an additional property called is_topic_change, which would be a boolean. If True, it means that change is for a topic (includes adding and removing subtopics), while False would mean it is for a Subtopic Page (this includes editing its html data as of now). """ topic_domain.Topic.require_valid_topic_id(topic_id) topic = topic_services.get_topic_by_id(topic_id, strict=False) version = self.payload.get('version') self._require_valid_version(version, topic.version) commit_message = self.payload.get('commit_message') topic_and_subtopic_page_change_dicts = self.payload.get( 'topic_and_subtopic_page_change_dicts') topic_and_subtopic_page_change_list = [] for change in topic_and_subtopic_page_change_dicts: if change['cmd'] == ( subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY): topic_and_subtopic_page_change_list.append( subtopic_page_domain.SubtopicPageChange(change)) else: topic_and_subtopic_page_change_list.append( topic_domain.TopicChange(change)) try: topic_services.update_topic_and_subtopic_pages( self.user_id, topic_id, topic_and_subtopic_page_change_list, commit_message) except utils.ValidationError as e: raise self.InvalidInputException(e) topic = topic_services.get_topic_by_id(topic_id, strict=False) skill_ids = topic.get_all_skill_ids() skill_id_to_description_dict = ( skill_services.get_skill_descriptions_by_ids(topic_id, skill_ids)) self.values.update({ 'topic_dict': topic.to_dict(), 'skill_id_to_description_dict': skill_id_to_description_dict }) self.render_json(self.values)
def test_subtopic_page_change_object_with_update_subtopic_page_property( self): subtopic_page_change_object = subtopic_page_domain.SubtopicPageChange({ 'cmd': 'update_subtopic_page_property', 'subtopic_id': 'subtopic_id', 'property_name': 'page_contents_html', 'new_value': 'new_value', 'old_value': 'old_value' }) self.assertEqual( subtopic_page_change_object.cmd, 'update_subtopic_page_property') self.assertEqual(subtopic_page_change_object.subtopic_id, 'subtopic_id') self.assertEqual( subtopic_page_change_object.property_name, 'page_contents_html') self.assertEqual(subtopic_page_change_object.new_value, 'new_value') self.assertEqual(subtopic_page_change_object.old_value, 'old_value')
def test_update_topic_and_subtopic_page(self): changelist = [topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'title': 'Title3', 'subtopic_id': 3 })] with self.assertRaisesRegexp( Exception, 'The given new subtopic id 3 is not equal to ' 'the expected next subtopic id: 2'): topic_services.update_topic_and_subtopic_pages( self.user_id_admin, self.TOPIC_ID, changelist, 'Added subtopic.') # Test whether the subtopic page was created for the above failed # attempt. subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 3, strict=False) self.assertIsNone(subtopic_page) # Test exception raised for simultaneous adding and removing of # subtopics. changelist = [ topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'title': 'Title2', 'subtopic_id': 2 }), topic_domain.TopicChange({ 'cmd': topic_domain.CMD_DELETE_SUBTOPIC, 'subtopic_id': 2 }) ] with self.assertRaisesRegexp( Exception, 'The incoming changelist had simultaneous' ' creation and deletion of subtopics.'): topic_services.update_topic_and_subtopic_pages( self.user_id_admin, self.TOPIC_ID, changelist, 'Added and deleted a subtopic.') # Test whether a subtopic page already existing in datastore can be # edited. changelist = [subtopic_page_domain.SubtopicPageChange({ 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'property_name': ( subtopic_page_domain.SUBTOPIC_PAGE_PROPERTY_HTML_DATA), 'old_value': '', 'subtopic_id': 1, 'new_value': '<p>New Value</p>' })] topic_services.update_topic_and_subtopic_pages( self.user_id_admin, self.TOPIC_ID, changelist, 'Updated html data') subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 1) self.assertEqual(subtopic_page.html_data, '<p>New Value</p>') # Test a sequence of changes with both topic and subtopic page changes. changelist = [ topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'title': 'Title2', 'subtopic_id': 2 }), topic_domain.TopicChange({ 'cmd': topic_domain.CMD_DELETE_SUBTOPIC, 'subtopic_id': 1 }), subtopic_page_domain.SubtopicPageChange({ 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'property_name': ( subtopic_page_domain.SUBTOPIC_PAGE_PROPERTY_HTML_DATA), 'old_value': '', 'subtopic_id': 2, 'new_value': '<p>New Value</p>' }), topic_domain.TopicChange({ 'cmd': topic_domain.CMD_MOVE_SKILL_ID_TO_SUBTOPIC, 'old_subtopic_id': None, 'new_subtopic_id': 2, 'skill_id': self.skill_id_1 }) ] topic_services.update_topic_and_subtopic_pages( self.user_id_admin, self.TOPIC_ID, changelist, 'Added and removed a subtopic.') topic = topic_services.get_topic_by_id(self.TOPIC_ID) self.assertEqual(len(topic.subtopics), 1) self.assertEqual(topic.next_subtopic_id, 3) self.assertEqual(topic.subtopics[0].title, 'Title2') self.assertEqual(topic.subtopics[0].skill_ids, [self.skill_id_1]) # Test whether the subtopic page corresponding to the deleted subtopic # was also deleted. subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 1, strict=False) self.assertIsNone(subtopic_page) # Validate the newly created subtopic page. subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 2, strict=False) self.assertEqual(subtopic_page.html_data, '<p>New Value</p>') # Making sure everything resets when an error is encountered anywhere. changelist = [ topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'title': 'Title3', 'subtopic_id': 3 }), topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'title': 'Title4', 'subtopic_id': 4 }), topic_domain.TopicChange({ 'cmd': topic_domain.CMD_DELETE_SUBTOPIC, 'subtopic_id': 2 }), # The following is an invalid command as subtopic with id 2 was # deleted in previous step. subtopic_page_domain.SubtopicPageChange({ 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'property_name': ( subtopic_page_domain.SUBTOPIC_PAGE_PROPERTY_HTML_DATA), 'old_value': '', 'subtopic_id': 2, 'new_value': '<p>New Value</p>' }), ] with self.assertRaisesRegexp( Exception, 'The subtopic with id 2 doesn\'t exist'): topic_services.update_topic_and_subtopic_pages( self.user_id_admin, self.TOPIC_ID, changelist, 'Done some changes.') # Make sure the topic object in datastore is not affected. topic = topic_services.get_topic_by_id(self.TOPIC_ID) self.assertEqual(len(topic.subtopics), 1) self.assertEqual(topic.next_subtopic_id, 3) self.assertEqual(topic.subtopics[0].title, 'Title2') self.assertEqual(topic.subtopics[0].skill_ids, [self.skill_id_1]) subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 3, strict=False) self.assertIsNone(subtopic_page) subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 4, strict=False) self.assertIsNone(subtopic_page) subtopic_page = subtopic_page_services.get_subtopic_page_by_id( self.TOPIC_ID, 2, strict=False) self.assertIsNotNone(subtopic_page)
def test_subtopic_page_change_object_with_missing_cmd(self) -> None: with self.assertRaisesRegex( # type: ignore[no-untyped-call] utils.ValidationError, 'Missing cmd key in change dict'): subtopic_page_domain.SubtopicPageChange({'invalid': 'data'})
def test_subtopic_page_change_object_with_invalid_cmd(self): with self.assertRaisesRegexp(utils.ValidationError, 'Command invalid is not allowed'): subtopic_page_domain.SubtopicPageChange({'cmd': 'invalid'})
def test_subtopic_page_change_object_with_missing_cmd(self): with self.assertRaisesRegexp(utils.ValidationError, 'Missing cmd key in change dict'): subtopic_page_domain.SubtopicPageChange({'invalid': 'data'})
def test_subtopic_page_change_object_with_invalid_cmd(self) -> None: with self.assertRaisesRegex( # type: ignore[no-untyped-call] utils.ValidationError, 'Command invalid is not allowed'): subtopic_page_domain.SubtopicPageChange({'cmd': 'invalid'})
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(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: 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 = 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_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_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_DESCRIPTION): topic.update_description(change.new_value) elif (change.property_name == topic_domain.TOPIC_PROPERTY_CANONICAL_STORY_IDS): topic.update_canonical_story_ids(change.new_value) elif (change.property_name == topic_domain.TOPIC_PROPERTY_ADDITIONAL_STORY_IDS): topic.update_additional_story_ids(change.new_value) elif (change.property_name == topic_domain.TOPIC_PROPERTY_LANGUAGE_CODE): topic.update_language_code(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): modified_subtopic_pages[ subtopic_page_id].update_page_contents_html( change.new_value) elif (change.property_name == subtopic_page_domain. SUBTOPIC_PAGE_PROPERTY_PAGE_CONTENTS_AUDIO): modified_subtopic_pages[ subtopic_page_id].update_page_contents_audio( 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) 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)) raise
def setUp(self): super(BaseSubtopicViewerControllerTests, self).setUp() self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME) self.admin_id = self.get_user_id_from_email(self.ADMIN_EMAIL) self.set_admins([self.ADMIN_USERNAME]) self.admin = user_services.UserActionsInfo(self.admin_id) self.topic_id = 'topic_id' self.subtopic_id = 1 self.subtopic_page = ( subtopic_page_domain.SubtopicPage.create_default_subtopic_page( self.subtopic_id, self.topic_id)) subtopic_page_services.save_subtopic_page( self.admin_id, self.subtopic_page, 'Added subtopic', [topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'subtopic_id': self.subtopic_id, 'title': 'Sample' })] ) subtopic_page_2 = ( subtopic_page_domain.SubtopicPage.create_default_subtopic_page( self.subtopic_id, 'topic_id_2')) subtopic_page_services.save_subtopic_page( self.admin_id, subtopic_page_2, 'Added subtopic', [topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'subtopic_id': self.subtopic_id, 'title': 'Sample' })] ) subtopic = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title') self.save_new_topic( self.topic_id, self.admin_id, 'Name', 'Description', [], [], [], [subtopic], 2) topic_services.publish_topic(self.topic_id, self.admin_id) self.save_new_topic( 'topic_id_2', self.admin_id, 'Private_Name', 'Description', [], [], [], [subtopic], 2) self.recorded_voiceovers_dict = { 'voiceovers_mapping': { 'content': { 'en': { 'filename': 'test.mp3', 'file_size_bytes': 100, 'needs_update': False } } } } self.written_translations_dict = { 'translations_mapping': { 'content': {} } } self.subtopic_page.update_page_contents_html( state_domain.SubtitledHtml.from_dict({ 'html': '<p>hello world</p>', 'content_id': 'content' })) self.subtopic_page.update_page_contents_audio( state_domain.RecordedVoiceovers.from_dict( self.recorded_voiceovers_dict)) subtopic_page_services.save_subtopic_page( self.admin_id, self.subtopic_page, 'Updated page contents', [subtopic_page_domain.SubtopicPageChange({ 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'subtopic_id': self.subtopic_id, 'property_name': 'page_contents_html', 'new_value': 'a', 'old_value': 'b' })] )
def setUp(self): super(BaseSubtopicViewerControllerTests, self).setUp() self.signup(self.CURRICULUM_ADMIN_EMAIL, self.CURRICULUM_ADMIN_USERNAME) self.admin_id = self.get_user_id_from_email( self.CURRICULUM_ADMIN_EMAIL) self.set_curriculum_admins([self.CURRICULUM_ADMIN_USERNAME]) self.admin = user_services.get_user_actions_info(self.admin_id) self.topic_id = 'topic_id' self.subtopic_id_1 = 1 self.subtopic_id_2 = 2 self.subtopic_page_1 = ( subtopic_page_domain.SubtopicPage.create_default_subtopic_page( self.subtopic_id_1, self.topic_id)) self.subtopic_page_2 = ( subtopic_page_domain.SubtopicPage.create_default_subtopic_page( self.subtopic_id_2, self.topic_id)) subtopic_page_services.save_subtopic_page( self.admin_id, self.subtopic_page_1, 'Added subtopic', [ topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'subtopic_id': self.subtopic_id_1, 'title': 'Sample' }) ]) subtopic_page_services.save_subtopic_page( self.admin_id, self.subtopic_page_2, 'Added subtopic', [ topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'subtopic_id': self.subtopic_id_2, 'title': 'Sample' }) ]) subtopic_page_private_topic = ( subtopic_page_domain.SubtopicPage.create_default_subtopic_page( self.subtopic_id_1, 'topic_id_2')) subtopic_page_services.save_subtopic_page( self.admin_id, subtopic_page_private_topic, 'Added subtopic', [ topic_domain.TopicChange({ 'cmd': topic_domain.CMD_ADD_SUBTOPIC, 'subtopic_id': self.subtopic_id_1, 'title': 'Sample' }) ]) subtopic = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title') subtopic.skill_ids = ['skill_id_1'] subtopic.url_fragment = 'sub-url-frag-one' subtopic2 = topic_domain.Subtopic.create_default_subtopic( 2, 'Subtopic Title 2') subtopic2.skill_ids = ['skill_id_2'] subtopic2.url_fragment = 'sub-url-frag-two' self.save_new_topic(self.topic_id, self.admin_id, name='Name', abbreviated_name='name', url_fragment='name', description='Description', canonical_story_ids=[], additional_story_ids=[], uncategorized_skill_ids=[], subtopics=[subtopic, subtopic2], next_subtopic_id=3) topic_services.publish_topic(self.topic_id, self.admin_id) self.save_new_topic('topic_id_2', self.admin_id, name='Private_Name', abbreviated_name='pvttopic', url_fragment='pvttopic', description='Description', canonical_story_ids=[], additional_story_ids=[], uncategorized_skill_ids=[], subtopics=[subtopic], next_subtopic_id=2) self.recorded_voiceovers_dict = { 'voiceovers_mapping': { 'content': { 'en': { 'filename': 'test.mp3', 'file_size_bytes': 100, 'needs_update': False, 'duration_secs': 0.34234 } } } } self.written_translations_dict = { 'translations_mapping': { 'content': {} } } self.subtopic_page_1.update_page_contents_html( state_domain.SubtitledHtml.from_dict({ 'html': '<p>hello world</p>', 'content_id': 'content' })) self.subtopic_page_1.update_page_contents_audio( state_domain.RecordedVoiceovers.from_dict( self.recorded_voiceovers_dict)) subtopic_page_services.save_subtopic_page( self.admin_id, self.subtopic_page_1, 'Updated page contents', [ subtopic_page_domain.SubtopicPageChange( { 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'subtopic_id': self.subtopic_id_1, 'property_name': 'page_contents_html', 'new_value': '<p>hello world</p>', 'old_value': '' }) ]) self.subtopic_page_2.update_page_contents_html( state_domain.SubtitledHtml.from_dict({ 'html': '<p>hello world 2</p>', 'content_id': 'content' })) self.subtopic_page_2.update_page_contents_audio( state_domain.RecordedVoiceovers.from_dict( self.recorded_voiceovers_dict)) subtopic_page_services.save_subtopic_page( self.admin_id, self.subtopic_page_2, 'Updated page contents', [ subtopic_page_domain.SubtopicPageChange( { 'cmd': subtopic_page_domain.CMD_UPDATE_SUBTOPIC_PAGE_PROPERTY, 'subtopic_id': self.subtopic_id_2, 'property_name': 'page_contents_html', 'new_value': '<p>hello world 2</p>', 'old_value': '' }) ])