def _upload_suggestion_images(files, suggestion, filenames): """Saves a suggestion's images to storage. Args: files: dict. Files containing a mapping of image filename to image blob. suggestion: BaseSuggestion. The suggestion for which images are being uploaded. filenames: list(str). The image filenames. """ suggestion_image_context = suggestion.image_context # TODO(#10513): Find a way to save the images before the suggestion is # created. for filename in filenames: image = files.get(filename) image = base64.decodebytes(image.encode('utf-8')) file_format = (image_validation_services.validate_image_and_filename( image, filename)) image_is_compressible = (file_format in feconf.COMPRESSIBLE_IMAGE_FORMATS) fs_services.save_original_and_compressed_versions_of_image( filename, suggestion_image_context, suggestion.target_id, image, 'image', image_is_compressible) 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, suggestion_image_context, suggestion.target_id, target_image_filenames)
def test_copy_images(self): with utils.open_file(os.path.join(feconf.TESTS_DATA_DIR, 'img.png'), 'rb', encoding=None) as f: original_image_content = f.read() fs_services.save_original_and_compressed_versions_of_image( self.FILENAME, 'exploration', self.EXPLORATION_ID, original_image_content, 'image', True) destination_fs = fs_domain.AbstractFileSystem( fs_domain.GcsFileSystem(feconf.ENTITY_TYPE_QUESTION, 'question_id1')) self.assertFalse(destination_fs.isfile('image/%s' % self.FILENAME)) self.assertFalse( destination_fs.isfile('image/%s' % self.COMPRESSED_IMAGE_FILENAME)) self.assertFalse( destination_fs.isfile('image/%s' % self.MICRO_IMAGE_FILENAME)) fs_services.copy_images(feconf.ENTITY_TYPE_EXPLORATION, self.EXPLORATION_ID, feconf.ENTITY_TYPE_QUESTION, 'question_id1', ['image.png']) self.assertTrue(destination_fs.isfile('image/%s' % self.FILENAME)) self.assertTrue( destination_fs.isfile('image/%s' % self.COMPRESSED_IMAGE_FILENAME)) self.assertTrue( destination_fs.isfile('image/%s' % self.MICRO_IMAGE_FILENAME))
def map(item): if item.deleted: yield (FixQuestionImagesStorageOneOffJob._DELETED_KEY, 1) return question = question_fetchers.get_question_from_model(item) html_list = question.question_state_data.get_all_html_content_strings() image_filenames = html_cleaner.get_image_filenames_from_html_strings( html_list) file_system_class = fs_services.get_entity_file_system_class() question_fs = fs_domain.AbstractFileSystem(file_system_class( feconf.ENTITY_TYPE_QUESTION, question.id)) success_count = 0 # For each image filename, check if it exists in the correct path. If # not, copy the image file to the correct path else continue. for image_filename in image_filenames: if not question_fs.isfile('image/%s' % image_filename): for skill_id in question.linked_skill_ids: skill_fs = fs_domain.AbstractFileSystem(file_system_class( feconf.ENTITY_TYPE_SKILL, skill_id)) if skill_fs.isfile('image/%s' % image_filename): fs_services.copy_images( feconf.ENTITY_TYPE_SKILL, skill_id, feconf.ENTITY_TYPE_QUESTION, question.id, [image_filename]) success_count += 1 break if success_count > 0: yield ( FixQuestionImagesStorageOneOffJob._IMAGE_COPIED, '%s image paths were fixed for question id %s with ' 'linked_skill_ids: %r' % ( success_count, question.id, question.linked_skill_ids))
def accept(self, unused_commit_message): """Accepts the suggestion. Args: unused_commit_message: str. This parameter is passed in for consistency with the existing suggestions. As a default commit message is used in the add_question function, the arg is unused. """ question_dict = self.change.question_dict question_dict['version'] = 1 question_dict['id'] = (question_services.get_new_question_id()) html_list = self.get_all_html_content_strings() filenames = ( html_cleaner.get_image_filenames_from_html_strings(html_list)) image_context = fs_services.get_image_context_for_suggestion_target( self.target_type) fs_services.copy_images(image_context, self.target_id, feconf.ENTITY_TYPE_QUESTION, self.target_id, filenames) question_dict['linked_skill_ids'] = [self.change.skill_id] question = question_domain.Question.from_dict(question_dict) question.validate() question_services.add_question(self.author_id, question) skill = skill_fetchers.get_skill_by_id(self.change.skill_id, strict=False) if skill is None: raise utils.ValidationError( 'The skill with the given id doesn\'t exist.') question_services.create_new_question_skill_link( self.author_id, question_dict['id'], self.change.skill_id, self._get_skill_difficulty())
def _copy_new_images_to_target_entity_storage(self): """Copy newly added images in suggestion to the target entity storage. """ new_image_filenames = self.get_new_image_filenames_added_in_suggestion() fs_services.copy_images( self.image_context, self.target_id, self.target_type, self.target_id, new_image_filenames)
def post(self): try: # The create_suggestion method needs to be run in transaction as it # generates multiple connected models (suggestion, feedback thread, # feedback message etc.) and all these models needs to be created # together, in a batch. suggestion = transaction_services.run_in_transaction( suggestion_services.create_suggestion, self.payload.get('suggestion_type'), self.payload.get('target_type'), self.payload.get('target_id'), self.payload.get('target_version_at_submission'), self.user_id, self.payload.get('change'), self.payload.get('description')) except utils.ValidationError as e: raise self.InvalidInputException(e) # TODO(#10513) : Find a way to save the images before the suggestion is # created. suggestion_image_context = suggestion.image_context # For suggestion which doesn't need images for rendering the # image_context is set to None. if suggestion_image_context is None: self.render_json(self.values) return new_image_filenames = ( suggestion.get_new_image_filenames_added_in_suggestion()) for filename in new_image_filenames: image = self.request.get(filename) if not image: logging.error( 'Image not provided for file with name %s when the ' ' suggestion with target id %s was created.' % (filename, suggestion.target_id)) raise self.InvalidInputException( 'No image data provided for file with name %s.' % (filename)) try: file_format = ( image_validation_services.validate_image_and_filename( image, filename)) except utils.ValidationError as e: raise self.InvalidInputException('%s' % (e)) image_is_compressible = (file_format in feconf.COMPRESSIBLE_IMAGE_FORMATS) fs_services.save_original_and_compressed_versions_of_image( filename, suggestion_image_context, suggestion.target_id, image, 'image', image_is_compressible) 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, suggestion_image_context, suggestion.target_id, target_image_filenames) self.render_json(self.values)
def post(self): """Handles POST requests.""" if (self.payload.get('suggestion_type') == feconf.SUGGESTION_TYPE_EDIT_STATE_CONTENT): raise self.InvalidInputException( 'Content suggestion submissions are no longer supported.') try: suggestion = suggestion_services.create_suggestion( self.payload.get('suggestion_type'), self.payload.get('target_type'), self.payload.get('target_id'), self.payload.get('target_version_at_submission'), self.user_id, self.payload.get('change'), self.payload.get('description')) except utils.ValidationError as e: raise self.InvalidInputException(e) # TODO(#10513) : Find a way to save the images before the suggestion is # created. suggestion_image_context = suggestion.image_context new_image_filenames = ( suggestion.get_new_image_filenames_added_in_suggestion()) for filename in new_image_filenames: image = self.request.get(filename) if not image: logging.exception( 'Image not provided for file with name %s when the ' ' suggestion with target id %s was created.' % ( filename, suggestion.target_id)) raise self.InvalidInputException( 'No image data provided for file with name %s.' % (filename)) try: file_format = ( image_validation_services.validate_image_and_filename( image, filename)) except utils.ValidationError as e: raise self.InvalidInputException('%s' % (e)) image_is_compressible = ( file_format in feconf.COMPRESSIBLE_IMAGE_FORMATS) fs_services.save_original_and_compressed_versions_of_image( filename, suggestion_image_context, suggestion.target_id, image, 'image', image_is_compressible) 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, suggestion_image_context, suggestion.target_id, target_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_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 _upload_suggestion_images(request, suggestion, filenames): """Saves a suggestion's images to storage. Args: request: webapp2.Request. Request object containing a mapping of image filename to image blob. suggestion: BaseSuggestion. The suggestion for which images are being uploaded. filenames: list(str). The image filenames. """ suggestion_image_context = suggestion.image_context # TODO(#10513) : Find a way to save the images before the suggestion is # created. for filename in filenames: image = request.get(filename) if not image: logging.exception( 'Image not provided for file with name %s when the ' ' suggestion with target id %s was created.' % (filename, suggestion.target_id)) raise base.BaseHandler.InvalidInputException( 'No image data provided for file with name %s.' % (filename)) try: file_format = ( image_validation_services.validate_image_and_filename( image, filename)) except utils.ValidationError as e: raise base.BaseHandler.InvalidInputException('%s' % (e)) image_is_compressible = (file_format in feconf.COMPRESSIBLE_IMAGE_FORMATS) fs_services.save_original_and_compressed_versions_of_image( filename, suggestion_image_context, suggestion.target_id, image, 'image', image_is_compressible) 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, suggestion_image_context, suggestion.target_id, target_image_filenames)