Skip to content

Commit

Permalink
Create the controllers for classroom page (oppia#7455)
Browse files Browse the repository at this point in the history
* Made the infrastructural changes for classroom page

* lint

* added imports

* made review changes

* fix oppia#7431

* fix test

* flipped flag

* removed comment
  • Loading branch information
aks681 authored and seanlip committed Aug 30, 2019
1 parent 05e671a commit 1521c07
Show file tree
Hide file tree
Showing 21 changed files with 661 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -304,15 +304,18 @@

# Topic project.
# Instead of * we have used _* to avoid topics_and_skills_dashboard related files.
/core/controllers/classroom*.py @aks681
/core/controllers/topic_*.py @aks681
/core/domain/subtopic_page_domain*.py @aks681
/core/domain/subtopic_page_services*.py @aks681
/core/domain/topic*.py @aks681
/core/storage/topic/ @aks681
/core/templates/dev/head/components/entity-creation-services/topic-creation.service.ts.ts @aks681
/core/templates/dev/head/domain/classroom/ @aks681
/core/templates/dev/head/domain/subtopic_viewer/ @aks681
/core/templates/dev/head/domain/topic/ @aks681
/core/templates/dev/head/domain/topic_viewer @aks681
/core/templates/dev/head/pages/classroom-page/ @aks681
/core/templates/dev/head/pages/subtopic-viewer-page/ @aks681
/core/templates/dev/head/pages/topic-editor-page/ @aks681
/core/templates/dev/head/pages/topic-viewer-page/ @aks681
Expand Down
77 changes: 77 additions & 0 deletions core/controllers/classroom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 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 classroom page."""
from __future__ import absolute_import # pylint: disable=import-only-modules

from constants import constants
from core.controllers import acl_decorators
from core.controllers import base
from core.domain import config_domain
from core.domain import topic_services
import feconf


class ClassroomPage(base.BaseHandler):
"""Renders the classroom page."""

@acl_decorators.open_access
def get(self, classroom_name):
"""Handles GET requests."""

if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
raise self.PageNotFoundException

classroom_name_is_valid = False
for classroom_dict in config_domain.TOPIC_IDS_FOR_CLASSROOM_PAGES.value:
if classroom_dict['name'] == classroom_name:
classroom_name_is_valid = True
break

if not classroom_name_is_valid:
raise self.PageNotFoundException

self.render_template('classroom-page.mainpage.html')


class ClassroomDataHandler(base.BaseHandler):
"""Manages the data that needs to be displayed to a learner on the classroom
page.
"""
GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

@acl_decorators.open_access
def get(self, classroom_name):
"""Handles GET requests."""

if not constants.ENABLE_NEW_STRUCTURE_PLAYERS:
raise self.PageNotFoundException

classroom_name_is_valid = False
for classroom_dict in config_domain.TOPIC_IDS_FOR_CLASSROOM_PAGES.value:
if classroom_dict['name'] == classroom_name:
classroom_name_is_valid = True
topic_ids = classroom_dict['topic_ids']
break

if not classroom_name_is_valid:
raise self.PageNotFoundException

topic_summaries = topic_services.get_multi_topic_summaries(topic_ids)
topic_summary_dicts = [summary.to_dict() for summary in topic_summaries]

self.values.update({
'topic_summary_dicts': topic_summary_dicts
})
self.render_json(self.values)
76 changes: 76 additions & 0 deletions core/controllers/classroom_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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 classroom page."""
from __future__ import absolute_import # pylint: disable=import-only-modules

from constants import constants
from core.tests import test_utils
import feconf


class BaseClassroomControllerTests(test_utils.GenericTestBase):

def setUp(self):
"""Completes the sign-up process for the various users."""
super(BaseClassroomControllerTests, self).setUp()
self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME)
self.user_id = self.get_user_id_from_email(self.NEW_USER_EMAIL)


class ClassroomPageTests(BaseClassroomControllerTests):

def test_any_user_can_access_classroom_page(self):
with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
response = self.get_html_response(
'%s/%s' % (feconf.CLASSROOM_URL_PREFIX, 'Math'))
self.assertIn('<classroom-page></classroom-page>', response)

def test_no_user_can_access_invalid_classroom_page(self):
with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
self.get_html_response(
'%s/%s' % (
feconf.CLASSROOM_URL_PREFIX, 'invalid_subject'),
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.CLASSROOM_URL_PREFIX, 'Math'),
expected_status_int=404)


class ClassroomDataHandlerTests(BaseClassroomControllerTests):

def test_get(self):
with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
json_response = self.get_json(
'%s/%s' % (feconf.CLASSROOM_DATA_HANDLER, 'Math'))
expected_dict = {
'topic_summary_dicts': []
}
self.assertDictContainsSubset(expected_dict, json_response)

def test_get_fails_for_invalid_classroom_name(self):
with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', True):
self.get_json(
'%s/%s' % (
feconf.CLASSROOM_DATA_HANDLER, 'invalid_subject'),
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_json(
'%s/%s' % (feconf.CLASSROOM_DATA_HANDLER, 'Math'),
expected_status_int=404)
32 changes: 32 additions & 0 deletions core/domain/config_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,30 @@
}],
}

SET_OF_CLASSROOM_DICTS_SCHEMA = {
'type': 'list',
'items': {
'type': 'dict',
'properties': [{
'name': 'name',
'schema': {
'type': 'unicode'
}
}, {
'name': 'topic_ids',
'schema': {
'type': 'list',
'items': {
'type': 'unicode',
},
'validators': [{
'id': 'is_uniquified',
}]
}
}]
}
}

