Repository: oppia/oppia

Issue:
Create the Practice Sessions Page that integrates the question player into it.
Mocks for the practice Session Page - https://www.figma.com/file/X17OLoQ3HIbn51fDzIAFKxna/Question-Player?node-id=0%3A1


Discussion:

Resolving patch:
diff --git a/core/controllers/practice_sessions.py b/core/controllers/practice_sessions.py
new file mode 100644
--- /dev/null
+++ b/core/controllers/practice_sessions.py
@@ -0,0 +1,43 @@
+# Copyright 2018 The Oppia Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Controllers for the practice sessions page."""
+
+from constants import constants
+from core.controllers import acl_decorators
+from core.controllers import base
+from core.domain import topic_services
+
+
+class PracticeSessionsPage(base.BaseHandler):
+    """Renders the practice sessions page."""
+
+    @acl_decorators.can_access_topic_viewer_page
+    def get(self, topic_name):
+        """Handles GET requests."""
+
+        if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
+            raise self.PageNotFoundException
+
+        topic = topic_services.get_topic_by_name(topic_name)
+
+        if topic is None:
+            raise self.PageNotFoundException(
+                Exception('The topic with the given name doesn\'t exist.'))
+        topic_name = topic.name
+
+        self.values.update({
+            'topic_name': topic.name
+        })
+        self.render_template('/pages/practice_session/practice_session.html')
diff --git a/feconf.py b/feconf.py
--- a/feconf.py
+++ b/feconf.py
@@ -677,6 +677,7 @@ def get_empty_ratings():
 TOPIC_EDITOR_STORY_URL = '/topic_editor_story_handler'
 TOPIC_EDITOR_QUESTION_URL = '/topic_editor_question_handler'
 NEW_TOPIC_URL = '/topic_editor_handler/create_new'
+PRACTICE_SESSION_URL_PREFIX = '/practice_session'
 PREFERENCES_DATA_URL = '/preferenceshandler/data'
 QUESTION_EDITOR_DATA_URL_PREFIX = '/question_editor_handler/data'
 QUESTION_SKILL_LINK_URL_PREFIX = '/manage_question_skill_link'
diff --git a/main.py b/main.py
--- a/main.py
+++ b/main.py
@@ -36,6 +36,7 @@
 from core.controllers import library
 from core.controllers import moderator
 from core.controllers import pages
+from core.controllers import practice_sessions
 from core.controllers import profile
 from core.controllers import question_editor
 from core.controllers import reader
@@ -227,6 +228,9 @@ def ui_access_wrapper(self, *args, **kwargs):
     get_redirect_route(
         r'%s/<skill_id>' % feconf.NEW_QUESTION_URL,
         question_editor.QuestionCreationHandler),
+    get_redirect_route(
+        r'%s/<topic_name>' % feconf.PRACTICE_SESSION_URL_PREFIX,
+        practice_sessions.PracticeSessionsPage),
     get_redirect_route(
         r'%s/<story_id>' % feconf.STORY_DATA_HANDLER,
         story_viewer.StoryPageDataHandler),


Test patch:
diff --git a/core/controllers/practice_sessions_test.py b/core/controllers/practice_sessions_test.py
new file mode 100644
--- /dev/null
+++ b/core/controllers/practice_sessions_test.py
@@ -0,0 +1,85 @@
+# Copyright 2018 The Oppia Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for the practice sessions page."""
+
+from constants import constants
+from core.domain import topic_domain
+from core.domain import topic_services
+from core.domain import user_services
+from core.tests import test_utils
+import feconf
+
+
+class BasePracticeSessionsControllerTests(test_utils.GenericTestBase):
+
+    def setUp(self):
+        """Completes the sign-up process for the various users."""
+        super(BasePracticeSessionsControllerTests, self).setUp()
+        self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME)
+        self.admin_id = self.get_user_id_from_email(self.ADMIN_EMAIL)
+        self.set_admins([self.ADMIN_USERNAME])
+        self.admin = user_services.UserActionsInfo(self.admin_id)
+
+        self.topic_id = 'topic'
+        self.topic_id_1 = 'topic1'
+
+        self.topic = topic_domain.Topic.create_default_topic(
+            self.topic_id, 'public_topic_name')
+        self.topic.uncategorized_skill_ids.append('skill_id_1')
+        self.topic.subtopics.append(topic_domain.Subtopic(
+            1, 'subtopic_name', ['skill_id_2']))
+        self.topic.next_subtopic_id = 2
+        topic_services.save_new_topic(self.admin_id, self.topic)
+
+        self.topic = topic_domain.Topic.create_default_topic(
+            self.topic_id_1, 'private_topic_name')
+        topic_services.save_new_topic(self.admin_id, self.topic)
+
+        topic_services.publish_topic(self.topic_id, self.admin_id)
+
+
+class PracticeSessionsPageTests(BasePracticeSessionsControllerTests):
+
+    def test_any_user_can_access_practice_sessions_page(self):
+        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
+            self.get_html_response(
+                '%s/%s' % (
+                    feconf.PRACTICE_SESSION_URL_PREFIX,
+                    'public_topic_name'))
+
+
+    def test_no_user_can_access_unpublished_topic_practice_session_page(self):
+        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
+            self.get_html_response(
+                '%s/%s' % (
+                    feconf.PRACTICE_SESSION_URL_PREFIX, 'private_topic_name'),
+                expected_status_int=404)
+
+
+    def test_get_fails_when_new_structures_not_enabled(self):
+        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', False):
+            self.get_html_response(
+                '%s/%s' % (
+                    feconf.PRACTICE_SESSION_URL_PREFIX,
+                    'public_topic_name'),
+                expected_status_int=404)
+
+    def test_get_fails_when_topic_doesnt_exist(self):
+        with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', False):
+            self.get_html_response(
+                '%s/%s' % (
+                    feconf.PRACTICE_SESSION_URL_PREFIX,
+                    'non_existent_topic_name'),
+                expected_status_int=404)