Repository: oppia/oppia

Issue:
User can view exploration in play later list even after it's unpublished.
Reproduction Steps:

- Create an exploration and publish it.
- Login as another user (user2) and add that exploration to `play later`.
- Login as moderator and unpublish the exploration.
- Login as user2 and you can still view the exploration there. If you click on it, you are redirected to a 404 page. There is no indication that the exploration is unpublished.


Discussion:

Resolving patch:
diff --git a/core/controllers/learner_dashboard.py b/core/controllers/learner_dashboard.py
--- a/core/controllers/learner_dashboard.py
+++ b/core/controllers/learner_dashboard.py
@@ -47,7 +47,7 @@ class LearnerDashboardHandler(base.BaseHandler):
     @acl_decorators.can_access_learner_dashboard
     def get(self):
         """Handles GET requests."""
-        (learner_progress, number_of_deleted_activities,
+        (learner_progress, number_of_nonexistent_activities,
          completed_to_incomplete_collections) = (
              learner_progress_services.get_activity_progress(self.user_id))
 
@@ -107,7 +107,8 @@ def get(self):
             'incomplete_collections_list': incomplete_collection_summary_dicts,
             'exploration_playlist': exploration_playlist_summary_dicts,
             'collection_playlist': collection_playlist_summary_dicts,
-            'number_of_deleted_activities': number_of_deleted_activities,
+            'number_of_nonexistent_activities': (
+                number_of_nonexistent_activities),
             'completed_to_incomplete_collections': (
                 completed_to_incomplete_collections),
             'thread_summaries': thread_summaries,
diff --git a/core/domain/exp_services.py b/core/domain/exp_services.py
--- a/core/domain/exp_services.py
+++ b/core/domain/exp_services.py
@@ -1075,7 +1075,7 @@ def _get_last_updated_by_human_ms(exp_id):
 def publish_exploration_and_update_user_profiles(committer, exp_id):
     """Publishes the exploration with publish_exploration() function in
     rights_manager.py, as well as updates first_contribution_msec. Sends an
-    email to the subscribers of the commiter informing them that an exploration
+    email to the subscribers of the committer informing them that an exploration
     has been published.
 
     It is the responsibility of the caller to check that the exploration is
diff --git a/core/domain/learner_progress_services.py b/core/domain/learner_progress_services.py
--- a/core/domain/learner_progress_services.py
+++ b/core/domain/learner_progress_services.py
@@ -18,12 +18,12 @@
 
 from core.domain import collection_services
 from core.domain import exp_services
-from core.domain import learner_progress_domain
 from core.domain import learner_playlist_services
+from core.domain import learner_progress_domain
 from core.domain import subscription_services
 from core.domain import user_domain
 from core.platform import models
-
+import feconf
 import utils
 
 (user_models,) = models.Registry.import_models([models.NAMES.user])
@@ -609,6 +609,8 @@ def _get_filtered_completed_exp_summaries(exploration_summaries,
     for index, exploration_summary in enumerate(exploration_summaries):
         if exploration_summary is None:
             nonexistent_completed_exp_ids.append(exploration_ids[index])
+        elif exploration_summary.status != feconf.ACTIVITY_STATUS_PUBLIC:
+            nonexistent_completed_exp_ids.append(exploration_ids[index])
         else:
             filtered_completed_exp_summaries.append(exploration_summary)
 
@@ -676,6 +678,8 @@ def _get_filtered_completed_collection_summaries(user_id, collection_summaries,
     for index, collection_summary in enumerate(collection_summaries):
         if collection_summary is None:
             nonexistent_completed_collection_ids.append(collection_ids[index])
+        elif collection_summary.status != feconf.ACTIVITY_STATUS_PUBLIC:
+            nonexistent_completed_collection_ids.append(collection_ids[index])
         else:
             completed_exploration_ids = (
                 exploration_ids_completed_in_collections[index])
@@ -741,6 +745,8 @@ def _get_filtered_incomplete_exp_summaries(exploration_summaries,
     for index, exploration_summary in enumerate(exploration_summaries):
         if exploration_summary is None:
             nonexistent_incomplete_exp_ids.append(exploration_ids[index])
+        elif exploration_summary.status != feconf.ACTIVITY_STATUS_PUBLIC:
+            nonexistent_incomplete_exp_ids.append(exploration_ids[index])
         else:
             filtered_incomplete_exp_summaries.append(exploration_summary)
 
@@ -792,6 +798,8 @@ def _get_filtered_incomplete_collection_summaries(collection_summaries,
     for index, collection_summary in enumerate(collection_summaries):
         if collection_summary is None:
             nonexistent_incomplete_collection_ids.append(collection_ids[index])
+        elif collection_summary.status != feconf.ACTIVITY_STATUS_PUBLIC:
+            nonexistent_incomplete_collection_ids.append(collection_ids[index])
         else:
             filtered_incomplete_collection_summaries.append(collection_summary)
 
@@ -821,6 +829,8 @@ def _get_filtered_exp_playlist_summaries(exploration_summaries,
     for index, exploration_summary in enumerate(exploration_summaries):
         if exploration_summary is None:
             nonexistent_playlist_exp_ids.append(exploration_ids[index])
+        elif exploration_summary.status != feconf.ACTIVITY_STATUS_PUBLIC:
+            nonexistent_playlist_exp_ids.append(exploration_ids[index])
         else:
             filtered_exp_playlist_summaries.append(exploration_summary)
 
@@ -849,6 +859,8 @@ def _get_filtered_collection_playlist_summaries(collection_summaries,
     for index, collection_summary in enumerate(collection_summaries):
         if collection_summary is None:
             nonexistent_playlist_collection_ids.append(collection_ids[index])
+        elif collection_summary.status != feconf.ACTIVITY_STATUS_PUBLIC:
+            nonexistent_playlist_collection_ids.append(collection_ids[index])
         else:
             filtered_collection_playlist_summaries.append(collection_summary)
 
@@ -1062,7 +1074,7 @@ def get_activity_progress(user_id):
          _get_filtered_collection_playlist_summaries(
              collection_playlist_summaries, collection_playlist_ids))
 
-    number_of_deleted_activities = {
+    number_of_nonexistent_activities = {
         'incomplete_explorations': len(nonexistent_incomplete_exp_ids),
         'incomplete_collections': len(nonexistent_incomplete_collection_ids),
         'completed_explorations': len(nonexistent_completed_exp_ids),
@@ -1089,5 +1101,5 @@ def get_activity_progress(user_id):
         filtered_exp_playlist_summaries,
         filtered_collection_playlist_summaries)
 
-    return (learner_progress, number_of_deleted_activities,
+    return (learner_progress, number_of_nonexistent_activities,
             completed_to_incomplete_collection_titles)


Test patch:
diff --git a/core/controllers/learner_dashboard_test.py b/core/controllers/learner_dashboard_test.py
--- a/core/controllers/learner_dashboard_test.py
+++ b/core/controllers/learner_dashboard_test.py
@@ -55,6 +55,7 @@ def test_can_see_completed_explorations(self):
 
         self.save_new_default_exploration(
             self.EXP_ID_1, self.owner_id, title=self.EXP_TITLE_1)
+        self.publish_exploration(self.owner_id, self.EXP_ID_1)
 
         learner_progress_services.mark_exploration_as_completed(
             self.viewer_id, self.EXP_ID_1)
@@ -72,6 +73,7 @@ def test_can_see_completed_collections(self):
 
         self.save_new_default_collection(
             self.COL_ID_1, self.owner_id, title=self.COL_TITLE_1)
+        self.publish_collection(self.owner_id, self.COL_ID_1)
 
         learner_progress_services.mark_collection_as_completed(
             self.viewer_id, self.COL_ID_1)
@@ -89,6 +91,7 @@ def test_can_see_incomplete_explorations(self):
 
         self.save_new_default_exploration(
             self.EXP_ID_1, self.owner_id, title=self.EXP_TITLE_1)
+        self.publish_exploration(self.owner_id, self.EXP_ID_1)
 
         state_name = 'state_name'
         version = 1
@@ -109,6 +112,7 @@ def test_can_see_incomplete_collections(self):
 
         self.save_new_default_collection(
             self.COL_ID_1, self.owner_id, title=self.COL_TITLE_1)
+        self.publish_collection(self.owner_id, self.COL_ID_1)
 
         learner_progress_services.mark_collection_as_incomplete(
             self.viewer_id, self.COL_ID_1)
@@ -126,6 +130,7 @@ def test_can_see_exploration_playlist(self):
 
         self.save_new_default_exploration(
             self.EXP_ID_1, self.owner_id, title=self.EXP_TITLE_1)
+        self.publish_exploration(self.owner_id, self.EXP_ID_1)
 
         learner_progress_services.add_exp_to_learner_playlist(
             self.viewer_id, self.EXP_ID_1)
@@ -143,6 +148,7 @@ def test_can_see_collection_playlist(self):
 
         self.save_new_default_collection(
             self.COL_ID_1, self.owner_id, title=self.COL_TITLE_1)
+        self.publish_collection(self.owner_id, self.COL_ID_1)
 
         learner_progress_services.add_collection_to_learner_playlist(
             self.viewer_id, self.COL_ID_1)
@@ -172,17 +178,23 @@ def test_get_learner_dashboard_ids(self):
 
         self.save_new_default_exploration(
             self.EXP_ID_1, self.owner_id, title=self.EXP_TITLE_1)
+        self.publish_exploration(self.owner_id, self.EXP_ID_1)
         self.save_new_default_exploration(
             self.EXP_ID_2, self.owner_id, title=self.EXP_TITLE_2)
+        self.publish_exploration(self.owner_id, self.EXP_ID_2)
         self.save_new_default_exploration(
             self.EXP_ID_3, self.owner_id, title=self.EXP_TITLE_3)
+        self.publish_exploration(self.owner_id, self.EXP_ID_3)
 
         self.save_new_default_collection(
             self.COL_ID_1, self.owner_id, title=self.COL_TITLE_1)
+        self.publish_collection(self.owner_id, self.COL_ID_1)
         self.save_new_default_collection(
             self.COL_ID_2, self.owner_id, title=self.COL_TITLE_2)
+        self.publish_collection(self.owner_id, self.COL_ID_2)
         self.save_new_default_collection(
             self.COL_ID_3, self.owner_id, title=self.COL_TITLE_3)
+        self.publish_collection(self.owner_id, self.COL_ID_3)
 
         state_name = 'state_name'
         version = 1
diff --git a/core/domain/learner_progress_services_test.py b/core/domain/learner_progress_services_test.py
--- a/core/domain/learner_progress_services_test.py
+++ b/core/domain/learner_progress_services_test.py
@@ -54,29 +54,37 @@ def setUp(self):
         self.save_new_valid_exploration(
             self.EXP_ID_0, self.owner_id, title='Bridges in England',
             category='Architecture', language_code='en')
+        self.publish_exploration(self.owner_id, self.EXP_ID_0)
         self.save_new_valid_exploration(
             self.EXP_ID_1, self.owner_id, title='Sillat Suomi',
             category='Architecture', language_code='fi')
+        self.publish_exploration(self.owner_id, self.EXP_ID_1)
         self.save_new_valid_exploration(
             self.EXP_ID_2, self.user_id, title='Introduce Oppia',
             category='Welcome', language_code='en')
+        self.publish_exploration(self.user_id, self.EXP_ID_2)
         self.save_new_valid_exploration(
             self.EXP_ID_3, self.owner_id, title='Welcome Oppia',
             category='Welcome', language_code='en')
+        self.publish_exploration(self.owner_id, self.EXP_ID_3)
 
         # Save a few collections.
         self.save_new_default_collection(
             self.COL_ID_0, self.owner_id, title='Bridges',
             category='Architecture')
+        self.publish_collection(self.owner_id, self.COL_ID_0)
         self.save_new_default_collection(
             self.COL_ID_1, self.owner_id, title='Introduce Oppia',
             category='Welcome')
+        self.publish_collection(self.owner_id, self.COL_ID_1)
         self.save_new_default_collection(
             self.COL_ID_2, self.user_id,
             title='Introduce Interactions in Oppia', category='Welcome')
+        self.publish_collection(self.user_id, self.COL_ID_2)
         self.save_new_default_collection(
             self.COL_ID_3, self.owner_id, title='Welcome Oppia Collection',
             category='Welcome')
+        self.publish_collection(self.owner_id, self.COL_ID_3)
 
     def _get_all_completed_exp_ids(self, user_id):
         completed_activities_model = (
diff --git a/core/tests/test_utils.py b/core/tests/test_utils.py
--- a/core/tests/test_utils.py
+++ b/core/tests/test_utils.py
@@ -32,6 +32,7 @@
 from core.domain import exp_domain
 from core.domain import exp_services
 from core.domain import rights_manager
+from core.domain import user_services
 from core.platform import models
 import feconf
 import main
@@ -503,6 +504,11 @@ def save_new_exp_with_states_schema_v0(self, exp_id, user_id, title):
         exploration = exp_services.get_exploration_from_model(exp_model)
         exp_services.create_and_save_state_id_mapping_model(exploration, [])
 
+    def publish_exploration(self, owner_id, exploration_id):
+        """Publish the exploration of given exploration_id."""
+        committer = user_services.UserActionsInfo(owner_id)
+        rights_manager.publish_exploration(committer, exploration_id)
+
     def save_new_default_collection(
             self, collection_id, owner_id, title='A title',
             category='A category', objective='An objective',
@@ -539,6 +545,11 @@ def save_new_valid_collection(
         collection_services.save_new_collection(owner_id, collection)
         return collection
 
+    def publish_collection(self, owner_id, collection_id):
+        """Publish the collection of given collection_id."""
+        committer = user_services.UserActionsInfo(owner_id)
+        rights_manager.publish_collection(committer, collection_id)
+
     def get_updated_param_dict(
             self, param_dict, param_changes, exp_param_specs):
         """Updates a param dict using the given list of param_changes.