VMID_SHARED_SECRET_KEY_SCHEMA = {
'type': 'list',
'items': {
Expand Down Expand Up @@ -285,6 +309,14 @@ def get_config_property_schemas(cls):
'0FBWxCE5egOw', '670bU6d9JGBh', 'aHikhPlxYgOH', '-tMgcP1i_4au',
'zW39GLG_BdN2', 'Xa3B_io-2WI5', '6Q6IyIDkjpYC', 'osw1m5Q3jK41'])

TOPIC_IDS_FOR_CLASSROOM_PAGES = ConfigProperty(
'topic_ids_for_classroom_pages', SET_OF_CLASSROOM_DICTS_SCHEMA,
'The set of topic IDs for each classroom page.', [{
'name': 'Math',
'topic_ids': []
}]
)

RECORD_PLAYTHROUGH_PROBABILITY = ConfigProperty(
'record_playthrough_probability', FLOAT_SCHEMA,
'The probability of recording playthroughs', 0.2)
Expand Down
41 changes: 41 additions & 0 deletions core/domain/topic_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ def get_all_topic_summaries():
return topic_summaries


def get_multi_topic_summaries(topic_ids):
"""Returns the summaries of all topics whose topic ids are passed in.
Args:
topic_ids: list(str). The IDs of topics for which summaries are to be
returned.
Returns:
list(TopicSummary). The list of summaries of all given topics present in
the datastore.
"""
topic_summaries_models = topic_models.TopicSummaryModel.get_multi(topic_ids)
topic_summaries = [
get_topic_summary_from_model(summary)
for summary in topic_summaries_models
if summary is not None]
return topic_summaries


def get_all_skill_ids_assigned_to_some_topic():
"""Returns the ids of all the skills that are linked to some topics.
Expand Down Expand Up @@ -952,6 +971,28 @@ def get_all_topic_rights():
return topic_rights


def filter_published_topic_ids(topic_ids):
"""Given list of topic IDs, returns the IDs of all topics that are published
in that list.
Args:
topic_ids: list(str). The list of topic ids.
Returns:
list(str). The topic IDs in the passed in list corresponding to
published topics.
"""
topic_rights_models = topic_models.TopicRightsModel.get_multi(topic_ids)
published_topic_ids = []
for ind, model in enumerate(topic_rights_models):
if model is None:
continue
rights = get_topic_rights_from_model(model)
if rights.topic_is_published:
published_topic_ids.append(topic_ids[ind])
return published_topic_ids


def check_can_edit_topic(user, topic_rights):
"""Checks whether the user can edit the given topic.
Expand Down
22 changes: 22 additions & 0 deletions core/domain/topic_services_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ def test_get_all_summaries(self):
self.assertEqual(topic_summaries[0].uncategorized_skill_count, 2)
self.assertEqual(topic_summaries[0].subtopic_count, 1)

def test_get_multi_summaries(self):
topic_summaries = topic_services.get_multi_topic_summaries([
self.TOPIC_ID, 'invalid_id'])

self.assertEqual(len(topic_summaries), 1)
self.assertEqual(topic_summaries[0].name, 'Name')
self.assertEqual(topic_summaries[0].canonical_story_count, 2)
self.assertEqual(topic_summaries[0].additional_story_count, 1)
self.assertEqual(topic_summaries[0].total_skill_count, 2)
self.assertEqual(topic_summaries[0].uncategorized_skill_count, 2)
self.assertEqual(topic_summaries[0].subtopic_count, 1)

def test_get_new_topic_id(self):
new_topic_id = topic_services.get_new_topic_id()

Expand Down Expand Up @@ -900,6 +912,16 @@ def test_admin_can_manage_topic(self):
self.assertTrue(topic_services.check_can_edit_topic(
self.user_admin, topic_rights))

def test_filter_published_topic_ids(self):
published_topic_ids = topic_services.filter_published_topic_ids([
self.TOPIC_ID, 'invalid_id'])
self.assertEqual(len(published_topic_ids), 0)
topic_services.publish_topic(self.TOPIC_ID, self.user_id_admin)
published_topic_ids = topic_services.filter_published_topic_ids([
self.TOPIC_ID, 'invalid_id'])
self.assertEqual(len(published_topic_ids), 1)
self.assertEqual(published_topic_ids[0], self.TOPIC_ID)

def test_publish_and_unpublish_topic(self):
topic_rights = topic_services.get_topic_rights(self.TOPIC_ID)
self.assertFalse(topic_rights.topic_is_published)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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.

/**
* @fileoverview Service to get topic data.
*/

require('domain/utilities/UrlInterpolationService.ts');

require('domain/classroom/classroom-domain.constants.ajs.ts');

angular.module('oppia').factory('ClassroomBackendApiService', [
'$http', '$q', 'UrlInterpolationService', 'CLASSROOOM_DATA_URL_TEMPLATE',
function($http, $q, UrlInterpolationService, CLASSROOOM_DATA_URL_TEMPLATE) {
var classroomDataDict = null;
var _fetchClassroomData = function(
classroomName, successCallback, errorCallback) {
var classroomDataUrl = UrlInterpolationService.interpolateUrl(
CLASSROOOM_DATA_URL_TEMPLATE, {
classroom_name: classroomName
});

$http.get(classroomDataUrl).then(function(response) {
classroomDataDict = angular.copy(response.data);
if (successCallback) {
successCallback(classroomDataDict);
}
}, function(errorResponse) {
if (errorCallback) {
errorCallback(errorResponse.data);
}
});
};

return {
fetchClassroomData: function(classroomName) {
return $q(function(resolve, reject) {
_fetchClassroomData(classroomName, resolve, reject);
});
}
};
}
]);
Loading

0 comments on commit 1521c07

Please sign in to comment.