We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Search input filter in collection editor might be catching valid names.
In the search bar in the collection editor, currently the regex allows "A-Za-z0-9-_ ". But when an exploration is selected from the dropdown it is displayed as "Name (explorationID)". On trying to make further edits to the search bar the bar is highlighted in red ( as '(' and ')') are invalid characters as per the regex. 
One of the demo explorations has title "Welcome to Oppia!" where '!' is not an allowed character in the title field. This is not being caught by the title validators. This also is shown up in red in the search field. 

Expected Behavior: The input field does not turn red for valid inputs

Search input filter in collection editor might be catching valid names.
In the search bar in the collection editor, currently the regex allows "A-Za-z0-9-_ ". But when an exploration is selected from the dropdown it is displayed as "Name (explorationID)". On trying to make further edits to the search bar the bar is highlighted in red ( as '(' and ')') are invalid characters as per the regex. 
One of the demo explorations has title "Welcome to Oppia!" where '!' is not an allowed character in the title field. This is not being caught by the title validators. This also is shown up in red in the search field. 

Expected Behavior: The input field does not turn red for valid inputs


--- 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/controllers/collection_editor.py`
Content:
```
1 # coding: utf-8
2 
3 # Copyright 2015 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 """Controllers for the collections editor."""
18 
19 from core.controllers import base
20 from core.domain import acl_decorators
21 from core.domain import collection_services
22 from core.domain import rights_manager
23 from core.domain import search_services
24 from core.domain import summary_services
25 from core.platform import models
26 import feconf
27 import utils
28 
29 current_user_services = models.Registry.import_current_user_services()
30 
31 
32 def _require_valid_version(version_from_payload, collection_version):
33     """Check that the payload version matches the given collection version."""
34     if version_from_payload is None:
35         raise base.BaseHandler.InvalidInputException(
36             'Invalid POST request: a version must be specified.')
37 
38     if version_from_payload != collection_version:
39         raise base.BaseHandler.InvalidInputException(
40             'Trying to update version %s of collection from version %s, '
41             'which is too old. Please reload the page and try again.'
42             % (collection_version, version_from_payload))
43 
44 
45 class CollectionEditorHandler(base.BaseHandler):
46     """Base class for all handlers for the collection editor page."""
47     pass
48 
49 
50 class CollectionEditorPage(CollectionEditorHandler):
51     """The editor page for a single collection."""
52 
53     @acl_decorators.can_edit_collection
54     def get(self, collection_id):
55         """Handles GET requests."""
56 
57         collection = collection_services.get_collection_by_id(
58             collection_id, strict=False)
59 
60         self.values.update({
61             'collection_id': collection.id,
62             'nav_mode': feconf.NAV_MODE_CREATE,
63             'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': (
64                 feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY),
65             'SHOW_COLLECTION_NAVIGATION_TAB_STATS': (
66                 feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS),
67             'TAG_REGEX': feconf.TAG_REGEX,
68         })
69 
70         self.render_template('pages/collection_editor/collection_editor.html')
71 
72 
73 class EditableCollectionDataHandler(CollectionEditorHandler):
74     """A data handler for collections which supports writing."""
75 
76     def _require_valid_version(self, version_from_payload, collection_version):
77         """Check that the payload version matches the given collection version.
78         """
79         if version_from_payload is None:
80             raise base.BaseHandler.InvalidInputException(
81                 'Invalid POST request: a version must be specified.')
82 
83         if version_from_payload != collection_version:
84             raise base.BaseHandler.InvalidInputException(
85                 'Trying to update version %s of collection from version %s, '
86                 'which is too old. Please reload the page and try again.'
87                 % (collection_version, version_from_payload))
88 
89     @acl_decorators.can_edit_collection
90     def get(self, collection_id):
91         """Populates the data on the individual collection page."""
92 
93         try:
94             # Try to retrieve collection
95             collection_dict = (
96                 summary_services.get_learner_collection_dict_by_id(
97                     collection_id, self.user,
98                     allow_invalid_explorations=True))
99         except Exception as e:
100             raise self.PageNotFoundException(e)
101 
102         self.values.update({
103             'collection': collection_dict
104         })
105 
106         self.render_json(self.values)
107 
108     @acl_decorators.can_edit_collection
109     def put(self, collection_id):
110         """Updates properties of the given collection."""
111 
112         collection = collection_services.get_collection_by_id(collection_id)
113         version = self.payload.get('version')
114         self._require_valid_version(version, collection.version)
115 
116         commit_message = self.payload.get('commit_message')
117         change_list = self.payload.get('change_list')
118 
119         try:
120             collection_services.update_collection(
121                 self.user_id, collection_id, change_list, commit_message)
122         except utils.ValidationError as e:
123             raise self.InvalidInputException(e)
124 
125         collection_dict = (
126             summary_services.get_learner_collection_dict_by_id(
127                 collection_id, self.user,
128                 allow_invalid_explorations=True))
129 
130         # Send the updated collection back to the frontend.
131         self.values.update({
132             'collection': collection_dict
133         })
134 
135         self.render_json(self.values)
136 
137 
138 class CollectionRightsHandler(CollectionEditorHandler):
139     """Handles management of collection editing rights."""
140 
141     @acl_decorators.can_edit_collection
142     def get(self, collection_id):
143         """Gets the editing rights for the given collection.
144 
145         Args:
146             collection_id: str. ID for the collection.
147         """
148         (collection, collection_rights) = (
149             collection_services.get_collection_and_collection_rights_by_id(
150                 collection_id))
151 
152         self.values.update({
153             'can_edit': True,
154             'can_unpublish': rights_manager.check_can_unpublish_activity(
155                 self.user, collection_rights),
156             'collection_id': collection.id,
157             'is_private': rights_manager.is_collection_private(collection_id),
158             'owner_names': rights_manager.get_collection_owner_names(
159                 collection_id)
160         })
161 
162         self.render_json(self.values)
163 
164 
165 class CollectionPublishHandler(base.BaseHandler):
166     """Handles the publication of the given collection."""
167 
168     @acl_decorators.can_publish_collection
169     def put(self, collection_id):
170         """Publishes the given collection."""
171         collection = collection_services.get_collection_by_id(collection_id)
172         version = self.payload.get('version')
173         _require_valid_version(version, collection.version)
174 
175         try:
176             collection.validate(strict=True)
177             collection_services.validate_exps_in_collection_are_public(
178                 collection)
179         except utils.ValidationError as e:
180             raise self.InvalidInputException(e)
181 
182         collection_services.publish_collection_and_update_user_profiles(
183             self.user, collection_id)
184         collection_services.index_collections_given_ids([
185             collection_id])
186 
187         collection_rights = rights_manager.get_collection_rights(
188             collection_id, strict=False)
189 
190         self.values.update({
191             'can_edit': True,
192             'can_unpublish': rights_manager.check_can_unpublish_activity(
193                 self.user, collection_rights),
194             'collection_id': collection.id,
195             'is_private': rights_manager.is_collection_private(collection_id),
196             'owner_names': rights_manager.get_collection_owner_names(
197                 collection_id)
198         })
199         self.render_json(self.values)
200 
201 
202 class CollectionUnpublishHandler(base.BaseHandler):
203     """Handles the unpublication of the given collection."""
204 
205     @acl_decorators.can_unpublish_collection
206     def put(self, collection_id):
207         """Unpublishes the given collection."""
208         collection = collection_services.get_collection_by_id(collection_id)
209         version = self.payload.get('version')
210         _require_valid_version(version, collection.version)
211 
212         rights_manager.unpublish_collection(self.user, collection_id)
213         search_services.delete_collections_from_search_index([
214             collection_id])
215 
216         collection_rights = rights_manager.get_collection_rights(
217             collection_id, strict=False)
218 
219         self.values.update({
220             'can_edit': True,
221             'can_unpublish': rights_manager.check_can_unpublish_activity(
222                 self.user, collection_rights),
223             'collection_id': collection.id,
224             'is_private': rights_manager.is_collection_private(collection_id),
225             'owner_names': rights_manager.get_collection_owner_names(
226                 collection_id)
227         })
228         self.render_json(self.values)
229 
230 
231 class ExplorationMetadataSearchHandler(base.BaseHandler):
232     """Provides data for exploration search."""
233 
234     @acl_decorators.open_access
235     def get(self):
236         """Handles GET requests."""
237         query_string = self.request.get('q')
238 
239         search_cursor = self.request.get('cursor', None)
240 
241         collection_node_metadata_list, new_search_cursor = (
242             summary_services.get_exp_metadata_dicts_matching_query(
243                 query_string, search_cursor, self.user))
244 
245         self.values.update({
246             'collection_node_metadata_list': collection_node_metadata_list,
247             'search_cursor': new_search_cursor,
248         })
249 
250         self.render_json(self.values)
251
```
--- 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/controllers/collection_editor.py b/core/controllers/collection_editor.py
--- a/core/controllers/collection_editor.py
+++ b/core/controllers/collection_editor.py
@@ -16,6 +16,8 @@
 
 """Controllers for the collections editor."""
 
+import base64
+
 from core.controllers import base
 from core.domain import acl_decorators
 from core.domain import collection_services
@@ -234,7 +236,7 @@
     @acl_decorators.open_access
     def get(self):
         """Handles GET requests."""
-        query_string = self.request.get('q')
+        query_string = base64.b64decode(self.request.get('q'))
 
         search_cursor = self.request.get('cursor', None)

{"golden_diff": "diff --git a/core/controllers/collection_editor.py b/core/controllers/collection_editor.py\n--- a/core/controllers/collection_editor.py\n+++ b/core/controllers/collection_editor.py\n@@ -16,6 +16,8 @@\n \n \"\"\"Controllers for the collections editor.\"\"\"\n \n+import base64\n+\n from core.controllers import base\n from core.domain import acl_decorators\n from core.domain import collection_services\n@@ -234,7 +236,7 @@\n     @acl_decorators.open_access\n     def get(self):\n         \"\"\"Handles GET requests.\"\"\"\n-        query_string = self.request.get('q')\n+        query_string = base64.b64decode(self.request.get('q'))\n \n         search_cursor = self.request.get('cursor', None)\n", "issue": "Search input filter in collection editor might be catching valid names.\nIn the search bar in the collection editor, currently the regex allows \"A-Za-z0-9-_ \". But when an exploration is selected from the dropdown it is displayed as \"Name (explorationID)\". On trying to make further edits to the search bar the bar is highlighted in red ( as '(' and ')') are invalid characters as per the regex. \r\nOne of the demo explorations has title \"Welcome to Oppia!\" where '!' is not an allowed character in the title field. This is not being caught by the title validators. This also is shown up in red in the search field. \r\n\r\nExpected Behavior: The input field does not turn red for valid inputs\r\n\nSearch input filter in collection editor might be catching valid names.\nIn the search bar in the collection editor, currently the regex allows \"A-Za-z0-9-_ \". But when an exploration is selected from the dropdown it is displayed as \"Name (explorationID)\". On trying to make further edits to the search bar the bar is highlighted in red ( as '(' and ')') are invalid characters as per the regex. \r\nOne of the demo explorations has title \"Welcome to Oppia!\" where '!' is not an allowed character in the title field. This is not being caught by the title validators. This also is shown up in red in the search field. \r\n\r\nExpected Behavior: The input field does not turn red for valid inputs\r\n\n", "before_files": [{"content": "# coding: utf-8\n\n# Copyright 2015 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\"\"\"Controllers for the collections editor.\"\"\"\n\nfrom core.controllers import base\nfrom core.domain import acl_decorators\nfrom core.domain import collection_services\nfrom core.domain import rights_manager\nfrom core.domain import search_services\nfrom core.domain import summary_services\nfrom core.platform import models\nimport feconf\nimport utils\n\ncurrent_user_services = models.Registry.import_current_user_services()\n\n\ndef _require_valid_version(version_from_payload, collection_version):\n    \"\"\"Check that the payload version matches the given collection version.\"\"\"\n    if version_from_payload is None:\n        raise base.BaseHandler.InvalidInputException(\n            'Invalid POST request: a version must be specified.')\n\n    if version_from_payload != collection_version:\n        raise base.BaseHandler.InvalidInputException(\n            'Trying to update version %s of collection from version %s, '\n            'which is too old. Please reload the page and try again.'\n            % (collection_version, version_from_payload))\n\n\nclass CollectionEditorHandler(base.BaseHandler):\n    \"\"\"Base class for all handlers for the collection editor page.\"\"\"\n    pass\n\n\nclass CollectionEditorPage(CollectionEditorHandler):\n    \"\"\"The editor page for a single collection.\"\"\"\n\n    @acl_decorators.can_edit_collection\n    def get(self, collection_id):\n        \"\"\"Handles GET requests.\"\"\"\n\n        collection = collection_services.get_collection_by_id(\n            collection_id, strict=False)\n\n        self.values.update({\n            'collection_id': collection.id,\n            'nav_mode': feconf.NAV_MODE_CREATE,\n            'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': (\n                feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY),\n            'SHOW_COLLECTION_NAVIGATION_TAB_STATS': (\n                feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS),\n            'TAG_REGEX': feconf.TAG_REGEX,\n        })\n\n        self.render_template('pages/collection_editor/collection_editor.html')\n\n\nclass EditableCollectionDataHandler(CollectionEditorHandler):\n    \"\"\"A data handler for collections which supports writing.\"\"\"\n\n    def _require_valid_version(self, version_from_payload, collection_version):\n        \"\"\"Check that the payload version matches the given collection version.\n        \"\"\"\n        if version_from_payload is None:\n            raise base.BaseHandler.InvalidInputException(\n                'Invalid POST request: a version must be specified.')\n\n        if version_from_payload != collection_version:\n            raise base.BaseHandler.InvalidInputException(\n                'Trying to update version %s of collection from version %s, '\n                'which is too old. Please reload the page and try again.'\n                % (collection_version, version_from_payload))\n\n    @acl_decorators.can_edit_collection\n    def get(self, collection_id):\n        \"\"\"Populates the data on the individual collection page.\"\"\"\n\n        try:\n            # Try to retrieve collection\n            collection_dict = (\n                summary_services.get_learner_collection_dict_by_id(\n                    collection_id, self.user,\n                    allow_invalid_explorations=True))\n        except Exception as e:\n            raise self.PageNotFoundException(e)\n\n        self.values.update({\n            'collection': collection_dict\n        })\n\n        self.render_json(self.values)\n\n    @acl_decorators.can_edit_collection\n    def put(self, collection_id):\n        \"\"\"Updates properties of the given collection.\"\"\"\n\n        collection = collection_services.get_collection_by_id(collection_id)\n        version = self.payload.get('version')\n        self._require_valid_version(version, collection.version)\n\n        commit_message = self.payload.get('commit_message')\n        change_list = self.payload.get('change_list')\n\n        try:\n            collection_services.update_collection(\n                self.user_id, collection_id, change_list, commit_message)\n        except utils.ValidationError as e:\n            raise self.InvalidInputException(e)\n\n        collection_dict = (\n            summary_services.get_learner_collection_dict_by_id(\n                collection_id, self.user,\n                allow_invalid_explorations=True))\n\n        # Send the updated collection back to the frontend.\n        self.values.update({\n            'collection': collection_dict\n        })\n\n        self.render_json(self.values)\n\n\nclass CollectionRightsHandler(CollectionEditorHandler):\n    \"\"\"Handles management of collection editing rights.\"\"\"\n\n    @acl_decorators.can_edit_collection\n    def get(self, collection_id):\n        \"\"\"Gets the editing rights for the given collection.\n\n        Args:\n            collection_id: str. ID for the collection.\n        \"\"\"\n        (collection, collection_rights) = (\n            collection_services.get_collection_and_collection_rights_by_id(\n                collection_id))\n\n        self.values.update({\n            'can_edit': True,\n            'can_unpublish': rights_manager.check_can_unpublish_activity(\n                self.user, collection_rights),\n            'collection_id': collection.id,\n            'is_private': rights_manager.is_collection_private(collection_id),\n            'owner_names': rights_manager.get_collection_owner_names(\n                collection_id)\n        })\n\n        self.render_json(self.values)\n\n\nclass CollectionPublishHandler(base.BaseHandler):\n    \"\"\"Handles the publication of the given collection.\"\"\"\n\n    @acl_decorators.can_publish_collection\n    def put(self, collection_id):\n        \"\"\"Publishes the given collection.\"\"\"\n        collection = collection_services.get_collection_by_id(collection_id)\n        version = self.payload.get('version')\n        _require_valid_version(version, collection.version)\n\n        try:\n            collection.validate(strict=True)\n            collection_services.validate_exps_in_collection_are_public(\n                collection)\n        except utils.ValidationError as e:\n            raise self.InvalidInputException(e)\n\n        collection_services.publish_collection_and_update_user_profiles(\n            self.user, collection_id)\n        collection_services.index_collections_given_ids([\n            collection_id])\n\n        collection_rights = rights_manager.get_collection_rights(\n            collection_id, strict=False)\n\n        self.values.update({\n            'can_edit': True,\n            'can_unpublish': rights_manager.check_can_unpublish_activity(\n                self.user, collection_rights),\n            'collection_id': collection.id,\n            'is_private': rights_manager.is_collection_private(collection_id),\n            'owner_names': rights_manager.get_collection_owner_names(\n                collection_id)\n        })\n        self.render_json(self.values)\n\n\nclass CollectionUnpublishHandler(base.BaseHandler):\n    \"\"\"Handles the unpublication of the given collection.\"\"\"\n\n    @acl_decorators.can_unpublish_collection\n    def put(self, collection_id):\n        \"\"\"Unpublishes the given collection.\"\"\"\n        collection = collection_services.get_collection_by_id(collection_id)\n        version = self.payload.get('version')\n        _require_valid_version(version, collection.version)\n\n        rights_manager.unpublish_collection(self.user, collection_id)\n        search_services.delete_collections_from_search_index([\n            collection_id])\n\n        collection_rights = rights_manager.get_collection_rights(\n            collection_id, strict=False)\n\n        self.values.update({\n            'can_edit': True,\n            'can_unpublish': rights_manager.check_can_unpublish_activity(\n                self.user, collection_rights),\n            'collection_id': collection.id,\n            'is_private': rights_manager.is_collection_private(collection_id),\n            'owner_names': rights_manager.get_collection_owner_names(\n                collection_id)\n        })\n        self.render_json(self.values)\n\n\nclass ExplorationMetadataSearchHandler(base.BaseHandler):\n    \"\"\"Provides data for exploration search.\"\"\"\n\n    @acl_decorators.open_access\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        query_string = self.request.get('q')\n\n        search_cursor = self.request.get('cursor', None)\n\n        collection_node_metadata_list, new_search_cursor = (\n            summary_services.get_exp_metadata_dicts_matching_query(\n                query_string, search_cursor, self.user))\n\n        self.values.update({\n            'collection_node_metadata_list': collection_node_metadata_list,\n            'search_cursor': new_search_cursor,\n        })\n\n        self.render_json(self.values)\n", "path": "core/controllers/collection_editor.py"}], "after_files": [{"content": "# coding: utf-8\n\n# Copyright 2015 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\"\"\"Controllers for the collections editor.\"\"\"\n\nimport base64\n\nfrom core.controllers import base\nfrom core.domain import acl_decorators\nfrom core.domain import collection_services\nfrom core.domain import rights_manager\nfrom core.domain import search_services\nfrom core.domain import summary_services\nfrom core.platform import models\nimport feconf\nimport utils\n\ncurrent_user_services = models.Registry.import_current_user_services()\n\n\ndef _require_valid_version(version_from_payload, collection_version):\n    \"\"\"Check that the payload version matches the given collection version.\"\"\"\n    if version_from_payload is None:\n        raise base.BaseHandler.InvalidInputException(\n            'Invalid POST request: a version must be specified.')\n\n    if version_from_payload != collection_version:\n        raise base.BaseHandler.InvalidInputException(\n            'Trying to update version %s of collection from version %s, '\n            'which is too old. Please reload the page and try again.'\n            % (collection_version, version_from_payload))\n\n\nclass CollectionEditorHandler(base.BaseHandler):\n    \"\"\"Base class for all handlers for the collection editor page.\"\"\"\n    pass\n\n\nclass CollectionEditorPage(CollectionEditorHandler):\n    \"\"\"The editor page for a single collection.\"\"\"\n\n    @acl_decorators.can_edit_collection\n    def get(self, collection_id):\n        \"\"\"Handles GET requests.\"\"\"\n\n        collection = collection_services.get_collection_by_id(\n            collection_id, strict=False)\n\n        self.values.update({\n            'collection_id': collection.id,\n            'nav_mode': feconf.NAV_MODE_CREATE,\n            'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': (\n                feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY),\n            'SHOW_COLLECTION_NAVIGATION_TAB_STATS': (\n                feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS),\n            'TAG_REGEX': feconf.TAG_REGEX,\n        })\n\n        self.render_template('pages/collection_editor/collection_editor.html')\n\n\nclass EditableCollectionDataHandler(CollectionEditorHandler):\n    \"\"\"A data handler for collections which supports writing.\"\"\"\n\n    def _require_valid_version(self, version_from_payload, collection_version):\n        \"\"\"Check that the payload version matches the given collection version.\n        \"\"\"\n        if version_from_payload is None:\n            raise base.BaseHandler.InvalidInputException(\n                'Invalid POST request: a version must be specified.')\n\n        if version_from_payload != collection_version:\n            raise base.BaseHandler.InvalidInputException(\n                'Trying to update version %s of collection from version %s, '\n                'which is too old. Please reload the page and try again.'\n                % (collection_version, version_from_payload))\n\n    @acl_decorators.can_edit_collection\n    def get(self, collection_id):\n        \"\"\"Populates the data on the individual collection page.\"\"\"\n\n        try:\n            # Try to retrieve collection\n            collection_dict = (\n                summary_services.get_learner_collection_dict_by_id(\n                    collection_id, self.user,\n                    allow_invalid_explorations=True))\n        except Exception as e:\n            raise self.PageNotFoundException(e)\n\n        self.values.update({\n            'collection': collection_dict\n        })\n\n        self.render_json(self.values)\n\n    @acl_decorators.can_edit_collection\n    def put(self, collection_id):\n        \"\"\"Updates properties of the given collection.\"\"\"\n\n        collection = collection_services.get_collection_by_id(collection_id)\n        version = self.payload.get('version')\n        self._require_valid_version(version, collection.version)\n\n        commit_message = self.payload.get('commit_message')\n        change_list = self.payload.get('change_list')\n\n        try:\n            collection_services.update_collection(\n                self.user_id, collection_id, change_list, commit_message)\n        except utils.ValidationError as e:\n            raise self.InvalidInputException(e)\n\n        collection_dict = (\n            summary_services.get_learner_collection_dict_by_id(\n                collection_id, self.user,\n                allow_invalid_explorations=True))\n\n        # Send the updated collection back to the frontend.\n        self.values.update({\n            'collection': collection_dict\n        })\n\n        self.render_json(self.values)\n\n\nclass CollectionRightsHandler(CollectionEditorHandler):\n    \"\"\"Handles management of collection editing rights.\"\"\"\n\n    @acl_decorators.can_edit_collection\n    def get(self, collection_id):\n        \"\"\"Gets the editing rights for the given collection.\n\n        Args:\n            collection_id: str. ID for the collection.\n        \"\"\"\n        (collection, collection_rights) = (\n            collection_services.get_collection_and_collection_rights_by_id(\n                collection_id))\n\n        self.values.update({\n            'can_edit': True,\n            'can_unpublish': rights_manager.check_can_unpublish_activity(\n                self.user, collection_rights),\n            'collection_id': collection.id,\n            'is_private': rights_manager.is_collection_private(collection_id),\n            'owner_names': rights_manager.get_collection_owner_names(\n                collection_id)\n        })\n\n        self.render_json(self.values)\n\n\nclass CollectionPublishHandler(base.BaseHandler):\n    \"\"\"Handles the publication of the given collection.\"\"\"\n\n    @acl_decorators.can_publish_collection\n    def put(self, collection_id):\n        \"\"\"Publishes the given collection.\"\"\"\n        collection = collection_services.get_collection_by_id(collection_id)\n        version = self.payload.get('version')\n        _require_valid_version(version, collection.version)\n\n        try:\n            collection.validate(strict=True)\n            collection_services.validate_exps_in_collection_are_public(\n                collection)\n        except utils.ValidationError as e:\n            raise self.InvalidInputException(e)\n\n        collection_services.publish_collection_and_update_user_profiles(\n            self.user, collection_id)\n        collection_services.index_collections_given_ids([\n            collection_id])\n\n        collection_rights = rights_manager.get_collection_rights(\n            collection_id, strict=False)\n\n        self.values.update({\n            'can_edit': True,\n            'can_unpublish': rights_manager.check_can_unpublish_activity(\n                self.user, collection_rights),\n            'collection_id': collection.id,\n            'is_private': rights_manager.is_collection_private(collection_id),\n            'owner_names': rights_manager.get_collection_owner_names(\n                collection_id)\n        })\n        self.render_json(self.values)\n\n\nclass CollectionUnpublishHandler(base.BaseHandler):\n    \"\"\"Handles the unpublication of the given collection.\"\"\"\n\n    @acl_decorators.can_unpublish_collection\n    def put(self, collection_id):\n        \"\"\"Unpublishes the given collection.\"\"\"\n        collection = collection_services.get_collection_by_id(collection_id)\n        version = self.payload.get('version')\n        _require_valid_version(version, collection.version)\n\n        rights_manager.unpublish_collection(self.user, collection_id)\n        search_services.delete_collections_from_search_index([\n            collection_id])\n\n        collection_rights = rights_manager.get_collection_rights(\n            collection_id, strict=False)\n\n        self.values.update({\n            'can_edit': True,\n            'can_unpublish': rights_manager.check_can_unpublish_activity(\n                self.user, collection_rights),\n            'collection_id': collection.id,\n            'is_private': rights_manager.is_collection_private(collection_id),\n            'owner_names': rights_manager.get_collection_owner_names(\n                collection_id)\n        })\n        self.render_json(self.values)\n\n\nclass ExplorationMetadataSearchHandler(base.BaseHandler):\n    \"\"\"Provides data for exploration search.\"\"\"\n\n    @acl_decorators.open_access\n    def get(self):\n        \"\"\"Handles GET requests.\"\"\"\n        query_string = base64.b64decode(self.request.get('q'))\n\n        search_cursor = self.request.get('cursor', None)\n\n        collection_node_metadata_list, new_search_cursor = (\n            summary_services.get_exp_metadata_dicts_matching_query(\n                query_string, search_cursor, self.user))\n\n        self.values.update({\n            'collection_node_metadata_list': collection_node_metadata_list,\n            'search_cursor': new_search_cursor,\n        })\n\n        self.render_json(self.values)\n", "path": "core/controllers/collection_editor.py"}]}