We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Blank page loaded when navigated to feedback page from creator dashboard_Both mobile and browsers
**Describe the bug**
Blank page loaded when navigated to feedback page from creator dashboard

**To Reproduce**
Steps to reproduce the behavior:
 1. Create an exploration as user1 and logout
 2. Play through the course as user2 and submit a feedback
 3. Login as user1 and navigate to creator dashboard
 4. Click on the open feedback count (2 in this scenario as in image1)

**Observed behavior**
Blank page loaded when navigated to feedback page from creator dashboard

**Expected behavior**
Meaningful message / page should be loaded

**Screenshots**
Image1
![image](https://user-images.githubusercontent.com/56336353/76447177-3c1b2680-63ee-11ea-879a-a9d01a73b6fb.png)

Blank page
![image](https://user-images.githubusercontent.com/56336353/76447267-6836a780-63ee-11ea-98b4-b5846bbab3d9.png)


**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**
OS: Chrome OS
Browser Chrome
Version 79.0.3945.119 (Official Build) (64-bit)

**Smartphone (please complete the following information; delete this section if the issue does not arise on smartphones):**
 Device: [iPhoneXS]
OS: [ iOS13.3]
Browser [chrome]

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `core/domain/config_domain.py`
Content:
```
1 # coding: utf-8
2 #
3 # Copyright 2014 The Oppia Authors. All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS-IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 """Domain objects for configuration properties."""
18 
19 from __future__ import absolute_import  # pylint: disable=import-only-modules
20 from __future__ import unicode_literals  # pylint: disable=import-only-modules
21 
22 from core.domain import change_domain
23 from core.platform import models
24 import feconf
25 import python_utils
26 import schema_utils
27 
28 (config_models,) = models.Registry.import_models([models.NAMES.config])
29 memcache_services = models.Registry.import_memcache_services()
30 
31 CMD_CHANGE_PROPERTY_VALUE = 'change_property_value'
32 
33 SET_OF_STRINGS_SCHEMA = {
34     'type': 'list',
35     'items': {
36         'type': 'unicode',
37     },
38     'validators': [{
39         'id': 'is_uniquified',
40     }],
41 }
42 
43 SET_OF_CLASSROOM_DICTS_SCHEMA = {
44     'type': 'list',
45     'items': {
46         'type': 'dict',
47         'properties': [{
48             'name': 'name',
49             'schema': {
50                 'type': 'unicode'
51             }
52         }, {
53             'name': 'topic_ids',
54             'schema': {
55                 'type': 'list',
56                 'items': {
57                     'type': 'unicode',
58                 },
59                 'validators': [{
60                     'id': 'is_uniquified',
61                 }]
62             }
63         }]
64     }
65 }
66 
67 VMID_SHARED_SECRET_KEY_SCHEMA = {
68     'type': 'list',
69     'items': {
70         'type': 'dict',
71         'properties': [{
72             'name': 'vm_id',
73             'schema': {
74                 'type': 'unicode'
75             }
76         }, {
77             'name': 'shared_secret_key',
78             'schema': {
79                 'type': 'unicode'
80             }
81         }]
82     }
83 }
84 
85 BOOL_SCHEMA = {
86     'type': schema_utils.SCHEMA_TYPE_BOOL
87 }
88 
89 UNICODE_SCHEMA = {
90     'type': schema_utils.SCHEMA_TYPE_UNICODE
91 }
92 
93 FLOAT_SCHEMA = {
94     'type': schema_utils.SCHEMA_TYPE_FLOAT
95 }
96 
97 
98 class ConfigPropertyChange(change_domain.BaseChange):
99     """Domain object for changes made to a config property object.
100 
101     The allowed commands, together with the attributes:
102         - 'change_property_value' (with new_value)
103     """
104 
105     ALLOWED_COMMANDS = [{
106         'name': CMD_CHANGE_PROPERTY_VALUE,
107         'required_attribute_names': ['new_value'],
108         'optional_attribute_names': []
109     }]
110 
111 
112 class ConfigProperty(python_utils.OBJECT):
113     """A property with a name and a default value.
114 
115     NOTE TO DEVELOPERS: These config properties are deprecated. Do not reuse
116     these names:
117     - about_page_youtube_video_id.
118     - admin_email_address.
119     - admin_ids.
120     - admin_usernames.
121     - allow_yaml_file_upload.
122     - banned_usernames.
123     - banner_alt_text.
124     - before_end_body_tag_hook.
125     - before_end_head_tag_hook.
126     - carousel_slides_config.
127     - collection_editor_whitelist.
128     - contact_email_address.
129     - contribute_gallery_page_announcement.
130     - default_twitter_share_message_editor.
131     - disabled_explorations.
132     - editor_page_announcement.
133     - editor_prerequisites_agreement.
134     - embedded_google_group_url.
135     - full_site_url.
136     - moderator_ids.
137     - moderator_request_forum_url.
138     - moderator_usernames.
139     - publicize_exploration_email_html_body.
140     - sharing_options.
141     - sharing_options_twitter_text.
142     - sidebar_menu_additional_links.
143     - site_forum_url.
144     - social_media_buttons.
145     - splash_page_exploration_id.
146     - splash_page_exploration_version.
147     - splash_page_youtube_video_id.
148     - ssl_challenge_responses.
149     - whitelisted_email_senders.
150     """
151 
152     def __init__(self, name, schema, description, default_value):
153         if Registry.get_config_property(name):
154             raise Exception('Property with name %s already exists' % name)
155 
156         self._name = name
157         self._schema = schema
158         self._description = description
159         self._default_value = schema_utils.normalize_against_schema(
160             default_value, self._schema)
161 
162         Registry.init_config_property(self.name, self)
163 
164     @property
165     def name(self):
166         """Returns the name of the configuration property."""
167 
168         return self._name
169 
170     @property
171     def schema(self):
172         """Returns the schema of the configuration property."""
173 
174         return self._schema
175 
176     @property
177     def description(self):
178         """Returns the description of the configuration property."""
179 
180         return self._description
181 
182     @property
183     def default_value(self):
184         """Returns the default value of the configuration property."""
185 
186         return self._default_value
187 
188     @property
189     def value(self):
190         """Get the latest value from memcache, datastore, or use default."""
191 
192         memcached_items = memcache_services.get_multi([self.name])
193         if self.name in memcached_items:
194             return memcached_items[self.name]
195 
196         datastore_item = config_models.ConfigPropertyModel.get(
197             self.name, strict=False)
198         if datastore_item is not None:
199             memcache_services.set_multi({
200                 datastore_item.id: datastore_item.value})
201             return datastore_item.value
202 
203         return self.default_value
204 
205     def set_value(self, committer_id, raw_value):
206         """Sets the value of the property. In general, this should not be
207         called directly -- use config_services.set_property() instead.
208         """
209         value = self.normalize(raw_value)
210 
211         # Set value in datastore.
212         model_instance = config_models.ConfigPropertyModel.get(
213             self.name, strict=False)
214         if model_instance is None:
215             model_instance = config_models.ConfigPropertyModel(
216                 id=self.name)
217         model_instance.value = value
218         model_instance.commit(
219             committer_id, [{
220                 'cmd': CMD_CHANGE_PROPERTY_VALUE,
221                 'new_value': value
222             }])
223 
224         # Set value in memcache.
225         memcache_services.set_multi({
226             model_instance.id: model_instance.value})
227 
228     def normalize(self, value):
229         """Validates the given object using the schema and normalizes if
230         necessary.
231 
232         Args:
233             value: The value of the configuration property.
234 
235         Returns:
236             instance. The normalized object.
237         """
238         return schema_utils.normalize_against_schema(value, self._schema)
239 
240 
241 class Registry(python_utils.OBJECT):
242     """Registry of all configuration properties."""
243 
244     # The keys of _config_registry are the property names, and the values are
245     # ConfigProperty instances.
246     _config_registry = {}
247 
248     @classmethod
249     def init_config_property(cls, name, instance):
250         """Initializes _config_registry with keys as the property names and
251         values as instances of the specified property.
252 
253         Args:
254             name: str. The name of the configuration property.
255             instance: *. The instance of the configuration property.
256         """
257         cls._config_registry[name] = instance
258 
259     @classmethod
260     def get_config_property(cls, name):
261         """Returns the instance of the specified name of the configuration
262         property.
263 
264         Args:
265             name: str. The name of the configuration property.
266 
267         Returns:
268             instance. The instance of the specified configuration property.
269         """
270         return cls._config_registry.get(name)
271 
272     @classmethod
273     def get_config_property_schemas(cls):
274         """Return a dict of editable config property schemas.
275 
276         The keys of the dict are config property names. The values are dicts
277         with the following keys: schema, description, value.
278         """
279         schemas_dict = {}
280 
281         for (property_name, instance) in cls._config_registry.items():
282             schemas_dict[property_name] = {
283                 'schema': instance.schema,
284                 'description': instance.description,
285                 'value': instance.value
286             }
287 
288         return schemas_dict
289 
290     @classmethod
291     def get_all_config_property_names(cls):
292         """Return a list of all the config property names.
293 
294         Returns:
295             list. The list of all config property names.
296         """
297         return list(cls._config_registry)
298 
299 
300 PROMO_BAR_ENABLED = ConfigProperty(
301     'promo_bar_enabled', BOOL_SCHEMA,
302     'Whether the promo bar should be enabled for all users', False)
303 PROMO_BAR_MESSAGE = ConfigProperty(
304     'promo_bar_message', UNICODE_SCHEMA,
305     'The message to show to all users if the promo bar is enabled', '')
306 
307 VMID_SHARED_SECRET_KEY_MAPPING = ConfigProperty(
308     'vmid_shared_secret_key_mapping', VMID_SHARED_SECRET_KEY_SCHEMA,
309     'VMID and shared secret key corresponding to that VM',
310     [{
311         'vm_id': feconf.DEFAULT_VM_ID,
312         'shared_secret_key': feconf.DEFAULT_VM_SHARED_SECRET
313     }])
314 
315 WHITELISTED_EXPLORATION_IDS_FOR_PLAYTHROUGHS = ConfigProperty(
316     'whitelisted_exploration_ids_for_playthroughs',
317     SET_OF_STRINGS_SCHEMA,
318     'The set of exploration IDs for recording playthrough issues', [
319         'umPkwp0L1M0-', 'MjZzEVOG47_1', '9trAQhj6uUC2', 'rfX8jNkPnA-1',
320         '0FBWxCE5egOw', '670bU6d9JGBh', 'aHikhPlxYgOH', '-tMgcP1i_4au',
321         'zW39GLG_BdN2', 'Xa3B_io-2WI5', '6Q6IyIDkjpYC', 'osw1m5Q3jK41'])
322 
323 TOPIC_IDS_FOR_CLASSROOM_PAGES = ConfigProperty(
324     'topic_ids_for_classroom_pages', SET_OF_CLASSROOM_DICTS_SCHEMA,
325     'The set of topic IDs for each classroom page.', [{
326         'name': 'Math',
327         'topic_ids': []
328     }]
329 )
330 
331 RECORD_PLAYTHROUGH_PROBABILITY = ConfigProperty(
332     'record_playthrough_probability', FLOAT_SCHEMA,
333     'The probability of recording playthroughs', 0.2)
334 
335 IS_IMPROVEMENTS_TAB_ENABLED = ConfigProperty(
336     'is_improvements_tab_enabled', BOOL_SCHEMA,
337     'Exposes the Improvements Tab for creators in the exploration editor.',
338     True)
339 
340 ALWAYS_ASK_LEARNERS_FOR_ANSWER_DETAILS = ConfigProperty(
341     'always_ask_learners_for_answer_details', BOOL_SCHEMA,
342     'Always ask learners for answer details. For testing -- do not use',
343     False)
344
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/core/domain/config_domain.py b/core/domain/config_domain.py
--- a/core/domain/config_domain.py
+++ b/core/domain/config_domain.py
@@ -335,7 +335,7 @@
 IS_IMPROVEMENTS_TAB_ENABLED = ConfigProperty(
     'is_improvements_tab_enabled', BOOL_SCHEMA,
     'Exposes the Improvements Tab for creators in the exploration editor.',
-    True)
+    False)
 
 ALWAYS_ASK_LEARNERS_FOR_ANSWER_DETAILS = ConfigProperty(
     'always_ask_learners_for_answer_details', BOOL_SCHEMA,

{"golden_diff": "diff --git a/core/domain/config_domain.py b/core/domain/config_domain.py\n--- a/core/domain/config_domain.py\n+++ b/core/domain/config_domain.py\n@@ -335,7 +335,7 @@\n IS_IMPROVEMENTS_TAB_ENABLED = ConfigProperty(\n     'is_improvements_tab_enabled', BOOL_SCHEMA,\n     'Exposes the Improvements Tab for creators in the exploration editor.',\n-    True)\n+    False)\n \n ALWAYS_ASK_LEARNERS_FOR_ANSWER_DETAILS = ConfigProperty(\n     'always_ask_learners_for_answer_details', BOOL_SCHEMA,\n", "issue": "Blank page loaded when navigated to feedback page from creator dashboard_Both mobile and browsers\n**Describe the bug**\r\nBlank page loaded when navigated to feedback page from creator dashboard\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n 1. Create an exploration as user1 and logout\r\n 2. Play through the course as user2 and submit a feedback\r\n 3. Login as user1 and navigate to creator dashboard\r\n 4. Click on the open feedback count (2 in this scenario as in image1)\r\n\r\n**Observed behavior**\r\nBlank page loaded when navigated to feedback page from creator dashboard\r\n\r\n**Expected behavior**\r\nMeaningful message / page should be loaded\r\n\r\n**Screenshots**\r\nImage1\r\n![image](https://user-images.githubusercontent.com/56336353/76447177-3c1b2680-63ee-11ea-879a-a9d01a73b6fb.png)\r\n\r\nBlank page\r\n![image](https://user-images.githubusercontent.com/56336353/76447267-6836a780-63ee-11ea-98b4-b5846bbab3d9.png)\r\n\r\n\r\n**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**\r\nOS: Chrome OS\r\nBrowser Chrome\r\nVersion 79.0.3945.119 (Official Build) (64-bit)\r\n\r\n**Smartphone (please complete the following information; delete this section if the issue does not arise on smartphones):**\r\n Device: [iPhoneXS]\r\nOS: [ iOS13.3]\r\nBrowser [chrome]\n", "before_files": [{"content": "# coding: utf-8\n#\n# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Domain objects for configuration properties.\"\"\"\n\nfrom __future__ import absolute_import  # pylint: disable=import-only-modules\nfrom __future__ import unicode_literals  # pylint: disable=import-only-modules\n\nfrom core.domain import change_domain\nfrom core.platform import models\nimport feconf\nimport python_utils\nimport schema_utils\n\n(config_models,) = models.Registry.import_models([models.NAMES.config])\nmemcache_services = models.Registry.import_memcache_services()\n\nCMD_CHANGE_PROPERTY_VALUE = 'change_property_value'\n\nSET_OF_STRINGS_SCHEMA = {\n    'type': 'list',\n    'items': {\n        'type': 'unicode',\n    },\n    'validators': [{\n        'id': 'is_uniquified',\n    }],\n}\n\nSET_OF_CLASSROOM_DICTS_SCHEMA = {\n    'type': 'list',\n    'items': {\n        'type': 'dict',\n        'properties': [{\n            'name': 'name',\n            'schema': {\n                'type': 'unicode'\n            }\n        }, {\n            'name': 'topic_ids',\n            'schema': {\n                'type': 'list',\n                'items': {\n                    'type': 'unicode',\n                },\n                'validators': [{\n                    'id': 'is_uniquified',\n                }]\n            }\n        }]\n    }\n}\n\nVMID_SHARED_SECRET_KEY_SCHEMA = {\n    'type': 'list',\n    'items': {\n        'type': 'dict',\n        'properties': [{\n            'name': 'vm_id',\n            'schema': {\n                'type': 'unicode'\n            }\n        }, {\n            'name': 'shared_secret_key',\n            'schema': {\n                'type': 'unicode'\n            }\n        }]\n    }\n}\n\nBOOL_SCHEMA = {\n    'type': schema_utils.SCHEMA_TYPE_BOOL\n}\n\nUNICODE_SCHEMA = {\n    'type': schema_utils.SCHEMA_TYPE_UNICODE\n}\n\nFLOAT_SCHEMA = {\n    'type': schema_utils.SCHEMA_TYPE_FLOAT\n}\n\n\nclass ConfigPropertyChange(change_domain.BaseChange):\n    \"\"\"Domain object for changes made to a config property object.\n\n    The allowed commands, together with the attributes:\n        - 'change_property_value' (with new_value)\n    \"\"\"\n\n    ALLOWED_COMMANDS = [{\n        'name': CMD_CHANGE_PROPERTY_VALUE,\n        'required_attribute_names': ['new_value'],\n        'optional_attribute_names': []\n    }]\n\n\nclass ConfigProperty(python_utils.OBJECT):\n    \"\"\"A property with a name and a default value.\n\n    NOTE TO DEVELOPERS: These config properties are deprecated. Do not reuse\n    these names:\n    - about_page_youtube_video_id.\n    - admin_email_address.\n    - admin_ids.\n    - admin_usernames.\n    - allow_yaml_file_upload.\n    - banned_usernames.\n    - banner_alt_text.\n    - before_end_body_tag_hook.\n    - before_end_head_tag_hook.\n    - carousel_slides_config.\n    - collection_editor_whitelist.\n    - contact_email_address.\n    - contribute_gallery_page_announcement.\n    - default_twitter_share_message_editor.\n    - disabled_explorations.\n    - editor_page_announcement.\n    - editor_prerequisites_agreement.\n    - embedded_google_group_url.\n    - full_site_url.\n    - moderator_ids.\n    - moderator_request_forum_url.\n    - moderator_usernames.\n    - publicize_exploration_email_html_body.\n    - sharing_options.\n    - sharing_options_twitter_text.\n    - sidebar_menu_additional_links.\n    - site_forum_url.\n    - social_media_buttons.\n    - splash_page_exploration_id.\n    - splash_page_exploration_version.\n    - splash_page_youtube_video_id.\n    - ssl_challenge_responses.\n    - whitelisted_email_senders.\n    \"\"\"\n\n    def __init__(self, name, schema, description, default_value):\n        if Registry.get_config_property(name):\n            raise Exception('Property with name %s already exists' % name)\n\n        self._name = name\n        self._schema = schema\n        self._description = description\n        self._default_value = schema_utils.normalize_against_schema(\n            default_value, self._schema)\n\n        Registry.init_config_property(self.name, self)\n\n    @property\n    def name(self):\n        \"\"\"Returns the name of the configuration property.\"\"\"\n\n        return self._name\n\n    @property\n    def schema(self):\n        \"\"\"Returns the schema of the configuration property.\"\"\"\n\n        return self._schema\n\n    @property\n    def description(self):\n        \"\"\"Returns the description of the configuration property.\"\"\"\n\n        return self._description\n\n    @property\n    def default_value(self):\n        \"\"\"Returns the default value of the configuration property.\"\"\"\n\n        return self._default_value\n\n    @property\n    def value(self):\n        \"\"\"Get the latest value from memcache, datastore, or use default.\"\"\"\n\n        memcached_items = memcache_services.get_multi([self.name])\n        if self.name in memcached_items:\n            return memcached_items[self.name]\n\n        datastore_item = config_models.ConfigPropertyModel.get(\n            self.name, strict=False)\n        if datastore_item is not None:\n            memcache_services.set_multi({\n                datastore_item.id: datastore_item.value})\n            return datastore_item.value\n\n        return self.default_value\n\n    def set_value(self, committer_id, raw_value):\n        \"\"\"Sets the value of the property. In general, this should not be\n        called directly -- use config_services.set_property() instead.\n        \"\"\"\n        value = self.normalize(raw_value)\n\n        # Set value in datastore.\n        model_instance = config_models.ConfigPropertyModel.get(\n            self.name, strict=False)\n        if model_instance is None:\n            model_instance = config_models.ConfigPropertyModel(\n                id=self.name)\n        model_instance.value = value\n        model_instance.commit(\n            committer_id, [{\n                'cmd': CMD_CHANGE_PROPERTY_VALUE,\n                'new_value': value\n            }])\n\n        # Set value in memcache.\n        memcache_services.set_multi({\n            model_instance.id: model_instance.value})\n\n    def normalize(self, value):\n        \"\"\"Validates the given object using the schema and normalizes if\n        necessary.\n\n        Args:\n            value: The value of the configuration property.\n\n        Returns:\n            instance. The normalized object.\n        \"\"\"\n        return schema_utils.normalize_against_schema(value, self._schema)\n\n\nclass Registry(python_utils.OBJECT):\n    \"\"\"Registry of all configuration properties.\"\"\"\n\n    # The keys of _config_registry are the property names, and the values are\n    # ConfigProperty instances.\n    _config_registry = {}\n\n    @classmethod\n    def init_config_property(cls, name, instance):\n        \"\"\"Initializes _config_registry with keys as the property names and\n        values as instances of the specified property.\n\n        Args:\n            name: str. The name of the configuration property.\n            instance: *. The instance of the configuration property.\n        \"\"\"\n        cls._config_registry[name] = instance\n\n    @classmethod\n    def get_config_property(cls, name):\n        \"\"\"Returns the instance of the specified name of the configuration\n        property.\n\n        Args:\n            name: str. The name of the configuration property.\n\n        Returns:\n            instance. The instance of the specified configuration property.\n        \"\"\"\n        return cls._config_registry.get(name)\n\n    @classmethod\n    def get_config_property_schemas(cls):\n        \"\"\"Return a dict of editable config property schemas.\n\n        The keys of the dict are config property names. The values are dicts\n        with the following keys: schema, description, value.\n        \"\"\"\n        schemas_dict = {}\n\n        for (property_name, instance) in cls._config_registry.items():\n            schemas_dict[property_name] = {\n                'schema': instance.schema,\n                'description': instance.description,\n                'value': instance.value\n            }\n\n        return schemas_dict\n\n    @classmethod\n    def get_all_config_property_names(cls):\n        \"\"\"Return a list of all the config property names.\n\n        Returns:\n            list. The list of all config property names.\n        \"\"\"\n        return list(cls._config_registry)\n\n\nPROMO_BAR_ENABLED = ConfigProperty(\n    'promo_bar_enabled', BOOL_SCHEMA,\n    'Whether the promo bar should be enabled for all users', False)\nPROMO_BAR_MESSAGE = ConfigProperty(\n    'promo_bar_message', UNICODE_SCHEMA,\n    'The message to show to all users if the promo bar is enabled', '')\n\nVMID_SHARED_SECRET_KEY_MAPPING = ConfigProperty(\n    'vmid_shared_secret_key_mapping', VMID_SHARED_SECRET_KEY_SCHEMA,\n    'VMID and shared secret key corresponding to that VM',\n    [{\n        'vm_id': feconf.DEFAULT_VM_ID,\n        'shared_secret_key': feconf.DEFAULT_VM_SHARED_SECRET\n    }])\n\nWHITELISTED_EXPLORATION_IDS_FOR_PLAYTHROUGHS = ConfigProperty(\n    'whitelisted_exploration_ids_for_playthroughs',\n    SET_OF_STRINGS_SCHEMA,\n    'The set of exploration IDs for recording playthrough issues', [\n        'umPkwp0L1M0-', 'MjZzEVOG47_1', '9trAQhj6uUC2', 'rfX8jNkPnA-1',\n        '0FBWxCE5egOw', '670bU6d9JGBh', 'aHikhPlxYgOH', '-tMgcP1i_4au',\n        'zW39GLG_BdN2', 'Xa3B_io-2WI5', '6Q6IyIDkjpYC', 'osw1m5Q3jK41'])\n\nTOPIC_IDS_FOR_CLASSROOM_PAGES = ConfigProperty(\n    'topic_ids_for_classroom_pages', SET_OF_CLASSROOM_DICTS_SCHEMA,\n    'The set of topic IDs for each classroom page.', [{\n        'name': 'Math',\n        'topic_ids': []\n    }]\n)\n\nRECORD_PLAYTHROUGH_PROBABILITY = ConfigProperty(\n    'record_playthrough_probability', FLOAT_SCHEMA,\n    'The probability of recording playthroughs', 0.2)\n\nIS_IMPROVEMENTS_TAB_ENABLED = ConfigProperty(\n    'is_improvements_tab_enabled', BOOL_SCHEMA,\n    'Exposes the Improvements Tab for creators in the exploration editor.',\n    True)\n\nALWAYS_ASK_LEARNERS_FOR_ANSWER_DETAILS = ConfigProperty(\n    'always_ask_learners_for_answer_details', BOOL_SCHEMA,\n    'Always ask learners for answer details. For testing -- do not use',\n    False)\n", "path": "core/domain/config_domain.py"}], "after_files": [{"content": "# coding: utf-8\n#\n# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Domain objects for configuration properties.\"\"\"\n\nfrom __future__ import absolute_import  # pylint: disable=import-only-modules\nfrom __future__ import unicode_literals  # pylint: disable=import-only-modules\n\nfrom core.domain import change_domain\nfrom core.platform import models\nimport feconf\nimport python_utils\nimport schema_utils\n\n(config_models,) = models.Registry.import_models([models.NAMES.config])\nmemcache_services = models.Registry.import_memcache_services()\n\nCMD_CHANGE_PROPERTY_VALUE = 'change_property_value'\n\nSET_OF_STRINGS_SCHEMA = {\n    'type': 'list',\n    'items': {\n        'type': 'unicode',\n    },\n    'validators': [{\n        'id': 'is_uniquified',\n    }],\n}\n\nSET_OF_CLASSROOM_DICTS_SCHEMA = {\n    'type': 'list',\n    'items': {\n        'type': 'dict',\n        'properties': [{\n            'name': 'name',\n            'schema': {\n                'type': 'unicode'\n            }\n        }, {\n            'name': 'topic_ids',\n            'schema': {\n                'type': 'list',\n                'items': {\n                    'type': 'unicode',\n                },\n                'validators': [{\n                    'id': 'is_uniquified',\n                }]\n            }\n        }]\n    }\n}\n\nVMID_SHARED_SECRET_KEY_SCHEMA = {\n    'type': 'list',\n    'items': {\n        'type': 'dict',\n        'properties': [{\n            'name': 'vm_id',\n            'schema': {\n                'type': 'unicode'\n            }\n        }, {\n            'name': 'shared_secret_key',\n            'schema': {\n                'type': 'unicode'\n            }\n        }]\n    }\n}\n\nBOOL_SCHEMA = {\n    'type': schema_utils.SCHEMA_TYPE_BOOL\n}\n\nUNICODE_SCHEMA = {\n    'type': schema_utils.SCHEMA_TYPE_UNICODE\n}\n\nFLOAT_SCHEMA = {\n    'type': schema_utils.SCHEMA_TYPE_FLOAT\n}\n\n\nclass ConfigPropertyChange(change_domain.BaseChange):\n    \"\"\"Domain object for changes made to a config property object.\n\n    The allowed commands, together with the attributes:\n        - 'change_property_value' (with new_value)\n    \"\"\"\n\n    ALLOWED_COMMANDS = [{\n        'name': CMD_CHANGE_PROPERTY_VALUE,\n        'required_attribute_names': ['new_value'],\n        'optional_attribute_names': []\n    }]\n\n\nclass ConfigProperty(python_utils.OBJECT):\n    \"\"\"A property with a name and a default value.\n\n    NOTE TO DEVELOPERS: These config properties are deprecated. Do not reuse\n    these names:\n    - about_page_youtube_video_id.\n    - admin_email_address.\n    - admin_ids.\n    - admin_usernames.\n    - allow_yaml_file_upload.\n    - banned_usernames.\n    - banner_alt_text.\n    - before_end_body_tag_hook.\n    - before_end_head_tag_hook.\n    - carousel_slides_config.\n    - collection_editor_whitelist.\n    - contact_email_address.\n    - contribute_gallery_page_announcement.\n    - default_twitter_share_message_editor.\n    - disabled_explorations.\n    - editor_page_announcement.\n    - editor_prerequisites_agreement.\n    - embedded_google_group_url.\n    - full_site_url.\n    - moderator_ids.\n    - moderator_request_forum_url.\n    - moderator_usernames.\n    - publicize_exploration_email_html_body.\n    - sharing_options.\n    - sharing_options_twitter_text.\n    - sidebar_menu_additional_links.\n    - site_forum_url.\n    - social_media_buttons.\n    - splash_page_exploration_id.\n    - splash_page_exploration_version.\n    - splash_page_youtube_video_id.\n    - ssl_challenge_responses.\n    - whitelisted_email_senders.\n    \"\"\"\n\n    def __init__(self, name, schema, description, default_value):\n        if Registry.get_config_property(name):\n            raise Exception('Property with name %s already exists' % name)\n\n        self._name = name\n        self._schema = schema\n        self._description = description\n        self._default_value = schema_utils.normalize_against_schema(\n            default_value, self._schema)\n\n        Registry.init_config_property(self.name, self)\n\n    @property\n    def name(self):\n        \"\"\"Returns the name of the configuration property.\"\"\"\n\n        return self._name\n\n    @property\n    def schema(self):\n        \"\"\"Returns the schema of the configuration property.\"\"\"\n\n        return self._schema\n\n    @property\n    def description(self):\n        \"\"\"Returns the description of the configuration property.\"\"\"\n\n        return self._description\n\n    @property\n    def default_value(self):\n        \"\"\"Returns the default value of the configuration property.\"\"\"\n\n        return self._default_value\n\n    @property\n    def value(self):\n        \"\"\"Get the latest value from memcache, datastore, or use default.\"\"\"\n\n        memcached_items = memcache_services.get_multi([self.name])\n        if self.name in memcached_items:\n            return memcached_items[self.name]\n\n        datastore_item = config_models.ConfigPropertyModel.get(\n            self.name, strict=False)\n        if datastore_item is not None:\n            memcache_services.set_multi({\n                datastore_item.id: datastore_item.value})\n            return datastore_item.value\n\n        return self.default_value\n\n    def set_value(self, committer_id, raw_value):\n        \"\"\"Sets the value of the property. In general, this should not be\n        called directly -- use config_services.set_property() instead.\n        \"\"\"\n        value = self.normalize(raw_value)\n\n        # Set value in datastore.\n        model_instance = config_models.ConfigPropertyModel.get(\n            self.name, strict=False)\n        if model_instance is None:\n            model_instance = config_models.ConfigPropertyModel(\n                id=self.name)\n        model_instance.value = value\n        model_instance.commit(\n            committer_id, [{\n                'cmd': CMD_CHANGE_PROPERTY_VALUE,\n                'new_value': value\n            }])\n\n        # Set value in memcache.\n        memcache_services.set_multi({\n            model_instance.id: model_instance.value})\n\n    def normalize(self, value):\n        \"\"\"Validates the given object using the schema and normalizes if\n        necessary.\n\n        Args:\n            value: The value of the configuration property.\n\n        Returns:\n            instance. The normalized object.\n        \"\"\"\n        return schema_utils.normalize_against_schema(value, self._schema)\n\n\nclass Registry(python_utils.OBJECT):\n    \"\"\"Registry of all configuration properties.\"\"\"\n\n    # The keys of _config_registry are the property names, and the values are\n    # ConfigProperty instances.\n    _config_registry = {}\n\n    @classmethod\n    def init_config_property(cls, name, instance):\n        \"\"\"Initializes _config_registry with keys as the property names and\n        values as instances of the specified property.\n\n        Args:\n            name: str. The name of the configuration property.\n            instance: *. The instance of the configuration property.\n        \"\"\"\n        cls._config_registry[name] = instance\n\n    @classmethod\n    def get_config_property(cls, name):\n        \"\"\"Returns the instance of the specified name of the configuration\n        property.\n\n        Args:\n            name: str. The name of the configuration property.\n\n        Returns:\n            instance. The instance of the specified configuration property.\n        \"\"\"\n        return cls._config_registry.get(name)\n\n    @classmethod\n    def get_config_property_schemas(cls):\n        \"\"\"Return a dict of editable config property schemas.\n\n        The keys of the dict are config property names. The values are dicts\n        with the following keys: schema, description, value.\n        \"\"\"\n        schemas_dict = {}\n\n        for (property_name, instance) in cls._config_registry.items():\n            schemas_dict[property_name] = {\n                'schema': instance.schema,\n                'description': instance.description,\n                'value': instance.value\n            }\n\n        return schemas_dict\n\n    @classmethod\n    def get_all_config_property_names(cls):\n        \"\"\"Return a list of all the config property names.\n\n        Returns:\n            list. The list of all config property names.\n        \"\"\"\n        return list(cls._config_registry)\n\n\nPROMO_BAR_ENABLED = ConfigProperty(\n    'promo_bar_enabled', BOOL_SCHEMA,\n    'Whether the promo bar should be enabled for all users', False)\nPROMO_BAR_MESSAGE = ConfigProperty(\n    'promo_bar_message', UNICODE_SCHEMA,\n    'The message to show to all users if the promo bar is enabled', '')\n\nVMID_SHARED_SECRET_KEY_MAPPING = ConfigProperty(\n    'vmid_shared_secret_key_mapping', VMID_SHARED_SECRET_KEY_SCHEMA,\n    'VMID and shared secret key corresponding to that VM',\n    [{\n        'vm_id': feconf.DEFAULT_VM_ID,\n        'shared_secret_key': feconf.DEFAULT_VM_SHARED_SECRET\n    }])\n\nWHITELISTED_EXPLORATION_IDS_FOR_PLAYTHROUGHS = ConfigProperty(\n    'whitelisted_exploration_ids_for_playthroughs',\n    SET_OF_STRINGS_SCHEMA,\n    'The set of exploration IDs for recording playthrough issues', [\n        'umPkwp0L1M0-', 'MjZzEVOG47_1', '9trAQhj6uUC2', 'rfX8jNkPnA-1',\n        '0FBWxCE5egOw', '670bU6d9JGBh', 'aHikhPlxYgOH', '-tMgcP1i_4au',\n        'zW39GLG_BdN2', 'Xa3B_io-2WI5', '6Q6IyIDkjpYC', 'osw1m5Q3jK41'])\n\nTOPIC_IDS_FOR_CLASSROOM_PAGES = ConfigProperty(\n    'topic_ids_for_classroom_pages', SET_OF_CLASSROOM_DICTS_SCHEMA,\n    'The set of topic IDs for each classroom page.', [{\n        'name': 'Math',\n        'topic_ids': []\n    }]\n)\n\nRECORD_PLAYTHROUGH_PROBABILITY = ConfigProperty(\n    'record_playthrough_probability', FLOAT_SCHEMA,\n    'The probability of recording playthroughs', 0.2)\n\nIS_IMPROVEMENTS_TAB_ENABLED = ConfigProperty(\n    'is_improvements_tab_enabled', BOOL_SCHEMA,\n    'Exposes the Improvements Tab for creators in the exploration editor.',\n    False)\n\nALWAYS_ASK_LEARNERS_FOR_ANSWER_DETAILS = ConfigProperty(\n    'always_ask_learners_for_answer_details', BOOL_SCHEMA,\n    'Always ask learners for answer details. For testing -- do not use',\n    False)\n", "path": "core/domain/config_domain.py"}]}