Repository: oppia/oppia

Issue:
Unable to submit image region question
<!--
  - Thanks for taking the time to report a bug in the Oppia project.
  - Before filing a new issue, please do a quick search to check that it hasn't
  - already been filed on the [issue tracker](https://github.com/oppia/oppia/issues)._
  -->

**Describe the bug**
Can't create image region question suggestion.

**To Reproduce**
Steps to reproduce the behavior:
 1. Go to submit question tab
 2. Click on a card 
 3. Select image region
 4. upload image or draw an image
 5. Click use this image
 6. See console error

**Screenshots**
<img width="1277" alt="Screen Shot 2021-11-12 at 2 37 35 AM" src="https://user-images.githubusercontent.com/35385366/141454330-45a7690d-4208-4474-8472-e1b30a98a109.png">



**Additional context**
Add any other context about the problem here.

Unable to submit image region question
<!--
  - Thanks for taking the time to report a bug in the Oppia project.
  - Before filing a new issue, please do a quick search to check that it hasn't
  - already been filed on the [issue tracker](https://github.com/oppia/oppia/issues)._
  -->

**Describe the bug**
Can't create image region question suggestion.

**To Reproduce**
Steps to reproduce the behavior:
 1. Go to submit question tab
 2. Click on a card 
 3. Select image region
 4. upload image or draw an image
 5. Click use this image
 6. See console error

**Screenshots**
<img width="1277" alt="Screen Shot 2021-11-12 at 2 37 35 AM" src="https://user-images.githubusercontent.com/35385366/141454330-45a7690d-4208-4474-8472-e1b30a98a109.png">



**Additional context**
Add any other context about the problem here.



Discussion:
@sagangwee PTAL
@kevintab95 Can you take a look at this? This seems like an issue with the question editor.
Confirming that this issue is question related. It seems like the image context (which determines the location where the image gets stored) is not being set correctly.
@Rijuta-s, @krishita30j do you guys think you could look into this since you are working on practice questions?
Reassigning to @chris7716. (Rijuta and Krishita are away this week.)
@sagangwee PTAL
@kevintab95 Can you take a look at this? This seems like an issue with the question editor.
Confirming that this issue is question related. It seems like the image context (which determines the location where the image gets stored) is not being set correctly.
@Rijuta-s, @krishita30j do you guys think you could look into this since you are working on practice questions?
Reassigning to @chris7716. (Rijuta and Krishita are away this week.)

Resolving patch:
diff --git a/core/controllers/acl_decorators.py b/core/controllers/acl_decorators.py
--- a/core/controllers/acl_decorators.py
+++ b/core/controllers/acl_decorators.py
@@ -2566,6 +2566,45 @@ def test_can_edit_skill(self, skill_id, **kwargs):
     return test_can_edit_skill
 
 
+def can_submit_images_to_questions(handler):
+    """Decorator to check whether the user can submit images to questions.
+
+    Args:
+        handler: function. The function to be decorated.
+
+    Returns:
+        function. The newly decorated function that now also checks if
+        the user has permission to submit a question.
+    """
+    def test_can_submit_images_to_questions(self, skill_id, **kwargs):
+        """Test to see if user can submit images to questions.
+
+        Args:
+            skill_id: str. The skill ID.
+            **kwargs: *. Keyword arguments.
+
+        Returns:
+            *. The return value of the decorated function.
+
+        Raises:
+            NotLoggedInException. The user is not logged in.
+            PageNotFoundException. The given page cannot be found.
+            UnauthorizedUserException. The user does not have the
+                credentials to edit the given skill.
+        """
+        if not self.user_id:
+            raise base.UserFacingExceptions.NotLoggedInException
+
+        if role_services.ACTION_SUGGEST_CHANGES in self.user.actions:
+            return handler(self, skill_id, **kwargs)
+        else:
+            raise self.UnauthorizedUserException(
+                'You do not have credentials to submit images to questions.')
+
+    test_can_submit_images_to_questions.__wrapped__ = True
+    return test_can_submit_images_to_questions
+
+
 def can_delete_skill(handler):
     """Decorator to check whether the user can delete a skill.
 
@@ -3407,22 +3446,32 @@ def test_can_edit_entity(self, entity_type, entity_id, **kwargs):
         # for the corresponding decorators.
         reduced_handler = functools.partial(
             arg_swapped_handler, entity_type)
-        if entity_type == feconf.ENTITY_TYPE_EXPLORATION:
-            return can_edit_exploration(reduced_handler)(
-                self, entity_id, **kwargs)
-        elif entity_type == feconf.ENTITY_TYPE_QUESTION:
-            return can_edit_question(reduced_handler)(self, entity_id, **kwargs)
-        elif entity_type == feconf.ENTITY_TYPE_TOPIC:
-            return can_edit_topic(reduced_handler)(self, entity_id, **kwargs)
-        elif entity_type == feconf.ENTITY_TYPE_SKILL:
-            return can_edit_skill(reduced_handler)(self, entity_id, **kwargs)
-        elif entity_type == feconf.ENTITY_TYPE_STORY:
-            return can_edit_story(reduced_handler)(self, entity_id, **kwargs)
-        elif entity_type == feconf.ENTITY_TYPE_BLOG_POST:
-            return (
-                can_edit_blog_post(reduced_handler)(self, entity_id, **kwargs))
-        else:
+        functions = {
+            feconf.ENTITY_TYPE_EXPLORATION: lambda entity_id: (
+                can_edit_exploration(reduced_handler)(
+                    self, entity_id, **kwargs)),
+            feconf.ENTITY_TYPE_QUESTION: lambda entity_id: (
+                can_edit_question(reduced_handler)(
+                    self, entity_id, **kwargs)),
+            feconf.ENTITY_TYPE_TOPIC: lambda entity_id: (
+                can_edit_topic(reduced_handler)(
+                    self, entity_id, **kwargs)),
+            feconf.ENTITY_TYPE_SKILL: lambda entity_id: (
+                can_edit_skill(reduced_handler)(
+                    self, entity_id, **kwargs)),
+            feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS: lambda entity_id: (
+                can_submit_images_to_questions(reduced_handler)(
+                    self, entity_id, **kwargs)),
+            feconf.ENTITY_TYPE_STORY: lambda entity_id: (
+                can_edit_story(reduced_handler)(
+                    self, entity_id, **kwargs)),
+            feconf.ENTITY_TYPE_BLOG_POST: lambda entity_id: (
+                can_edit_blog_post(reduced_handler)(
+                    self, entity_id, **kwargs))
+        }
+        if entity_type not in dict.keys(functions):
             raise self.PageNotFoundException
+        return functions[entity_type](entity_id)
 
     test_can_edit_entity.__wrapped__ = True
 
diff --git a/core/controllers/suggestion.py b/core/controllers/suggestion.py
--- a/core/controllers/suggestion.py
+++ b/core/controllers/suggestion.py
@@ -71,10 +71,18 @@ def post(self):
             self.render_json(self.values)
             return
 
-        _upload_suggestion_images(
-            self.request,
-            suggestion,
-            suggestion.get_new_image_filenames_added_in_suggestion())
+        # Images for question suggestions are already stored in the server
+        # before actually the question is submitted. Therefore no need of
+        # uploading images when the suggestion type is 'add_question'. But this
+        # is not good, since when the user cancels a question suggestion after
+        # adding an image, there is no method to remove the uploaded image.
+        # See more - https://github.com/oppia/oppia/issues/14298
+        if self.payload.get(
+            'suggestion_type') != (feconf.SUGGESTION_TYPE_ADD_QUESTION):
+            _upload_suggestion_images(
+                self.request,
+                suggestion,
+                suggestion.get_new_image_filenames_added_in_suggestion())
 
         self.render_json(self.values)
 
@@ -177,6 +185,18 @@ def put(self, target_id, suggestion_id):
             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'))
@@ -388,21 +408,11 @@ def post(self, suggestion_id):
             self.payload.get('question_state_data'))
         question_state_data_obj.validate(None, False)
 
-        updated_suggestion = suggestion_services.update_question_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)
 
 
diff --git a/core/domain/suggestion_registry.py b/core/domain/suggestion_registry.py
--- a/core/domain/suggestion_registry.py
+++ b/core/domain/suggestion_registry.py
@@ -895,6 +895,16 @@ def accept(self, unused_commit_message):
         # Images need to be stored in the storage path corresponding to the
         # question.
         new_image_filenames = self.get_new_image_filenames_added_in_suggestion()
+
+        # Image for interaction with Image Region is not included as an html
+        # string. This image is included in the imagePath in customization args.
+        # Other interactions such as Item Selection, Multiple Choice, Drag and
+        # Drop Sort have ck editor that includes the images of the interactions
+        # so that references for those images are included as html strings.
+        if question.question_state_data.interaction.id == 'ImageClickInput':
+            new_image_filenames.append(
+                question.question_state_data.interaction.customization_args[
+                    'imageAndRegions'].value['imagePath'])
         fs_services.copy_images(
             self.image_context, self.target_id, feconf.ENTITY_TYPE_QUESTION,
             question_dict['id'], new_image_filenames)


Test patch:
diff --git a/core/controllers/acl_decorators_test.py b/core/controllers/acl_decorators_test.py
--- a/core/controllers/acl_decorators_test.py
+++ b/core/controllers/acl_decorators_test.py
@@ -4737,6 +4737,38 @@ def test_can_edit_skill(self):
             self.assertEqual(response['entity_type'], 'skill')
         self.logout()
 
+    def test_can_submit_images_to_questions(self):
+        self.login(self.CURRICULUM_ADMIN_EMAIL)
+        skill_id = skill_services.get_new_skill_id()
+        self.save_new_skill(skill_id, self.admin_id, description='Description')
+        with self.swap(self, 'testapp', self.mock_testapp):
+            response = self.get_json('/mock_edit_entity/%s/%s' % (
+                feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS, skill_id))
+            self.assertEqual(response['entity_id'], skill_id)
+            self.assertEqual(response['entity_type'], 'question_suggestions')
+        self.logout()
+
+    def test_unauthenticated_users_cannot_submit_images_to_questions(self):
+        skill_id = skill_services.get_new_skill_id()
+        self.save_new_skill(skill_id, self.admin_id, description='Description')
+        with self.swap(self, 'testapp', self.mock_testapp):
+            self.get_json('/mock_edit_entity/%s/%s' % (
+                feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS, skill_id),
+                expected_status_int=401)
+
+    def test_cannot_submit_images_to_questions_without_having_permissions(self):
+        self.login(self.user_email)
+        skill_id = skill_services.get_new_skill_id()
+        self.save_new_skill(skill_id, self.admin_id, description='Description')
+        with self.swap(self, 'testapp', self.mock_testapp):
+            response = self.get_json('/mock_edit_entity/%s/%s' % (
+                feconf.IMAGE_CONTEXT_QUESTION_SUGGESTIONS, skill_id),
+                expected_status_int=401)
+            self.assertEqual(
+                response['error'], 'You do not have credentials to submit'
+                ' images to questions.')
+        self.logout()
+
     def test_can_edit_blog_post(self):
         self.login(self.BLOG_ADMIN_EMAIL)
         blog_admin_id = (
diff --git a/core/controllers/resources_test.py b/core/controllers/resources_test.py
--- a/core/controllers/resources_test.py
+++ b/core/controllers/resources_test.py
@@ -252,6 +252,32 @@ def test_image_upload_and_download(self):
             self._get_image_url('skill', skill_id, filename), 'image/png')
         self.assertEqual(response.body, raw_image)
 
+        # Image context: Question Suggestions.
+        self.login(self.CURRICULUM_ADMIN_EMAIL)
+        csrf_token = self.get_new_csrf_token()
+
+        with python_utils.open_file(
+            os.path.join(feconf.TESTS_DATA_DIR, 'img.png'), 'rb',
+            encoding=None
+        ) as f:
+            raw_image = f.read()
+        response_dict = self.post_json(
+            '%s/question_suggestions/%s' % (
+                self.IMAGE_UPLOAD_URL_PREFIX,
+                skill_id
+            ),
+            {'filename': 'test.png'},
+            csrf_token=csrf_token,
+            upload_files=(('image', 'unused_filename', raw_image),)
+        )
+        filename = response_dict['filename']
+
+        self.logout()
+
+        response = self.get_custom_response(
+            self._get_image_url('skill', skill_id, filename), 'image/png')
+        self.assertEqual(response.body, raw_image)
+
     def test_non_matching_extensions_are_detected(self):
         self.login(self.EDITOR_EMAIL)
         csrf_token = self.get_new_csrf_token()
diff --git a/core/controllers/suggestion_test.py b/core/controllers/suggestion_test.py
--- a/core/controllers/suggestion_test.py
+++ b/core/controllers/suggestion_test.py
@@ -28,6 +28,7 @@
 from core.domain import exp_services
 from core.domain import feedback_services
 from core.domain import fs_domain
+from core.domain import fs_services
 from core.domain import opportunity_services
 from core.domain import question_domain
 from core.domain import question_services
@@ -1003,132 +1004,6 @@ def test_update_suggestion_updates_question_suggestion_content(self):
             new_solution_dict)
         self.logout()
 
-    def test_update_question_suggestion_with_large_image_throws_error(self):
-        skill_id = skill_services.get_new_skill_id()
-        self.save_new_skill(
-            skill_id, self.author_id, description='description')
-        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': ['skill_1'],
-                'inapplicable_skill_misconception_ids': ['skillid12345-1']
-            },
-            'skill_id': skill_id,
-            'skill_difficulty': 0.3
-        }
-        new_solution_dict = {
-            'answer_is_exclusive': False,
-            'correct_answer': 'Solution',
-            'explanation': {
-                'content_id': 'solution',
-                'html': '<p>This is the updated solution.</p>',
-            },
-        }
-        suggestion = suggestion_services.create_suggestion(
-            feconf.SUGGESTION_TYPE_ADD_QUESTION,
-            feconf.ENTITY_TYPE_SKILL, skill_id, 1,
-            self.author_id, suggestion_change, 'test description')
-
-        question_state_data = self._create_valid_question_data(
-            'default_state').to_dict()
-        html_with_file = (
-            '<oppia-noninteractive-math math_content-with-value="{&amp;q'
-            'uot;raw_latex&amp;quot;: &amp;quot;(x - a_1)(x - a_2)(x - a'
-            '_3)...(x - a_n-1)(x - a_n)&amp;quot;, &amp;quot;svg_filenam'
-            'e&amp;quot;: &amp;quot;file.svg&amp;quot;}"></oppia-noninte'
-            'ractive-math>'
-        )
-        question_state_data['content']['html'] = html_with_file
-        question_state_data['interaction'][
-            'solution'] = new_solution_dict
-
-        self.login(self.CURRICULUM_ADMIN_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-        large_image = '<svg><path d="%s" /></svg>' % (
-            'M150 0 L75 200 L225 200 Z ' * 4000)
-
-        response_dict = self.post_json('%s/%s' % (
-            feconf.UPDATE_QUESTION_SUGGESTION_URL_PREFIX,
-            suggestion.suggestion_id), {
-                'question_state_data': question_state_data,
-                'skill_difficulty': 0.6
-            }, csrf_token=csrf_token, expected_status_int=400, upload_files=(
-                ('file.svg', 'file.svg', large_image),),)
-
-        self.assertIn(
-            'Image exceeds file size limit of 100 KB.',
-            response_dict['error'])
-        self.logout()
-
-    def test_update_question_suggestion_with_missing_image_data_throws_error(
-            self):
-        skill_id = skill_services.get_new_skill_id()
-        self.save_new_skill(
-            skill_id, self.author_id, description='description')
-        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': ['skill_1'],
-                'inapplicable_skill_misconception_ids': ['skillid12345-1']
-            },
-            'skill_id': skill_id,
-            'skill_difficulty': 0.3
-        }
-        suggestion = suggestion_services.create_suggestion(
-            feconf.SUGGESTION_TYPE_ADD_QUESTION,
-            feconf.ENTITY_TYPE_SKILL, skill_id, 1,
-            self.author_id, suggestion_change, 'test description')
-
-        question_state_data = self._create_valid_question_data(
-            'default_state').to_dict()
-        html_with_file = (
-            '<oppia-noninteractive-math math_content-with-value="{&amp;q'
-            'uot;raw_latex&amp;quot;: &amp;quot;(x - a_1)(x - a_2)(x - a'
-            '_3)...(x - a_n-1)(x - a_n)&amp;quot;, &amp;quot;svg_filenam'
-            'e&amp;quot;: &amp;quot;file.svg&amp;quot;}"></oppia-noninte'
-            'ractive-math>'
-        )
-        question_state_data['content']['html'] = html_with_file
-        new_solution_dict = {
-            'answer_is_exclusive': False,
-            'correct_answer': 'Solution',
-            'explanation': {
-                'content_id': 'solution',
-                'html': '<p>This is the updated solution.</p>',
-            },
-        }
-        question_state_data['interaction'][
-            'solution'] = new_solution_dict
-
-        self.login(self.CURRICULUM_ADMIN_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-
-        response_dict = self.post_json('%s/%s' % (
-            feconf.UPDATE_QUESTION_SUGGESTION_URL_PREFIX,
-            suggestion.suggestion_id), {
-                'question_state_data': question_state_data,
-                'skill_difficulty': 0.6
-            }, csrf_token=csrf_token, expected_status_int=400)
-
-        self.assertIn(
-            'No image data provided for file with name file.svg.',
-            response_dict['error'])
-        self.logout()
-
     def test_cannot_update_question_with_invalid_skill_difficulty(self):
         skill_id = skill_services.get_new_skill_id()
         self.save_new_skill(
@@ -1389,6 +1264,157 @@ def test_cannot_update_question_when_provided_state_data_is_invalid(self):
             }, csrf_token=csrf_token, expected_status_int=400)
         self.logout()
 
+    def test_suggestion_creation_when_images_are_not_provided(self):
+        exp_id = '12345678exp1'
+        exploration = (
+            self.save_new_linear_exp_with_state_names_and_interactions(
+                exp_id, self.editor_id, ['State 1'],
+                ['EndExploration'], category='Algebra'))
+
+        state_content_dict = {
+            'content_id': 'content',
+            'html': (
+                '<oppia-noninteractive-image filepath-with-value='
+                '"&quot;img.png&quot;" caption-with-value="&quot;&quot;" '
+                'alt-with-value="&quot;Image&quot;">'
+                '</oppia-noninteractive-image>')
+        }
+        self.login(self.EDITOR_EMAIL)
+        csrf_token = self.get_new_csrf_token()
+
+        with python_utils.open_file(
+            os.path.join(feconf.TESTS_DATA_DIR, 'img.png'),
+            'rb', encoding=None
+        ) as f:
+            raw_image = f.read()
+        self.post_json(
+            '%s/exploration/%s' % (self.IMAGE_UPLOAD_URL_PREFIX, exp_id),
+            {'filename': 'img.png'},
+            csrf_token=csrf_token,
+            upload_files=(('image', 'unused_filename', raw_image),))
+        exp_services.update_exploration(
+            self.editor_id, exp_id, [exp_domain.ExplorationChange({
+                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
+                'property_name': exp_domain.STATE_PROPERTY_CONTENT,
+                'state_name': 'State 1',
+                'new_value': state_content_dict
+            })], 'Changes content.')
+        rights_manager.publish_exploration(self.editor, exp_id)
+
+        exploration = exp_fetchers.get_exploration_by_id(exp_id)
+        text_to_translate = exploration.states['State 1'].content.html
+        self.logout()
+
+        valid_html = (
+            '<oppia-noninteractive-math math_content-with-value="{&amp;q'
+            'uot;raw_latex&amp;quot;: &amp;quot;(x - a_1)(x - a_2)(x - a'
+            '_3)...(x - a_n-1)(x - a_n)&amp;quot;, &amp;quot;svg_filenam'
+            'e&amp;quot;: &amp;quot;file.svg&amp;quot;}"></oppia-noninte'
+            'ractive-math>'
+        )
+        self.login(self.TRANSLATOR_EMAIL)
+        csrf_token = self.get_new_csrf_token()
+        response_dict = self.post_json(
+            '%s/' % feconf.SUGGESTION_URL_PREFIX, {
+                'suggestion_type': (
+                    feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT),
+                'target_type': feconf.ENTITY_TYPE_EXPLORATION,
+                'target_id': exp_id,
+                'target_version_at_submission': exploration.version,
+                'change': {
+                    'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
+                    'state_name': 'State 1',
+                    'content_id': 'content',
+                    'language_code': 'hi',
+                    'content_html': text_to_translate,
+                    'translation_html': valid_html,
+                    'data_format': 'html'
+                },
+            }, csrf_token=csrf_token, expected_status_int=400)
+
+        self.assertIn(
+            'No image data provided for file with name file.svg.',
+            response_dict['error'])
+        self.logout()
+
+    def test_suggestion_creation_when_images_are_not_valid(self):
+        exp_id = '12345678exp1'
+        exploration = (
+            self.save_new_linear_exp_with_state_names_and_interactions(
+                exp_id, self.editor_id, ['State 1'],
+                ['EndExploration'], category='Algebra'))
+
+        state_content_dict = {
+            'content_id': 'content',
+            'html': (
+                '<oppia-noninteractive-image filepath-with-value='
+                '"&quot;img.png&quot;" caption-with-value="&quot;&quot;" '
+                'alt-with-value="&quot;Image&quot;">'
+                '</oppia-noninteractive-image>')
+        }
+        self.login(self.EDITOR_EMAIL)
+        csrf_token = self.get_new_csrf_token()
+
+        with python_utils.open_file(
+            os.path.join(feconf.TESTS_DATA_DIR, 'img.png'),
+            'rb', encoding=None
+        ) as f:
+            raw_image = f.read()
+        self.post_json(
+            '%s/exploration/%s' % (self.IMAGE_UPLOAD_URL_PREFIX, exp_id),
+            {'filename': 'img.png'},
+            csrf_token=csrf_token,
+            upload_files=(('image', 'unused_filename', raw_image),))
+        exp_services.update_exploration(
+            self.editor_id, exp_id, [exp_domain.ExplorationChange({
+                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
+                'property_name': exp_domain.STATE_PROPERTY_CONTENT,
+                'state_name': 'State 1',
+                'new_value': state_content_dict
+            })], 'Changes content.')
+        rights_manager.publish_exploration(self.editor, exp_id)
+
+        exploration = exp_fetchers.get_exploration_by_id(exp_id)
+        text_to_translate = exploration.states['State 1'].content.html
+        self.logout()
+
+        valid_html = (
+            '<oppia-noninteractive-math math_content-with-value="{&amp;q'
+            'uot;raw_latex&amp;quot;: &amp;quot;(x - a_1)(x - a_2)(x - a'
+            '_3)...(x - a_n-1)(x - a_n)&amp;quot;, &amp;quot;svg_filenam'
+            'e&amp;quot;: &amp;quot;file.svg&amp;quot;}"></oppia-noninte'
+            'ractive-math>'
+        )
+        large_image = '<svg><path d="%s" /></svg>' % (
+             'M150 0 L75 200 L225 200 Z ' * 4000)
+        self.login(self.TRANSLATOR_EMAIL)
+        csrf_token = self.get_new_csrf_token()
+        response_dict = self.post_json(
+            '%s/' % feconf.SUGGESTION_URL_PREFIX, {
+                'suggestion_type': (
+                    feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT),
+                'target_type': feconf.ENTITY_TYPE_EXPLORATION,
+                'target_id': exp_id,
+                'target_version_at_submission': exploration.version,
+                'change': {
+                    'cmd': exp_domain.CMD_ADD_WRITTEN_TRANSLATION,
+                    'state_name': 'State 1',
+                    'content_id': 'content',
+                    'language_code': 'hi',
+                    'content_html': text_to_translate,
+                    'translation_html': valid_html,
+                    'data_format': 'html'
+                },
+            }, csrf_token=csrf_token,
+            upload_files=(
+                 ('file.svg', 'file.svg', large_image),),
+            expected_status_int=400)
+
+        self.assertIn(
+            'Image exceeds file size limit of 100 KB.',
+            response_dict['error'])
+        self.logout()
+
 
 class QuestionSuggestionTests(test_utils.GenericTestBase):
 
@@ -1512,6 +1538,181 @@ def test_accept_question_suggestion(self):
             suggestion_to_accept['suggestion_id'])
         last_message = thread_messages[len(thread_messages) - 1]
         self.assertEqual(last_message.text, 'This looks good!')
+        self.logout()
+
+    def test_accept_question_suggestion_with_image_region_interactions(self):
+        with python_utils.open_file(
+            os.path.join(feconf.TESTS_DATA_DIR, 'img.png'), 'rb',
+            encoding=None) as f:
+            original_image_content = f.read()
+
+        skill_id = skill_services.get_new_skill_id()
+        self.save_new_skill(
+            skill_id, self.admin_id, description='Description')
+
+        fs_services.save_original_and_compressed_versions_of_image(
+            'image.png', 'question_suggestions', skill_id,
+            original_image_content, 'image', True)
+        question_state_dict = {
+            'content': {
+                'html': '<p>Text</p>',
+                'content_id': 'content'
+            },
+            'classifier_model_id': None,
+            'linked_skill_id': None,
+            'interaction': {
+                'answer_groups': [
+                    {
+                        'rule_specs': [
+                            {
+                                'rule_type': 'IsInRegion',
+                                'inputs': {'x': 'Region1'}
+                            }
+                        ],
+                        'outcome': {
+                            'dest': None,
+                            'feedback': {
+                                'html': '<p>assas</p>',
+                                'content_id': 'feedback_0'
+                            },
+                            'labelled_as_correct': True,
+                            'param_changes': [],
+                            'refresher_exploration_id': None,
+                            'missing_prerequisite_skill_id': None
+                        },
+                        'training_data': [],
+                        'tagged_skill_misconception_id': None
+                    }
+                ],
+                'confirmed_unclassified_answers': [],
+                'customization_args': {
+                    'imageAndRegions': {
+                        'value': {
+                            'imagePath': 'image.png',
+                            'labeledRegions': [
+                                {
+                                    'label': 'Region1',
+                                    'region': {
+                                        'regionType': 'Rectangle',
+                                        'area': [
+                                            [
+                                                0.2644628099173554,
+                                                0.21807065217391305
+                                            ],
+                                            [
+                                                0.9201101928374655,
+                                                0.8847373188405797
+                                            ]
+                                        ]
+                                    }
+                                }
+                            ]
+                        }
+                    },
+                    'highlightRegionsOnHover': {
+                        'value': False
+                    }
+                },
+                'default_outcome': {
+                    'dest': None,
+                    'feedback': {
+                        'html': '<p>wer</p>',
+                        'content_id': 'default_outcome'
+                    },
+                    'labelled_as_correct': False,
+                    'param_changes': [],
+                    'refresher_exploration_id': None,
+                    'missing_prerequisite_skill_id': None
+                },
+                'hints': [
+                    {
+                        'hint_content': {
+                            'html': '<p>assaas</p>',
+                            'content_id': 'hint_1'
+                        }
+                    }
+                ],
+                'id': 'ImageClickInput', 'solution': None
+            },
+            'param_changes': [],
+            'recorded_voiceovers': {
+                'voiceovers_mapping': {
+                    'content': {},
+                    'default_outcome': {},
+                    'feedback_0': {},
+                    'hint_1': {}
+                }
+            },
+            'solicit_answer_details': False,
+            'card_is_checkpoint': False,
+            'written_translations': {
+                'translations_mapping': {
+                    'content': {},
+                    'default_outcome': {},
+                    'feedback_0': {},
+                    'hint_1': {}
+                }
+            },
+            'next_content_id_index': 2
+        }
+        question_dict = {
+            'question_state_data': question_state_dict,
+            'language_code': 'en',
+            'question_state_data_schema_version': (
+                feconf.CURRENT_STATE_SCHEMA_VERSION),
+            'linked_skill_ids': [skill_id],
+            'inapplicable_skill_misconception_ids': ['skillid12345-1']
+        }
+        suggestion_change = {
+            'cmd': (
+                question_domain
+                .CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION),
+            'question_dict': question_dict,
+            'skill_id': skill_id,
+            'skill_difficulty': 0.3
+        }
+
+        suggestion = suggestion_services.create_suggestion(
+            feconf.SUGGESTION_TYPE_ADD_QUESTION,
+            feconf.ENTITY_TYPE_SKILL, skill_id, 1,
+            self.author_id, suggestion_change, 'test description')
+
+        self.assertEqual(
+            suggestion.status,
+            suggestion_models.STATUS_IN_REVIEW)
+
+        self.login(self.CURRICULUM_ADMIN_EMAIL)
+        csrf_token = self.get_new_csrf_token()
+
+        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True):
+            self.put_json('%s/skill/%s/%s' % (
+                feconf.SUGGESTION_ACTION_URL_PREFIX,
+                suggestion.target_id,
+                suggestion.suggestion_id), {
+                    'action': u'accept',
+                    'commit_message': u'commit message',
+                    'review_message': u'This looks good!',
+                    'skill_id': skill_id
+                }, csrf_token=csrf_token)
+
+        self.logout()
+
+        suggestion_post_accept = suggestion_services.get_suggestion_by_id(
+            suggestion.suggestion_id)
+        question = question_services.get_questions_by_skill_ids(
+            1, [skill_id], False)[0]
+        self.assertEqual(
+            suggestion_post_accept.status,
+            suggestion_models.STATUS_ACCEPTED)
+        # Checks whether image of the Image Region interaction is accessible
+        # from the question player. Pre checks can not be added to check there
+        # are no images in the given directory before accepting the question
+        # suggestion since the directory is created only after the suggestion
+        # is accepted.
+        destination_fs = fs_domain.AbstractFileSystem(
+            fs_domain.GcsFileSystem(
+                feconf.ENTITY_TYPE_QUESTION, question.id))
+        self.assertTrue(destination_fs.isfile('image/%s' % 'image.png'))
 
     def test_create_suggestion_invalid_target_version_input(self):
         self.login(self.AUTHOR_EMAIL)
@@ -1597,106 +1798,6 @@ def test_suggestion_creation_with_valid_images(self):
                 ('file.svg', 'file.svg', raw_image), ))
         self.logout()
 
-    def test_suggestion_creation_when_images_are_not_provided(self):
-        self.save_new_skill(
-            'skill_id2', self.admin_id, description='description')
-        question_state_data_dict = self._create_valid_question_data(
-            'default_state').to_dict()
-        valid_html = (
-            '<oppia-noninteractive-math math_content-with-value="{&amp;q'
-            'uot;raw_latex&amp;quot;: &amp;quot;(x - a_1)(x - a_2)(x - a'
-            '_3)...(x - a_n-1)(x - a_n)&amp;quot;, &amp;quot;svg_filenam'
-            'e&amp;quot;: &amp;quot;file.svg&amp;quot;}"></oppia-noninte'
-            'ractive-math>'
-        )
-        question_state_data_dict['content']['html'] = valid_html
-        self.question_dict = {
-            'question_state_data': question_state_data_dict,
-            'language_code': 'en',
-            'question_state_data_schema_version': (
-                feconf.CURRENT_STATE_SCHEMA_VERSION),
-            'linked_skill_ids': ['skill_id2'],
-            'inapplicable_skill_misconception_ids': []
-        }
-        self.login(self.AUTHOR_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-
-        response_dict = self.post_json(
-            '%s/' % feconf.SUGGESTION_URL_PREFIX, {
-                'suggestion_type': (
-                    feconf.SUGGESTION_TYPE_ADD_QUESTION),
-                'target_type': feconf.ENTITY_TYPE_SKILL,
-                'target_id': self.SKILL_ID,
-                'target_version_at_submission': 1,
-                'change': {
-                    'cmd': (
-                        question_domain
-                        .CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION),
-                    'question_dict': self.question_dict,
-                    'skill_id': self.SKILL_ID,
-                    'skill_difficulty': 0.3
-                },
-                'description': 'Add new question to skill'
-            }, csrf_token=csrf_token, expected_status_int=400)
-
-        self.assertIn(
-            'No image data provided for file with name file.svg.',
-            response_dict['error'])
-        self.logout()
-
-    def test_suggestion_creation_when_images_are_not_valid(self):
-        self.save_new_skill(
-            'skill_id2', self.admin_id, description='description')
-        question_state_data_dict = self._create_valid_question_data(
-            'default_state').to_dict()
-        valid_html = (
-            '<oppia-noninteractive-math math_content-with-value="{&amp;q'
-            'uot;raw_latex&amp;quot;: &amp;quot;(x - a_1)(x - a_2)(x - a'
-            '_3)...(x - a_n-1)(x - a_n)&amp;quot;, &amp;quot;svg_filenam'
-            'e&amp;quot;: &amp;quot;file.svg&amp;quot;}"></oppia-noninte'
-            'ractive-math>'
-        )
-        question_state_data_dict['content']['html'] = valid_html
-        self.question_dict = {
-            'question_state_data': question_state_data_dict,
-            'language_code': 'en',
-            'question_state_data_schema_version': (
-                feconf.CURRENT_STATE_SCHEMA_VERSION),
-            'linked_skill_ids': ['skill_id2'],
-            'inapplicable_skill_misconception_ids': []
-        }
-        self.login(self.AUTHOR_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-
-        large_image = '<svg><path d="%s" /></svg>' % (
-            'M150 0 L75 200 L225 200 Z ' * 4000)
-
-        response_dict = self.post_json(
-            '%s/' % feconf.SUGGESTION_URL_PREFIX, {
-                'suggestion_type': (
-                    feconf.SUGGESTION_TYPE_ADD_QUESTION),
-                'target_type': feconf.ENTITY_TYPE_SKILL,
-                'target_id': self.SKILL_ID,
-                'target_version_at_submission': 1,
-                'change': {
-                    'cmd': (
-                        question_domain
-                        .CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION),
-                    'question_dict': self.question_dict,
-                    'skill_id': self.SKILL_ID,
-                    'skill_difficulty': 0.3
-                },
-                'description': 'Add new question to skill'
-            }, csrf_token=csrf_token,
-            upload_files=(
-                ('file.svg', 'file.svg', large_image),),
-            expected_status_int=400)
-
-        self.assertIn(
-            'Image exceeds file size limit of 100 KB.',
-            response_dict['error'])
-        self.logout()
-
 
 class SkillSuggestionTests(test_utils.GenericTestBase):
 
diff --git a/core/domain/suggestion_registry_test.py b/core/domain/suggestion_registry_test.py
--- a/core/domain/suggestion_registry_test.py
+++ b/core/domain/suggestion_registry_test.py
@@ -26,9 +26,11 @@
 from core.domain import exp_domain
 from core.domain import exp_fetchers
 from core.domain import exp_services
+from core.domain import fs_domain
 from core.domain import fs_services
 from core.domain import html_validation_service
 from core.domain import question_domain
+from core.domain import question_services
 from core.domain import skill_services
 from core.domain import state_domain
 from core.domain import suggestion_registry
@@ -2437,6 +2439,164 @@ def test_accept_suggestion_with_images(self):
             suggestion_dict['language_code'], False, self.fake_date)
         suggestion.accept('commit_message')
 
+    def test_accept_suggestion_with_image_region_interactions(self):
+        with python_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(
+            'image.png', 'question_suggestions', 'skill1',
+            original_image_content, 'image', True)
+
+        question_state_dict = {
+            'content': {
+                'html': '<p>Text</p>',
+                'content_id': 'content'
+            },
+            'classifier_model_id': None,
+            'linked_skill_id': None,
+            'interaction': {
+                'answer_groups': [
+                    {
+                        'rule_specs': [
+                            {
+                                'rule_type': 'IsInRegion',
+                                'inputs': {'x': 'Region1'}
+                            }
+                        ],
+                        'outcome': {
+                            'dest': None,
+                            'feedback': {
+                                'html': '<p>assas</p>',
+                                'content_id': 'feedback_0'
+                            },
+                            'labelled_as_correct': True,
+                            'param_changes': [],
+                            'refresher_exploration_id': None,
+                            'missing_prerequisite_skill_id': None
+                        },
+                        'training_data': [],
+                        'tagged_skill_misconception_id': None
+                    }
+                ],
+                'confirmed_unclassified_answers': [],
+                'customization_args': {
+                    'imageAndRegions': {
+                        'value': {
+                            'imagePath': 'image.png',
+                            'labeledRegions': [
+                                {
+                                    'label': 'Region1',
+                                    'region': {
+                                        'regionType': 'Rectangle',
+                                        'area': [
+                                            [
+                                                0.2644628099173554,
+                                                0.21807065217391305
+                                            ],
+                                            [
+                                                0.9201101928374655,
+                                                0.8847373188405797
+                                            ]
+                                        ]
+                                    }
+                                }
+                            ]
+                        }
+                    },
+                    'highlightRegionsOnHover': {
+                        'value': False
+                    }
+                },
+                'default_outcome': {
+                    'dest': None,
+                    'feedback': {
+                        'html': '<p>wer</p>',
+                        'content_id': 'default_outcome'
+                    },
+                    'labelled_as_correct': False,
+                    'param_changes': [],
+                    'refresher_exploration_id': None,
+                    'missing_prerequisite_skill_id': None
+                },
+                'hints': [
+                    {
+                        'hint_content': {
+                            'html': '<p>assaas</p>',
+                            'content_id': 'hint_1'
+                        }
+                    }
+                ],
+                'id': 'ImageClickInput', 'solution': None
+            },
+            'param_changes': [],
+            'recorded_voiceovers': {
+                'voiceovers_mapping': {
+                    'content': {},
+                    'default_outcome': {},
+                    'feedback_0': {},
+                    'hint_1': {}
+                }
+            },
+            'solicit_answer_details': False,
+            'card_is_checkpoint': False,
+            'written_translations': {
+                'translations_mapping': {
+                    'content': {},
+                    'default_outcome': {},
+                    'feedback_0': {},
+                    'hint_1': {}
+                }
+            },
+            'next_content_id_index': 2
+        }
+        suggestion_dict = {
+            'suggestion_id': 'skill1.thread1',
+            'suggestion_type': feconf.SUGGESTION_TYPE_ADD_QUESTION,
+            'target_type': feconf.ENTITY_TYPE_SKILL,
+            'target_id': 'skill1',
+            'target_version_at_submission': 1,
+            'status': suggestion_models.STATUS_ACCEPTED,
+            'author_name': 'author',
+            'final_reviewer_id': self.reviewer_id,
+            'change': {
+                'cmd': question_domain.CMD_CREATE_NEW_FULLY_SPECIFIED_QUESTION,
+                'question_dict': {
+                    'question_state_data': question_state_dict,
+                    'language_code': 'en',
+                    'question_state_data_schema_version': (
+                        feconf.CURRENT_STATE_SCHEMA_VERSION),
+                    'linked_skill_ids': ['skill1'],
+                    'inapplicable_skill_misconception_ids': []
+                },
+                'skill_id': 'skill1',
+                'skill_difficulty': 0.3,
+            },
+            'score_category': 'question.skill1',
+            'language_code': 'en',
+            'last_updated': utils.get_time_in_millisecs(self.fake_date)
+        }
+        self.save_new_skill(
+            'skill1', self.author_id, description='description')
+        suggestion = suggestion_registry.SuggestionAddQuestion(
+            suggestion_dict['suggestion_id'], suggestion_dict['target_id'],
+            suggestion_dict['target_version_at_submission'],
+            suggestion_dict['status'], self.author_id, self.reviewer_id,
+            suggestion_dict['change'], suggestion_dict['score_category'],
+            suggestion_dict['language_code'], False, self.fake_date)
+
+        suggestion.accept('commit_message')
+
+        question = question_services.get_questions_by_skill_ids(
+            1, ['skill1'], False)[0]
+        destination_fs = fs_domain.AbstractFileSystem(
+            fs_domain.GcsFileSystem(
+                feconf.ENTITY_TYPE_QUESTION, question.id))
+        self.assertTrue(destination_fs.isfile('image/%s' % 'image.png'))
+        self.assertEqual(
+            suggestion.status,
+            suggestion_models.STATUS_ACCEPTED)
+
     def test_contructor_updates_state_shema_in_change_cmd(self):
         score_category = (
             suggestion_models.SCORE_TYPE_QUESTION +