Repository: oppia/oppia

Issue:
Collections should be unpublishable
Per discussion with @BenHenning, it should be possible for collections to be unpublished (at least by an admin), otherwise we have no way of reverting accidental publications. This should be fixed before collections functionality goes live (even in beta state).



Discussion:
typo: published -> **un**published

Fixed, thanks!

Hey @seanlip, though I have a PR open for an issue, can I work on this while that one gets reviewed ?

Hi @hitesh96db -- I've just had a look at that PR, and it seems to be going well, so yep, I've assigned you to this one. Thanks!

For now, I suggest following the general UX in explorations, where only an admin can unpublish a collection.

Having said that -- I think this somewhat depends on @BenHenning merging in #1433. I believe he's planning to get to that on Wednesday, so if you can get this done by Wednesday, it probably makes sense to make a PR to the 'collection-editor' branch -- otherwise, the PR will probably have to be made into 'develop' instead (after the 'collection-editor' changes are merged in).

Thanks!

@seanlip -- All this while, I was thinking of unpublishing explorations for some reason ! I'll probably pick this up later once collections gets merged in develop.

Would do you think about giving authors the power to unpublish an exploration ? 

@hitesh96db -- when an exploration is published, it becomes part of an open learning commons and is likely to be contributed to and improved by other people as well. This makes unpublication by individuals a slightly tricky proposition -- so, I'd say, let's not do that for now, since it merits a longer discussion.

#1433 is merged in. Please let me know if you have any questions about this moving forward. Feel free to email me or ping me on Gitter.

@hemanshu95 -- if you're looking for a starter project, would you like to try this one?

@kennho: would you like to have a go at taking on this issue? Note the instructions [here](https://github.com/oppia/oppia/issues/1587#issuecomment-196313127) for getting to the collections editor. You might want to coordinate with @oskarcieslik, too, if you're planning to put the 'unpublish' button in the settings page (though you could alternatively put it in the main page and move it later). 

Thanks!

sure I can try this =) @seanlip sorry for late reply!

Great, thanks @kennho!

Also, /cc @bhenning @amgowano @wxyxinyu who should be able to help if you have questions. :)


Resolving patch:
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
@@ -121,7 +121,10 @@ def get(self, collection_id):
             'is_public': rights_manager.is_collection_public(collection_id),
             'can_edit': can_edit,
             'collection_id': collection.id,
-            'title': collection.title
+            'title': collection.title,
+            'can_unpublish': rights_manager.Actor(
+                self.user_id).can_unpublish(
+                    rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id)
         })
 
         self.render_template('collection_editor/collection_editor.html')
@@ -196,7 +199,12 @@ def put(self, collection_id):
                     self.user_id, collection_id)
                 collection_services.index_collections_given_ids([
                     collection_id])
-            elif not self.is_admin:
+            elif rights_manager.Actor(self.user_id).can_unpublish(
+                    rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id):
+                rights_manager.unpublish_collection(self.user_id, collection_id)
+                collection_services.delete_documents_from_search_index([
+                    collection_id])
+            else:
                 raise self.InvalidInputException(
                     'Cannot unpublish a collection.')
 


Test patch:
diff --git a/core/controllers/collection_editor_test.py b/core/controllers/collection_editor_test.py
--- a/core/controllers/collection_editor_test.py
+++ b/core/controllers/collection_editor_test.py
@@ -15,6 +15,7 @@
 """Tests for the collection editor page."""
 
 from core.domain import collection_services
+from core.domain import collection_domain
 from core.domain import config_domain
 from core.domain import rights_manager
 from core.tests import test_utils
@@ -37,6 +38,7 @@ def setUp(self):
         self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)
         self.editor_id = self.get_user_id_from_email(self.EDITOR_EMAIL)
         self.viewer_id = self.get_user_id_from_email(self.VIEWER_EMAIL)
+        self.admin_id = self.get_user_id_from_email(self.ADMIN_EMAIL)
 
         self.set_admins([self.ADMIN_USERNAME])
 
@@ -96,3 +98,30 @@ def test_access_collection_editor_page(self):
         self.assertIn('Introduction to Collections in Oppia', response.body)
         self.assert_can_edit(response.body)
         self.logout()
+
+    def test_collection_rights_handler(self):
+        collection_id = 'collection_id'
+        collection = collection_domain.Collection.create_default_collection(
+            collection_id, 'A title', 'A Category', 'An Objective')
+        collection_services.save_new_collection(self.owner_id, collection)
+
+        # Check that collection is published correctly.
+        rights_manager.assign_role_for_collection(
+            self.owner_id, collection_id, self.editor_id,
+            rights_manager.ROLE_EDITOR)
+        rights_manager.publish_collection(self.owner_id, collection_id)
+
+        # Check that collection cannot be unpublished by non admin.
+        with self.assertRaisesRegexp(
+            Exception, 'This collection cannot be unpublished.'
+            ):
+            rights_manager.unpublish_collection(self.owner_id, collection_id)
+        collection_rights = rights_manager.get_collection_rights(collection_id)
+        self.assertEqual(collection_rights.status,
+                         rights_manager.ACTIVITY_STATUS_PUBLIC)
+
+        # Check that collection can be unpublished by admin.
+        rights_manager.unpublish_collection(self.admin_id, collection_id)
+        collection_rights = rights_manager.get_collection_rights(collection_id)
+        self.assertEqual(collection_rights.status,
+                         rights_manager.ACTIVITY_STATUS_PRIVATE)