def test_get_suggestions_after_updating_suggestion_summary(self): self.login(self.EDITOR_EMAIL) response_dict = self.get_json( '%s/%s' % (feconf.FEEDBACK_THREADLIST_URL_PREFIX, self.EXP_ID_1)) thread_id = response_dict['feedback_thread_dicts'][0]['thread_id'] thread_url = '%s/%s' % ( feconf.LEARNER_DASHBOARD_FEEDBACK_THREAD_DATA_URL, thread_id) response_dict = self.get_json(thread_url) messages_summary = response_dict['message_summary_list'][0] self.assertEqual(messages_summary['author_username'], self.EDITOR_USERNAME) self.assertTrue(messages_summary['author_picture_data_url'].startswith( 'data:image/png;')) self.assertFalse(messages_summary.get('suggestion_html')) self.assertFalse(messages_summary.get('current_content_html')) self.assertFalse(messages_summary.get('description')) new_content = state_domain.SubtitledHtml('content', 'new content html').to_dict() change_cmd = { 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': exp_domain.STATE_PROPERTY_CONTENT, 'state_name': 'Welcome!', 'new_value': new_content } suggestion_models.GeneralSuggestionModel.create( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.EXP_ID_1, 1, suggestion_models.STATUS_IN_REVIEW, self.editor_id, None, change_cmd, 'score category', thread_id) suggestion_thread = feedback_services.get_thread(thread_id) suggestion = suggestion_services.get_suggestion_by_id(thread_id) exploration = exp_services.get_exploration_by_id(self.EXP_ID_1) current_content_html = ( exploration.states[suggestion.change.state_name].content.html) response_dict = self.get_json(thread_url) messages_summary = response_dict['message_summary_list'][0] self.assertEqual(messages_summary['author_username'], self.EDITOR_USERNAME) self.assertTrue(messages_summary['author_picture_data_url'].startswith( 'data:image/png;')) self.assertEqual(messages_summary['suggestion_html'], 'new content html') self.assertEqual(messages_summary['current_content_html'], current_content_html) self.assertEqual(messages_summary['description'], suggestion_thread.subject) self.logout()
def test_reject_suggestion_successfully(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, self.score_category, 'test description', self.assigned_reviewer_id, self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) suggestion_services.reject_suggestion(suggestion, self.reviewer_id, 'reject review message') suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED) self.assertEqual(suggestion.final_reviewer_id, self.reviewer_id) thread_messages = feedback_services.get_messages(self.THREAD_ID) last_message = thread_messages[len(thread_messages) - 1] self.assertEqual(last_message.text, 'reject review message')
def post(self): payload = json.loads(self.request.body) exploration_id = payload['exploration_id'] thread_id = payload['thread_id'] exploration_rights = ( rights_manager.get_exploration_rights(exploration_id)) exploration = exp_fetchers.get_exploration_by_id(exploration_id) suggestion = suggestion_services.get_suggestion_by_id(thread_id) email_manager.send_suggestion_email( exploration.title, exploration.id, suggestion.author_id, exploration_rights.owner_ids)
def put(self, suggestion_id): suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if not suggestion: raise self.InvalidInputException( 'No suggestion found with given suggestion id') new_change = self.payload.get('change') change_cls = type(suggestion.change) change_object = change_cls(new_change) suggestion.pre_update_validate(change_object) suggestion.change = change_object summary_message = self.payload.get('summary_message') suggestion_services.resubmit_rejected_suggestion( suggestion, summary_message, self.user_id) self.render_json(self.values)
def test_resubmit_rejected_suggestion_success(self): with self.swap( feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.mock_generate_new_thread_id): with self.swap( exp_fetchers, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change, 'test description', self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) suggestion_services.reject_suggestion( suggestion, self.reviewer_id, 'reject review message') suggestion_services.resubmit_rejected_suggestion( suggestion, 'resubmit summary message', self.author_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual( suggestion.status, suggestion_models.STATUS_IN_REVIEW)
def get(self, thread_id): suggestion_id = thread_id suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) messages = [m.to_dict() for m in feedback_services.get_messages( thread_id)] message_ids = [message['message_id'] for message in messages] feedback_services.update_messages_read_by_the_user( self.user_id, thread_id, message_ids) self.values.update({ 'messages': messages, 'suggestion': suggestion.to_dict() if suggestion else None }) self.render_json(self.values)
def put(self, suggestion_id): """Handles PUT requests. Args: suggestion_id: str. The ID of the suggestion. """ suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) new_change = self.payload.get('change') change_cls = type(suggestion.change) change_object = change_cls(new_change) summary_message = self.payload.get('summary_message') suggestion_services.resubmit_rejected_suggestion( suggestion_id, summary_message, self.user_id, change_object) self.render_json(self.values)
def test_accept_suggestion_successfully(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', self.assigned_reviewer_id, self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.swap(exp_services, 'update_exploration', self.check_commit_message): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): with self.swap(suggestion_registry.SuggestionEditStateContent, 'pre_accept_validate', self.null_function): with self.swap( suggestion_registry.SuggestionEditStateContent, 'get_change_list_for_accepting_suggestion', self.null_function): suggestion_services.accept_suggestion( suggestion, self.reviewer_id, self.COMMIT_MESSAGE, 'review message') suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED) self.assertEqual(suggestion.final_reviewer_id, self.reviewer_id) self.assertEqual(suggestion.assigned_reviewer_id, None) thread_messages = feedback_services.get_messages(self.THREAD_ID) last_message = thread_messages[len(thread_messages) - 1] self.assertEqual(last_message.text, 'review message')
def test_reject_suggestion_handled_suggestion_failure(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', self.assigned_reviewer_id, self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) suggestion.status = suggestion_models.STATUS_ACCEPTED suggestion_services._update_suggestion(suggestion) # pylint: disable=protected-access with self.assertRaisesRegexp( Exception, 'The suggestion has already been accepted/rejected.'): suggestion_services.reject_suggestion(suggestion, self.reviewer_id, 'reject review message') suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED) suggestion.status = suggestion_models.STATUS_REJECTED suggestion_services._update_suggestion(suggestion) # pylint: disable=protected-access with self.assertRaisesRegexp( Exception, 'The suggestion has already been accepted/rejected.'): suggestion_services.reject_suggestion(suggestion, self.reviewer_id, 'reject review message') suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED)
def test_accept_suggestion_to_topic(self): self.login(self.ADMIN_EMAIL) csrf_token = self.get_new_csrf_token() suggestion_to_accept = self.get_json( '%s?author_id=%s' % ( feconf.SUGGESTION_LIST_URL_PREFIX, self.author_id))['suggestions'][0] suggestion = suggestion_services.get_suggestion_by_id( suggestion_to_accept['suggestion_id']) self.assertEqual( suggestion.status, suggestion_models.STATUS_IN_REVIEW) csrf_token = self.get_new_csrf_token() with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True): self.put_json('%s/topic/%s/%s' % ( feconf.SUGGESTION_ACTION_URL_PREFIX, suggestion_to_accept['target_id'], suggestion_to_accept['suggestion_id']), { 'action': u'accept', 'commit_message': u'commit message', 'review_message': u'Accepted!', 'skill_id': self.skill_id }, csrf_token=csrf_token) suggestion = suggestion_services.get_suggestion_by_id( suggestion_to_accept['suggestion_id']) self.assertEqual( suggestion.status, suggestion_models.STATUS_ACCEPTED) self.logout()
def post(self, thread_id): suggestion = suggestion_services.get_suggestion_by_id(thread_id) text = self.payload.get('text') updated_status = self.payload.get('updated_status') if not text and not updated_status: raise self.InvalidInputException( 'Text for the message must be specified.') if suggestion and updated_status: raise self.InvalidInputException( 'Suggestion thread status cannot be changed manually.') feedback_services.create_message( thread_id, self.user_id, updated_status, self.payload.get('updated_subject'), text) self.render_json(self.values)
def test_accept_suggestion_no_commit_message_failure(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, self.score_category, 'test description', self.assigned_reviewer_id, self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.assertRaisesRegexp(Exception, 'Commit message cannot be empty.'): suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.EMPTY_COMMIT_MESSAGE, None)
def create_translation_suggestion_for_exploration_0_and_verify(self): """Creates a translation suggestion for exploration 0 and performs basic assertions. """ with self.swap(feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.mock_generate_new_thread_id_for_suggestion): suggestion_services.create_suggestion( feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT, feconf.ENTITY_TYPE_EXPLORATION, self.suggestion_target_id, self.suggestion_target_version_at_submission, self.owner_id, self.suggestion_change, 'test description') suggestion = suggestion_services.get_suggestion_by_id(self.THREAD_ID) self.assertIsNotNone(suggestion) self.assertEqual(suggestion.status, suggestion_models.STATUS_IN_REVIEW)
def test_create_new_suggestion_successfully(self): expected_suggestion_dict = { 'suggestion_id': 'exploration.exp1.thread_1', 'suggestion_type': (suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT), 'target_type': suggestion_models.TARGET_TYPE_EXPLORATION, 'target_id': self.target_id, 'target_version_at_submission': self.target_version_at_submission, 'status': suggestion_models.STATUS_IN_REVIEW, 'author_name': 'author', 'final_reviewer_id': self.reviewer_id, 'assigned_reviewer_id': self.assigned_reviewer_id, 'change_cmd': { 'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY, 'property_name': exp_domain.STATE_PROPERTY_CONTENT, 'state_name': 'state_1', 'new_value': 'new suggestion content', 'old_value': None }, 'score_category': self.score_category } with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', self.assigned_reviewer_id, self.reviewer_id) observed_suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) self.assertDictContainsSubset(expected_suggestion_dict, observed_suggestion.to_dict())
def put(self, target_id, suggestion_id): """Handles PUT requests. Args: target_id: str. The ID of the suggestion target. suggestion_id: str. The ID of the suggestion. """ if suggestion_id.split('.')[0] != feconf.ENTITY_TYPE_SKILL: raise self.InvalidInputException( 'This handler allows actions only on suggestions to skills.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The skill id provided does not match the skill id present as ' 'part of the suggestion_id') action = self.payload.get('action') if action == constants.ACTION_ACCEPT_SUGGESTION: # Question suggestions do not use commit messages. suggestion_services.accept_suggestion( suggestion_id, self.user_id, 'UNUSED_COMMIT_MESSAGE', self.payload.get('review_message')) suggestion = suggestion_services.get_suggestion_by_id( suggestion_id) target_entity_html_list = ( suggestion.get_target_entity_html_strings()) target_image_filenames = ( html_cleaner.get_image_filenames_from_html_strings( target_entity_html_list)) fs_services.copy_images(suggestion.target_type, suggestion.target_id, feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS, suggestion.target_id, target_image_filenames) elif action == constants.ACTION_REJECT_SUGGESTION: suggestion_services.reject_suggestion( suggestion_id, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def post(self, suggestion_id): """Handles PUT requests. Raises: InvalidInputException. The suggestion is already handled. InvalidInputException. The 'skill_difficulty' parameter is missing. InvalidInputException. The 'skill_difficulty' is not a decimal. InvalidInputException. The 'question_state_data' parameter is missing. InvalidInputException. The 'question_state_data' parameter is invalid. """ suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if suggestion.is_handled: raise self.InvalidInputException( 'The suggestion with id %s has been accepted or rejected' % (suggestion_id)) if self.payload.get('skill_difficulty') is None: raise self.InvalidInputException( 'The parameter \'skill_difficulty\' is missing.') if not isinstance(self.payload.get('skill_difficulty'), float): raise self.InvalidInputException( 'The parameter \'skill_difficulty\' should be a decimal.') if self.payload.get('question_state_data') is None: raise self.InvalidInputException( 'The parameter \'question_state_data\' is missing.') question_state_data_obj = state_domain.State.from_dict( self.payload.get('question_state_data')) question_state_data_obj.validate(None, False) updated_suggestion = suggestion_services.update_question_suggestion( suggestion_id, self.payload.get('skill_difficulty'), self.payload.get('question_state_data')) new_image_filenames = (utils.compute_list_difference( updated_suggestion.get_new_image_filenames_added_in_suggestion(), suggestion.get_new_image_filenames_added_in_suggestion())) _upload_suggestion_images(self.request, updated_suggestion, new_image_filenames) self.render_json(self.values)
def put(self, target_id, suggestion_id): """Handles PUT requests. Args: target_id: str. The ID of the suggestion target. suggestion_id: str. The ID of the suggestion. """ if (suggestion_id.split('.')[0] != feconf.ENTITY_TYPE_EXPLORATION): raise self.InvalidInputException( 'This handler allows actions only' ' on suggestions to explorations.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The exploration id provided does not match the exploration id ' 'present as part of the suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if suggestion.author_id == self.user_id: raise self.UnauthorizedUserException( 'You cannot accept/reject your own suggestion.') if action == constants.ACTION_ACCEPT_SUGGESTION: commit_message = self.payload.get('commit_message') if (commit_message is not None and len(commit_message) > constants.MAX_COMMIT_MESSAGE_LENGTH): raise self.InvalidInputException( 'Commit messages must be at most %s characters long.' % constants.MAX_COMMIT_MESSAGE_LENGTH) suggestion_services.accept_suggestion( suggestion_id, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == constants.ACTION_REJECT_SUGGESTION: suggestion_services.reject_suggestion( suggestion_id, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def get(self, thread_id): if constants.USE_NEW_SUGGESTION_FRAMEWORK: suggestion = suggestion_services.get_suggestion_by_id(thread_id) else: suggestion = feedback_services.get_suggestion(thread_id) messages = [ m.to_dict() for m in feedback_services.get_messages(thread_id) ] message_ids = [message['message_id'] for message in messages] feedback_services.update_messages_read_by_the_user( self.user_id, thread_id, message_ids) self.values.update({ 'messages': messages, 'suggestion': suggestion.to_dict() if suggestion else None }) self.render_json(self.values)
def get(self, thread_id): if not constants.ENABLE_GENERALIZED_FEEDBACK_THREADS: suggestion_id = 'exploration.%s' % thread_id else: suggestion_id = thread_id suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) messages = [ m.to_dict() for m in feedback_services.get_messages(thread_id) ] message_ids = [message['message_id'] for message in messages] feedback_services.update_messages_read_by_the_user( self.user_id, thread_id, message_ids) self.values.update({ 'messages': messages, 'suggestion': suggestion.to_dict() if suggestion else None }) self.render_json(self.values)
def test_resubmit_rejected_suggestion_failure(self): with self.swap(feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change, 'test description', self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.assertRaisesRegexp(Exception, 'Summary message cannot be empty.'): suggestion_services.resubmit_rejected_suggestion( suggestion, '', self.author_id) with self.assertRaisesRegexp(Exception, 'The suggestion is not yet handled.'): suggestion_services.resubmit_rejected_suggestion( suggestion, 'resubmit summary message', self.author_id)
def test_create_and_accept_suggestion(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.EXP_ID, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', None) suggestion_id = 'exploration.' + self.THREAD_ID suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) suggestion_services.accept_suggestion(suggestion, self.reviewer_id, self.COMMIT_MESSAGE, None) exploration = exp_services.get_exploration_by_id(self.EXP_ID) self.assertEqual(exploration.states['State 1'].content.html, 'new content') self.assertEqual(suggestion.status, suggestion_models.STATUS_ACCEPTED)
def get(self, thread_id): suggestion_id = thread_id suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) message_dicts = [ message.to_dict() for message in feedback_services.get_messages(thread_id) ] message_ids = [message['message_id'] for message in message_dicts] if self.user_id: feedback_services.update_messages_read_by_the_user( self.user_id, thread_id, message_ids) self.values.update({ 'messages': replace_user_id_with_username_in_dict( message_dicts, [('author_id', 'author_username')]), 'suggestion': suggestion.to_dict() if suggestion else None }) self.render_json(self.values)
def put(self, target_id, suggestion_id): if not constants.USE_NEW_SUGGESTION_FRAMEWORK: raise self.PageNotFoundException if len(suggestion_id.split('.')) != 3: raise self.InvalidInputException('Invalid format for suggestion_id.' ' It must contain 3 parts' ' separated by \'.\'') if ( suggestion_id.split('.')[0] != suggestion_models.TARGET_TYPE_EXPLORATION): raise self.InvalidInputException('This handler allows actions only' ' on suggestions to explorations.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException('The exploration id provided does ' 'not match the exploration id ' 'present as part of the ' 'suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if suggestion.author_id == self.user_id: raise self.UnauthorizedUserException('You cannot accept/reject your' ' own suggestion.') if action == suggestion_models.ACTION_TYPE_ACCEPT: suggestion_services.accept_suggestion( suggestion, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == suggestion_models.ACTION_TYPE_REJECT: suggestion_services.reject_suggestion( suggestion, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def post(self, suggestion_id): """Handles PUT requests. Raises: InvalidInputException. The suggestion is already handled. InvalidInputException. The 'skill_difficulty' parameter is missing. InvalidInputException. The 'skill_difficulty' is not a decimal. InvalidInputException. The 'question_state_data' parameter is missing. InvalidInputException. The 'question_state_data' parameter is invalid. """ suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if suggestion.is_handled: raise self.InvalidInputException( 'The suggestion with id %s has been accepted or rejected' % suggestion_id) if self.payload.get('skill_difficulty') is None: raise self.InvalidInputException( 'The parameter \'skill_difficulty\' is missing.') if not isinstance(self.payload.get('skill_difficulty'), (float, int)): raise self.InvalidInputException( 'The parameter \'skill_difficulty\' should be a decimal.') if self.payload.get('question_state_data') is None: raise self.InvalidInputException( 'The parameter \'question_state_data\' is missing.') question_state_data_obj = state_domain.State.from_dict( self.payload.get('question_state_data')) question_state_data_obj.validate(None, False) suggestion_services.update_question_suggestion( suggestion_id, self.payload.get('skill_difficulty'), self.payload.get('question_state_data')) self.render_json(self.values)
def post(self, thread_id): suggestion = suggestion_services.get_suggestion_by_id(thread_id) text = self.normalized_payload.get('text') updated_status = self.normalized_payload.get('updated_status') updated_subject = self.normalized_payload.get('updated_subject') if suggestion and updated_status: raise self.InvalidInputException( 'Suggestion thread status cannot be changed manually.') messages = feedback_services.get_messages(thread_id) new_message = feedback_services.create_message( thread_id, self.user_id, updated_status, updated_subject, text) # Currently we are manually adding new message to the messages list as # the feedback_services.get_messages is not returning a correct list of # messages after adding new message model to the datastore because of an # unknown reason. messages.append(new_message) self.render_json({ 'messages': [message.to_dict() for message in messages] })
def put(self, target_id, suggestion_id): if not constants.ENABLE_NEW_STRUCTURES: raise self.PageNotFoundException if len(suggestion_id.split('.')) != 3: raise self.InvalidInputException( 'Invalid format for suggestion_id. It must contain 3 parts' ' separated by \'.\'') if suggestion_id.split('.')[0] != suggestion_models.TARGET_TYPE_TOPIC: raise self.InvalidInputException( 'This handler allows actions only on suggestions to topics.') if suggestion_id.split('.')[1] != target_id: raise self.InvalidInputException( 'The topic id provided does not match the topic id present as ' 'part of the suggestion_id') action = self.payload.get('action') suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) if action == suggestion_models.ACTION_TYPE_ACCEPT: if ( suggestion.suggestion_type == suggestion_models.SUGGESTION_TYPE_ADD_QUESTION): # The skill_id is passed only at the time of accepting the # suggestion. skill_id = self.payload.get('skill_id') suggestion.change.skill_id = skill_id suggestion_services.accept_suggestion( suggestion, self.user_id, self.payload.get('commit_message'), self.payload.get('review_message')) elif action == suggestion_models.ACTION_TYPE_REJECT: suggestion_services.reject_suggestion( suggestion, self.user_id, self.payload.get('review_message')) else: raise self.InvalidInputException('Invalid action.') self.render_json(self.values)
def test_create_and_reject_suggestion(self): with self.swap(feedback_models.FeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.EXP_ID, self.target_version_at_submission, self.author_id, self.change_cmd, 'test description', None) suggestion_id = 'exploration.' + self.THREAD_ID suggestion = suggestion_services.get_suggestion_by_id(suggestion_id) suggestion_services.reject_suggestion(suggestion, self.reviewer_id, 'Reject message') exploration = exp_services.get_exploration_by_id(self.EXP_ID) thread_messages = feedback_services.get_messages(self.THREAD_ID) last_message = thread_messages[len(thread_messages) - 1] self.assertEqual(last_message.text, 'Reject message') self.assertEqual(exploration.states['State 1'].content.html, 'old content') self.assertEqual(suggestion.status, suggestion_models.STATUS_REJECTED)
def test_accept_suggestion_no_commit_message_failure(self): with self.swap(feedback_models.GeneralFeedbackThreadModel, 'generate_new_thread_id', self.generate_thread_id): with self.swap(exp_services, 'get_exploration_by_id', self.mock_get_exploration_by_id): with self.swap(feconf, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True): suggestion_services.create_suggestion( suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT, suggestion_models.TARGET_TYPE_EXPLORATION, self.target_id, self.target_version_at_submission, self.author_id, self.change, 'test description', self.reviewer_id) suggestion = suggestion_services.get_suggestion_by_id( self.suggestion_id) with self.assertRaisesRegexp(Exception, 'Commit message cannot be empty.'): with self.swap(feconf, 'ENABLE_GENERALIZED_FEEDBACK_THREADS', True): suggestion_services.accept_suggestion( suggestion, self.reviewer_id, self.EMPTY_COMMIT_MESSAGE, None)
def map(item): suggestion = suggestion_services.get_suggestion_by_id(item.id) try: suggestion.validate() except Exception as e: logging.error( 'Suggestion %s failed validation: %s' % (item.id, e)) yield ( SuggestionMathMigrationOneOffJob._ERROR_KEY_BEFORE_MIGRATION, 'Suggestion %s failed validation: %s' % (item.id, e)) return html_string_list = suggestion.get_all_html_content_strings() html_string = ''.join(html_string_list) error_list = ( html_validation_service. validate_math_tags_in_html_with_attribute_math_content(html_string)) # Migrate the suggestion only if the suggestions have math-tags with # old schema. if len(error_list) > 0: suggestion.convert_html_in_suggestion_change( html_validation_service.add_math_content_to_math_rte_components) try: suggestion.validate() except Exception as e: logging.error( 'Suggestion %s failed validation after migration: %s' % ( item.id, e)) yield ( SuggestionMathMigrationOneOffJob._ERROR_KEY_AFTER_MIGRATION, 'Suggestion %s failed validation: %s' % ( item.id, e)) return item.change_cmd = suggestion.change.to_dict() item.update_timestamps(update_last_updated_time=False) item.put() yield ('suggestion_migrated', 1)
def test_migration_job_does_not_convert_up_to_date_suggestion(self): suggestion_change = { 'cmd': (question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION), 'question_dict': { 'question_state_data': self._create_valid_question_data('default_state').to_dict(), 'language_code': 'en', 'question_state_data_schema_version': (feconf.CURRENT_STATE_SCHEMA_VERSION), 'linked_skill_ids': [self.skill_id], 'inapplicable_skill_misconception_ids': [] }, 'skill_id': self.skill_id, 'skill_difficulty': 0.3 } suggestion = suggestion_services.create_suggestion( feconf.SUGGESTION_TYPE_ADD_QUESTION, feconf.ENTITY_TYPE_SKILL, self.skill_id, 1, self.albert_id, suggestion_change, 'test description') self.assertEqual( suggestion.change. question_dict['question_state_data_schema_version'], feconf.CURRENT_STATE_SCHEMA_VERSION) expected_output = [u'[u\'SUCCESS\', 1]'] self._run_job_and_verify_output(expected_output) updated_suggestion = suggestion_services.get_suggestion_by_id( suggestion.suggestion_id) self.assertEqual( updated_suggestion.change. question_dict['question_state_data_schema_version'], feconf.CURRENT_STATE_SCHEMA_VERSION)