def test_get_public_and_filtered_private_summary_dicts_for_creator(self): # If a new exploration is created by another user (Bob) and not public, # then Albert cannot see it when querying for explorations. displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3, self.EXP_ID_5], editor_user_id=self.albert_id)) self.assertEqual(len(displayable_summaries), 2) self.assertEqual(displayable_summaries[0]['id'], self.EXP_ID_1) self.assertEqual(displayable_summaries[1]['id'], self.EXP_ID_2) # However, if Albert is granted editor access to Bob's exploration, # then Albert has access to the corresponding summary. rights_manager.assign_role_for_exploration( self.bob_id, self.EXP_ID_5, self.albert_id, rights_manager.ROLE_EDITOR) displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3, self.EXP_ID_5], editor_user_id=self.albert_id)) self.assertEqual(len(displayable_summaries), 3) self.assertEqual(displayable_summaries[0]['status'], 'private') self.assertEqual(displayable_summaries[0]['id'], self.EXP_ID_1) self.assertEqual(displayable_summaries[1]['status'], 'public') self.assertEqual(displayable_summaries[1]['id'], self.EXP_ID_2) self.assertEqual(displayable_summaries[2]['status'], 'private') self.assertEqual(displayable_summaries[2]['id'], self.EXP_ID_5)
def get(self): """Handles GET requests.""" exp_ids = self.normalized_request.get('stringified_exp_ids') include_private_exps = self.normalized_request.get( 'include_private_explorations') editor_user_id = self.user_id if include_private_exps else None if not editor_user_id: include_private_exps = False if (not isinstance(exp_ids, list) or not all( isinstance( exp_id, python_utils.BASESTRING) for exp_id in exp_ids)): raise self.PageNotFoundException if include_private_exps: summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids, user=self.user)) else: summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) self.values.update({ 'summaries': summaries }) self.render_json(self.values)
def get(self): """Handles GET requests.""" try: exp_ids = json.loads(self.request.get('stringified_exp_ids')) except Exception: raise self.PageNotFoundException include_private_exps_str = self.request.get( 'include_private_explorations') include_private_exps = ( include_private_exps_str.lower() == 'true' if include_private_exps_str else False) editor_user_id = self.user_id if include_private_exps else None if not editor_user_id: include_private_exps = False if (not isinstance(exp_ids, list) or not all([ isinstance(exp_id, basestring) for exp_id in exp_ids])): raise self.PageNotFoundException if include_private_exps: summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids, user=self.user)) else: summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) self.values.update({ 'summaries': summaries }) self.render_json(self.values)
def test_get_public_and_filtered_private_summary_dicts_for_creator(self): # If a new exploration is created by another user (Bob) and not public, # then Albert cannot see it when querying for explorations. displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3, self.EXP_ID_5], user=self.albert)) self.assertEqual(len(displayable_summaries), 2) self.assertEqual(displayable_summaries[0]['id'], self.EXP_ID_1) self.assertEqual(displayable_summaries[1]['id'], self.EXP_ID_2) # However, if Albert is granted editor access to Bob's exploration, # then Albert has access to the corresponding summary. rights_manager.assign_role_for_exploration( self.bob, self.EXP_ID_5, self.albert_id, rights_domain.ROLE_EDITOR) displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3, self.EXP_ID_5], user=self.albert)) self.assertEqual(len(displayable_summaries), 3) self.assertEqual(displayable_summaries[0]['status'], 'private') self.assertEqual(displayable_summaries[0]['id'], self.EXP_ID_1) self.assertEqual(displayable_summaries[1]['status'], 'public') self.assertEqual(displayable_summaries[1]['id'], self.EXP_ID_2) self.assertEqual(displayable_summaries[2]['status'], 'private') self.assertEqual(displayable_summaries[2]['id'], self.EXP_ID_5)
def get(self): """Handles GET requests.""" try: exp_ids = json.loads(self.request.get('stringified_exp_ids')) except Exception: raise self.PageNotFoundException include_private_exps_str = self.request.get( 'include_private_explorations') include_private_exps = ( include_private_exps_str.lower() == 'true' if include_private_exps_str else False) editor_user_id = self.user_id if include_private_exps else None if not editor_user_id: include_private_exps = False if (not isinstance(exp_ids, list) or not all([ isinstance(exp_id, basestring) for exp_id in exp_ids])): raise self.PageNotFoundException if include_private_exps: summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids, editor_user_id=editor_user_id)) else: summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) self.values.update({ 'summaries': summaries }) self.render_json(self.values)
def get(self): """Handles GET requests.""" # TODO(sll): Figure out what to do about explorations in categories # other than those explicitly listed. query_string = self.request.get('q') search_cursor = self.request.get('cursor', None) exp_ids, search_cursor = ( exp_services.get_exploration_ids_matching_query( query_string, cursor=search_cursor)) explorations_list = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) if len(explorations_list) == feconf.DEFAULT_QUERY_LIMIT: logging.error( '%s explorations were fetched to load the gallery page. ' 'You may be running up against the default query limits.' % feconf.DEFAULT_QUERY_LIMIT) preferred_language_codes = [feconf.DEFAULT_LANGUAGE_CODE] if self.user_id: user_settings = user_services.get_user_settings(self.user_id) preferred_language_codes = user_settings.preferred_language_codes self.values.update({ 'explorations_list': explorations_list, 'preferred_language_codes': preferred_language_codes, 'search_cursor': search_cursor, }) self.render_json(self.values)
def test_get_displayable_exp_summary_dicts_matching_ids(self): # A list of exp_id's are passed in: # EXP_ID_1 -- private exploration # EXP_ID_2 -- pubished exploration # EXP_ID_3 -- deleted exploration # Should only return [EXP_ID_2] displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3])) self.assertEqual(len(displayable_summaries), 1) self.assertEqual( displayable_summaries[0]['id'], self.EXP_ID_2) self.assertEqual( displayable_summaries[0]['status'], rights_manager.ACTIVITY_STATUS_PUBLIC) self.assertEqual( displayable_summaries[0]['community_owned'], False) self.assertEqual( displayable_summaries[0]['language_code'], feconf.DEFAULT_LANGUAGE_CODE) self.assertEqual( displayable_summaries[0]['category'], 'A category') self.assertEqual( displayable_summaries[0]['ratings'], feconf.get_empty_ratings()) self.assertEqual( displayable_summaries[0]['title'], 'Exploration 2 Albert title') self.assertEqual( displayable_summaries[0]['contributor_names'], [self.ALBERT_NAME]) self.assertEqual( displayable_summaries[0]['objective'], 'An objective') self.assertEqual(displayable_summaries[0]['num_views'], 0) self.assertIn('last_updated_msec', displayable_summaries[0])
def test_get_displayable_exp_summary_dicts_matching_ids(self): # A list of exp_id's are passed in: # EXP_ID_1 -- private exploration owned by Albert. # EXP_ID_2 -- pubished exploration owned by Albert. # EXP_ID_3 -- deleted exploration. # EXP_ID_5 -- private exploration owned by Bob. # Should only return [EXP_ID_2]. displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3, self.EXP_ID_5])) expected_summary = { 'category': u'A category', 'community_owned': False, 'id': self.EXP_ID_2, 'language_code': constants.DEFAULT_LANGUAGE_CODE, 'num_views': 0, 'objective': u'An objective', 'ratings': feconf.get_empty_ratings(), 'status': 'public', 'tags': [], 'thumbnail_bg_color': '#a33f40', 'thumbnail_icon_url': '/subjects/Lightbulb.svg', 'title': u'Exploration 2 Albert title', } self.assertIn('last_updated_msec', displayable_summaries[0]) self.assertDictContainsSubset( expected_summary, displayable_summaries[0])
def get_matching_activity_dicts(query_string, search_cursor): """Given a query string and a search cursor, returns a list of activity dicts that satisfy the search query. """ # We only populate collections in the initial load, since the current # frontend search infrastructure is set up to only deal with one search # cursor at a time. # TODO(sll): Remove this special casing. collection_ids = [] if not search_cursor: collection_ids, _ = ( collection_services.get_collection_ids_matching_query( query_string)) exp_ids, new_search_cursor = ( exp_services.get_exploration_ids_matching_query( query_string, cursor=search_cursor)) activity_list = [] activity_list = ( summary_services.get_displayable_collection_summary_dicts_matching_ids( collection_ids)) activity_list += ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) if len(activity_list) == feconf.DEFAULT_QUERY_LIMIT: logging.error( '%s activities were fetched to load the library page. ' 'You may be running up against the default query limits.' % feconf.DEFAULT_QUERY_LIMIT) return activity_list, new_search_cursor
def test_get_displayable_exp_summary_dicts_matching_ids(self): # A list of exp_id's are passed in: # EXP_ID_1 -- private exploration owned by Albert # EXP_ID_2 -- pubished exploration owned by Albert # EXP_ID_3 -- deleted exploration # EXP_ID_5 -- private exploration owned by Bob # Should only return [EXP_ID_2] displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3, self.EXP_ID_5])) expected_summary = { 'category': u'A category', 'community_owned': False, 'id': self.EXP_ID_2, 'language_code': feconf.DEFAULT_LANGUAGE_CODE, 'num_views': 0, 'objective': u'An objective', 'ratings': feconf.get_empty_ratings(), 'status': 'public', 'tags': [], 'thumbnail_bg_color': '#a33f40', 'thumbnail_icon_url': self.get_static_asset_url( '/images/subjects/Lightbulb.svg'), 'title': u'Exploration 2 Albert title', } self.assertIn('last_updated_msec', displayable_summaries[0]) self.assertDictContainsSubset(expected_summary, displayable_summaries[0])
def get(self, story_id): """Handles GET requests.""" if not constants.ENABLE_NEW_STRUCTURE_PLAYERS: raise self.PageNotFoundException story = story_fetchers.get_story_by_id(story_id) completed_node_ids = [ completed_node.id for completed_node in story_fetchers.get_completed_nodes_in_story(self.user_id, story_id)] ordered_node_dicts = [ node.to_dict() for node in story.story_contents.get_ordered_nodes() ] for node in ordered_node_dicts: node['completed'] = False if node['id'] in completed_node_ids: node['completed'] = True exp_ids = [ node['exploration_id'] for node in ordered_node_dicts] exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids, user=self.user)) for ind, node in enumerate(ordered_node_dicts): node['exp_summary_dict'] = exp_summary_dicts[ind] self.values.update({ 'story_title': story.title, 'story_description': story.description, 'story_nodes': ordered_node_dicts }) self.render_json(self.values)
def test_get_displayable_exp_summary_dicts_matching_ids(self): # A list of exp_id's are passed in: # EXP_ID_1 -- private exploration # EXP_ID_2 -- pubished exploration # EXP_ID_3 -- deleted exploration # Should only return [EXP_ID_2] displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3])) expected_summary = { 'status': u'public', 'thumbnail_bg_color': '#05a69a', 'community_owned': False, 'tags': [], 'thumbnail_icon_url': '/images/gallery/thumbnails/Lightbulb.svg', 'language_code': feconf.DEFAULT_LANGUAGE_CODE, 'human_readable_contributors_summary': { self.ALBERT_NAME: { 'num_commits': 2, 'profile_picture_data_url': None } }, 'id': self.EXP_ID_2, 'category': u'A category', 'ratings': feconf.get_empty_ratings(), 'title': u'Exploration 2 Albert title', 'num_views': 0, 'objective': u'An objective' } self.assertIn('last_updated_msec', displayable_summaries[0]) self.assertDictContainsSubset(expected_summary, displayable_summaries[0])
def test_get_displayable_exp_summary_dicts_matching_ids(self): # A list of exp_id's are passed in: # EXP_ID_1 -- private exploration # EXP_ID_2 -- pubished exploration # EXP_ID_3 -- deleted exploration # Should only return [EXP_ID_2] displayable_summaries = summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3] ) expected_summary = { "status": u"public", "thumbnail_bg_color": "#05a69a", "community_owned": False, "tags": [], "thumbnail_icon_url": "/images/gallery/thumbnails/Lightbulb.svg", "language_code": feconf.DEFAULT_LANGUAGE_CODE, "human_readable_contributors_summary": {self.ALBERT_NAME: 2}, "id": self.EXP_ID_2, "category": u"A category", "ratings": feconf.get_empty_ratings(), "title": u"Exploration 2 Albert title", "num_views": 0, "objective": u"An objective", "contributor_names": [self.ALBERT_NAME], } self.assertIn("last_updated_msec", displayable_summaries[0]) self.assertDictContainsSubset(expected_summary, displayable_summaries[0])
def test_get_displayable_exp_summary_dicts_matching_ids(self): # A list of exp_id's are passed in: # EXP_ID_1 -- private exploration # EXP_ID_2 -- pubished exploration # EXP_ID_3 -- deleted exploration # Should only return [EXP_ID_2] displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3])) expected_summary = { 'status': u'public', 'thumbnail_bg_color': '#05a69a', 'community_owned': False, 'tags': [], 'thumbnail_icon_url': '/images/gallery/thumbnails/Lightbulb.svg', 'language_code': feconf.DEFAULT_LANGUAGE_CODE, 'human_readable_contributors_summary': {self.ALBERT_NAME: 2}, 'id': self.EXP_ID_2, 'category': u'A category', 'ratings': feconf.get_empty_ratings(), 'title': u'Exploration 2 Albert title', 'num_views': 0, 'objective': u'An objective', 'contributor_names': [self.ALBERT_NAME] } self.assertIn('last_updated_msec', displayable_summaries[0]) self.assertDictContainsSubset(expected_summary, displayable_summaries[0])
def get(self, username): """Handles GET requests.""" if not username: raise self.PageNotFoundException user_settings = user_services.get_user_settings_from_username(username) if not user_settings: raise self.PageNotFoundException created_exp_summary_dicts = [] edited_exp_summary_dicts = [] subscriber_ids = subscription_services.get_all_subscribers_of_creator( user_settings.user_id) is_already_subscribed = (self.user_id in subscriber_ids) is_user_visiting_own_profile = (self.user_id == user_settings.user_id) user_contributions = user_services.get_user_contributions( user_settings.user_id) if user_contributions: created_exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.created_exploration_ids)) edited_exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.edited_exploration_ids)) profile_is_of_current_user = (self.username == username) self.values.update({ 'profile_is_of_current_user': profile_is_of_current_user, 'profile_username': user_settings.username, 'user_bio': user_settings.user_bio, 'subject_interests': user_settings.subject_interests, 'first_contribution_msec': ( user_settings.first_contribution_msec if user_settings.first_contribution_msec else None), 'profile_picture_data_url': user_settings.profile_picture_data_url, 'user_impact_score':user_services.get_user_impact_score( user_settings.user_id), 'created_exp_summary_dicts': created_exp_summary_dicts, 'edited_exp_summary_dicts': edited_exp_summary_dicts, 'is_already_subscribed': is_already_subscribed, 'is_user_visiting_own_profile': is_user_visiting_own_profile }) self.render_json(self.values)
def get(self, username): """Handles GET requests.""" user_settings = user_services.get_user_settings_from_username(username) if not user_settings: raise self.PageNotFoundException created_exp_summary_dicts = [] edited_exp_summary_dicts = [] subscriber_ids = subscription_services.get_all_subscribers_of_creator( user_settings.user_id) is_already_subscribed = (self.user_id in subscriber_ids) is_user_visiting_own_profile = (self.user_id == user_settings.user_id) user_contributions = user_services.get_user_contributions( user_settings.user_id) if user_contributions: created_exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.created_exploration_ids)) edited_exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.edited_exploration_ids)) profile_is_of_current_user = (self.username == username) self.values.update({ 'profile_is_of_current_user': profile_is_of_current_user, 'profile_username': user_settings.username, 'user_bio': user_settings.user_bio, 'subject_interests': user_settings.subject_interests, 'first_contribution_msec': ( user_settings.first_contribution_msec if user_settings.first_contribution_msec else None), 'profile_picture_data_url': user_settings.profile_picture_data_url, 'user_impact_score': user_services.get_user_impact_score( user_settings.user_id), 'created_exp_summary_dicts': created_exp_summary_dicts, 'edited_exp_summary_dicts': edited_exp_summary_dicts, 'is_already_subscribed': is_already_subscribed, 'is_user_visiting_own_profile': is_user_visiting_own_profile }) self.render_json(self.values)
def get_matching_activity_dicts(query_string, categories, language_codes, search_offset): """Given the details of a query and a search offset, returns a list of activity dicts that satisfy the query. Args: query_string: str. The search query string (this is what the user enters). categories: list(str). The list of categories to query for. If it is empty, no category filter is applied to the results. If it is not empty, then a result is considered valid if it matches at least one of these categories. language_codes: list(str). The list of language codes to query for. If it is empty, no language code filter is applied to the results. If it is not empty, then a result is considered valid if it matches at least one of these language codes. search_offset: int or None. Offset indicating where, in the list of exploration search results, to start the search from. If None, collection search results are returned first before the explorations. Returns: tuple. A tuple consisting of two elements: - list(dict). Each element in this list is a collection or exploration summary dict, representing a search result. - int. The exploration index offset from which to start the next search. """ # We only populate collections in the initial load, since the current # frontend search infrastructure is set up to only deal with one search # offset at a time. # TODO(sll): Remove this special casing. collection_ids = [] if not search_offset: collection_ids, _ = ( collection_services.get_collection_ids_matching_query( query_string, categories, language_codes)) exp_ids, new_search_offset = ( exp_services.get_exploration_ids_matching_query(query_string, categories, language_codes, offset=search_offset)) activity_list = ( summary_services.get_displayable_collection_summary_dicts_matching_ids( collection_ids) + summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) if len(activity_list) == feconf.DEFAULT_QUERY_LIMIT: logging.exception( '%s activities were fetched to load the library page. ' 'You may be running up against the default query limits.' % feconf.DEFAULT_QUERY_LIMIT) return activity_list, new_search_offset
def post(self, story_id, node_id): if not constants.ENABLE_NEW_STRUCTURE_VIEWER_UPDATES: raise self.PageNotFoundException try: story_fetchers.get_node_index_by_story_id_and_node_id( story_id, node_id) except Exception as e: raise self.PageNotFoundException(e) story = story_fetchers.get_story_by_id(story_id) completed_nodes = story_fetchers.get_completed_nodes_in_story( self.user_id, story_id) completed_node_ids = [ completed_node.id for completed_node in completed_nodes ] ordered_nodes = [ node for node in story.story_contents.get_ordered_nodes() ] next_exp_ids = [] next_node_id = None if not node_id in completed_node_ids: story_services.record_completed_node_in_story_context( self.user_id, story_id, node_id) completed_nodes = story_fetchers.get_completed_nodes_in_story( self.user_id, story_id) completed_node_ids = [ completed_node.id for completed_node in completed_nodes ] for node in ordered_nodes: if node.id not in completed_node_ids: next_exp_ids = [node.exploration_id] next_node_id = node.id break ready_for_review_test = False exp_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( next_exp_ids)) if ((len(exp_summaries) != 0 and len(completed_nodes) % constants.NUM_EXPLORATIONS_PER_REVIEW_TEST == 0) or (len(completed_nodes) == len(ordered_nodes))): ready_for_review_test = True return self.render_json({ 'summaries': exp_summaries, 'ready_for_review_test': ready_for_review_test, 'next_node_id': next_node_id })
def get(self, username): """Handles GET requests.""" if not username: raise self.PageNotFoundException user_settings = user_services.get_user_settings_from_username(username) if not user_settings: raise self.PageNotFoundException created_exploration_summary_dicts = [] edited_exploration_summary_dicts = [] user_contributions = user_services.get_user_contributions( user_settings.user_id) if user_contributions: created_exploration_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.created_exploration_ids)) edited_exploration_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.edited_exploration_ids)) self.values.update({ 'user_bio': user_settings.user_bio, 'subject_interests': user_settings.subject_interests, 'first_contribution_msec': ( user_settings.first_contribution_msec if user_settings.first_contribution_msec else None), 'profile_picture_data_url': user_settings.profile_picture_data_url, 'user_impact_score':user_services.get_user_impact_score( user_settings.user_id), 'created_exploration_summary_dicts': ( created_exploration_summary_dicts), 'edited_exploration_summary_dicts': ( edited_exploration_summary_dicts) }) self.render_json(self.values)
def get(self, exploration_id): """Handles GET requests.""" collection_id = self.request.get('collection_id') include_system_recommendations = self.request.get( 'include_system_recommendations') try: author_recommended_exp_ids = json.loads( self.request.get('stringified_author_recommended_ids')) except Exception: raise self.PageNotFoundException auto_recommended_exp_ids = [] if self.user_id and collection_id: next_exp_ids_in_collection = ( collection_services. get_next_exploration_ids_to_complete_by_user( # pylint: disable=line-too-long self.user_id, collection_id)) auto_recommended_exp_ids = list( set(next_exp_ids_in_collection) - set(author_recommended_exp_ids)) else: next_exp_ids_in_collection = [] if collection_id: collection = collection_services.get_collection_by_id( collection_id) next_exp_ids_in_collection = ( collection.get_next_exploration_ids_in_sequence( exploration_id)) if next_exp_ids_in_collection: auto_recommended_exp_ids = list( set(next_exp_ids_in_collection) - set(author_recommended_exp_ids)) elif include_system_recommendations: system_chosen_exp_ids = ( recommendations_services.get_exploration_recommendations( exploration_id)) filtered_exp_ids = list( set(system_chosen_exp_ids) - set(author_recommended_exp_ids)) auto_recommended_exp_ids = random.sample( filtered_exp_ids, min(MAX_SYSTEM_RECOMMENDATIONS, len(filtered_exp_ids))) self.values.update({ 'summaries': (summary_services.get_displayable_exp_summary_dicts_matching_ids( author_recommended_exp_ids + auto_recommended_exp_ids)), }) self.render_json(self.values)
def get(self, username): """Handles GET requests.""" if not username: raise self.PageNotFoundException user_settings = user_services.get_user_settings_from_username(username) if not user_settings: raise self.PageNotFoundException created_exp_summary_dicts = [] edited_exp_summary_dicts = [] user_contributions = user_services.get_user_contributions( user_settings.user_id) if user_contributions: created_exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.created_exploration_ids)) edited_exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( user_contributions.edited_exploration_ids)) self.values.update({ 'profile_username': user_settings.username, 'user_bio': user_settings.user_bio, 'subject_interests': user_settings.subject_interests, 'first_contribution_msec': ( user_settings.first_contribution_msec if user_settings.first_contribution_msec else None), 'profile_picture_data_url': user_settings.profile_picture_data_url, 'user_impact_score':user_services.get_user_impact_score( user_settings.user_id), 'created_exp_summary_dicts': created_exp_summary_dicts, 'edited_exp_summary_dicts': edited_exp_summary_dicts, }) self.render_json(self.values)
def get(self, exploration_id): """Handles GET requests.""" collection_id = self.request.get('collection_id') include_system_recommendations = self.request.get( 'include_system_recommendations') try: author_recommended_exp_ids = json.loads(self.request.get( 'stringified_author_recommended_ids')) except Exception: raise self.PageNotFoundException auto_recommended_exp_ids = [] if self.user_id and collection_id: next_exp_ids_in_collection = ( collection_services.get_next_exploration_ids_to_complete_by_user( # pylint: disable=line-too-long self.user_id, collection_id)) auto_recommended_exp_ids = list( set(next_exp_ids_in_collection) - set(author_recommended_exp_ids)) else: next_exp_ids_in_collection = [] if collection_id: collection = collection_services.get_collection_by_id( collection_id) next_exp_ids_in_collection = ( collection.get_next_exploration_ids_in_sequence( exploration_id)) if next_exp_ids_in_collection: auto_recommended_exp_ids = list( set(next_exp_ids_in_collection) - set(author_recommended_exp_ids)) elif include_system_recommendations: system_chosen_exp_ids = ( recommendations_services.get_exploration_recommendations( exploration_id)) filtered_exp_ids = list( set(system_chosen_exp_ids) - set(author_recommended_exp_ids)) auto_recommended_exp_ids = random.sample( filtered_exp_ids, min(MAX_SYSTEM_RECOMMENDATIONS, len(filtered_exp_ids))) self.values.update({ 'summaries': ( summary_services.get_displayable_exp_summary_dicts_matching_ids( author_recommended_exp_ids + auto_recommended_exp_ids)), }) self.render_json(self.values)
def get(self): """Handles GET requests.""" try: exp_ids = json.loads(self.request.get('stringified_exp_ids')) except Exception: raise self.PageNotFoundException if (not isinstance(exp_ids, list) or not all([ isinstance(exp_id, basestring) for exp_id in exp_ids])): raise self.PageNotFoundException self.values.update({ 'summaries': ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) }) self.render_json(self.values)
def get(self): """Handles GET requests for group pages.""" # TODO(sll): Support index pages for other language codes. group_name = self.request.get('group_name') activity_list = [] header_i18n_id = '' if group_name == feconf.LIBRARY_GROUP_RECENTLY_PUBLISHED: recently_published_summary_dicts = ( summary_services.get_recently_published_exp_summary_dicts( feconf.RECENTLY_PUBLISHED_QUERY_LIMIT_FULL_PAGE)) if recently_published_summary_dicts: activity_list = recently_published_summary_dicts header_i18n_id = feconf.LIBRARY_CATEGORY_RECENTLY_PUBLISHED elif group_name == feconf.LIBRARY_GROUP_TOP_RATED: top_rated_activity_summary_dicts = ( summary_services.get_top_rated_exploration_summary_dicts( [feconf.DEFAULT_LANGUAGE_CODE], feconf.NUMBER_OF_TOP_RATED_EXPLORATIONS_FULL_PAGE)) if top_rated_activity_summary_dicts: activity_list = top_rated_activity_summary_dicts header_i18n_id = feconf.LIBRARY_CATEGORY_TOP_RATED_EXPLORATIONS # TODO: create a Splash controller and implement this properly. elif group_name == feconf.LIBRARY_CATEGORY_SPLASH_PAGE_FEATURED: splash_page_featured_exploration_ids = [ '0', 'yvqBFOQNDz5e', 'BvpDpLSmO2Iu', 'gC4_ggkWar-L'] activity_list = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( splash_page_featured_exploration_ids)) else: return self.PageNotFoundException preferred_language_codes = [feconf.DEFAULT_LANGUAGE_CODE] if self.user_id: user_settings = user_services.get_user_settings(self.user_id) preferred_language_codes = user_settings.preferred_language_codes self.values.update({ 'activity_list': activity_list, 'header_i18n_id': header_i18n_id, 'preferred_language_codes': preferred_language_codes, }) self.render_json(self.values)
def get_matching_exploration_dicts(query_string, search_cursor): """Given a query string and a search cursor, returns a list of exploration dicts that satisfy the search query. """ exp_ids, new_search_cursor = ( exp_services.get_exploration_ids_matching_query( query_string, cursor=search_cursor)) explorations_list = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) if len(explorations_list) == feconf.DEFAULT_QUERY_LIMIT: logging.error( '%s explorations were fetched to load the search results. ' 'You may be running up against the default query limits.' % feconf.DEFAULT_QUERY_LIMIT) return explorations_list, new_search_cursor
def get_matching_exploration_dicts(query_string, search_cursor): """Given a query string and a search cursor, returns a list of exploration dicts that satisfy the search query. """ exp_ids, new_search_cursor = ( exp_services.get_exploration_ids_matching_query(query_string, cursor=search_cursor)) explorations_list = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) if len(explorations_list) == feconf.DEFAULT_QUERY_LIMIT: logging.error( '%s explorations were fetched to load the search results. ' 'You may be running up against the default query limits.' % feconf.DEFAULT_QUERY_LIMIT) return explorations_list, new_search_cursor
def get(self): """Handles GET requests.""" try: exp_ids = json.loads(self.request.get('stringified_exp_ids')) except Exception: raise self.PageNotFoundException if (not isinstance(exp_ids, list) or not all([isinstance(exp_id, basestring) for exp_id in exp_ids])): raise self.PageNotFoundException self.values.update({ 'summaries': (summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) }) self.render_json(self.values)
def test_library_handler_with_given_category_and_language_code(self): self.login(self.ADMIN_EMAIL) exp_id = exp_fetchers.get_new_exploration_id() self.save_new_valid_exploration(exp_id, self.admin_id) self.publish_exploration(self.admin_id, exp_id) exp_services.index_explorations_given_ids([exp_id]) response_dict = self.get_json( feconf.LIBRARY_SEARCH_DATA_URL, params={ 'category': 'A category', 'language_code': 'en' }) activity_list = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [exp_id])) self.assertEqual(response_dict['activity_list'], activity_list) self.logout()
def post(self, story_id, node_id): story = story_fetchers.get_story_by_id(story_id) completed_nodes = story_fetchers.get_completed_nodes_in_story( self.user_id, story_id) completed_node_ids = [ completed_node.id for completed_node in completed_nodes ] ordered_nodes = story.story_contents.get_ordered_nodes() (next_exp_ids, next_node_id, completed_node_ids) = (self._record_node_completion( story_id, node_id, completed_node_ids, ordered_nodes)) ready_for_review_test = False exp_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( next_exp_ids)) # If there are no questions for any of the acquired skills that the # learner has completed, do not show review tests. acquired_skills = skill_fetchers.get_multi_skills( story.get_acquired_skill_ids_for_node_ids(completed_node_ids)) acquired_skill_ids = [skill.id for skill in acquired_skills] questions_available = len( question_services.get_questions_by_skill_ids( 1, acquired_skill_ids, False)) > 0 learner_completed_story = len(completed_node_ids) == len(ordered_nodes) learner_at_review_point_in_story = ( len(exp_summaries) != 0 and (len(completed_node_ids) & constants.NUM_EXPLORATIONS_PER_REVIEW_TEST == 0)) if questions_available and (learner_at_review_point_in_story or learner_completed_story): ready_for_review_test = True return self.render_json({ 'summaries': exp_summaries, 'ready_for_review_test': ready_for_review_test, 'next_node_id': next_node_id })
def get(self, story_id): """Handles GET requests.""" if not constants.ENABLE_NEW_STRUCTURE_PLAYERS: raise self.PageNotFoundException story = story_services.get_story_by_id(story_id) completed_node_ids = [ completed_node.id for completed_node in story_services.get_completed_nodes_in_story( self.user_id, story_id) ] ordered_node_dicts = [ node.to_dict() for node in story.story_contents.get_ordered_nodes() # TODO(aks681): Once the story publication is done, add a check so # that only if all explorations in the story are published, can the # story itself be published. After which, remove the following # condition. if node.exploration_id ] for node in ordered_node_dicts: node['completed'] = False if node['id'] in completed_node_ids: node['completed'] = True exp_ids = [node['exploration_id'] for node in ordered_node_dicts] exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids, user=self.user)) for ind, node in enumerate(ordered_node_dicts): node['exp_summary_dict'] = exp_summary_dicts[ind] self.values.update({ 'story_title': story.title, 'story_description': story.description, 'story_nodes': ordered_node_dicts }) self.render_json(self.values)
def get(self, story_id): """Handles GET requests.""" story = story_fetchers.get_story_by_id(story_id) topic_id = story.corresponding_topic_id topic_name = topic_fetchers.get_topic_by_id(topic_id).name completed_node_ids = [ completed_node.id for completed_node in story_fetchers.get_completed_nodes_in_story( self.user_id, story_id) ] ordered_node_dicts = [ node.to_dict() for node in story.story_contents.get_ordered_nodes() ] for node in ordered_node_dicts: node['completed'] = False if node['id'] in completed_node_ids: node['completed'] = True exp_ids = [node['exploration_id'] for node in ordered_node_dicts] exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids, user=self.user)) for ind, node in enumerate(ordered_node_dicts): node['exp_summary_dict'] = exp_summary_dicts[ind] self.values.update({ 'story_id': story.id, 'story_title': story.title, 'story_description': story.description, 'story_nodes': ordered_node_dicts, 'topic_name': topic_name, 'meta_tag_content': story.meta_tag_content }) self.render_json(self.values)
def get_matching_activity_dicts(query_string, search_cursor): """Given a query string and a search cursor, returns a list of activity dicts that satisfy the search query. """ collection_ids, search_cursor = ( collection_services.get_collection_ids_matching_query( query_string, cursor=search_cursor)) exp_ids, new_search_cursor = ( exp_services.get_exploration_ids_matching_query( query_string, cursor=search_cursor)) activity_list = [] activity_list = ( summary_services.get_displayable_collection_summary_dicts_matching_ids( collection_ids)) activity_list += ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) if len(activity_list) == feconf.DEFAULT_QUERY_LIMIT: logging.error( '%s activities were fetched to load the library page. ' 'You may be running up against the default query limits.' % feconf.DEFAULT_QUERY_LIMIT) return activity_list, new_search_cursor
def test_get_displayable_exp_summary_dicts_matching_ids(self): # A list of exp_id's are passed in: # EXP_ID_1 -- private exploration owned by Albert # EXP_ID_2 -- pubished exploration owned by Albert # EXP_ID_3 -- deleted exploration # EXP_ID_5 -- private exploration owned by Bob # Should only return [EXP_ID_2] displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_1, self.EXP_ID_2, self.EXP_ID_3, self.EXP_ID_5])) expected_summary = { 'category': u'A category', 'community_owned': False, 'human_readable_contributors_summary': { self.ALBERT_NAME: { 'num_commits': 2, 'profile_picture_data_url': (user_services.DEFAULT_IDENTICON_DATA_URL) } }, 'id': self.EXP_ID_2, 'language_code': feconf.DEFAULT_LANGUAGE_CODE, 'num_views': 0, 'objective': u'An objective', 'ratings': feconf.get_empty_ratings(), 'status': 'public', 'tags': [], 'thumbnail_bg_color': '#a33f40', 'thumbnail_icon_url': '/images/subjects/Lightbulb.svg', 'title': u'Exploration 2 Albert title', } self.assertIn('last_updated_msec', displayable_summaries[0]) self.assertDictContainsSubset(expected_summary, displayable_summaries[0])
def setUp(self): """Completes the sign up process for the various users.""" super(BaseStoryViewerControllerTests, self).setUp() self.VIEWER_EMAIL = '*****@*****.**' self.VIEWER_USERNAME = '******' 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.login(self.ADMIN_EMAIL) self.TOPIC_ID = 'topic_id' self.STORY_ID = 'story_id' self.STORY_URL_FRAGMENT = 'title-one' self.NODE_ID_1 = 'node_1' self.NODE_ID_2 = 'node_2' self.NODE_ID_3 = 'node_3' self.EXP_ID_0 = '0' self.EXP_ID_1 = '1' self.EXP_ID_7 = '7' self.save_new_valid_exploration(self.EXP_ID_0, self.admin_id, title='Title 1', end_state_name='End') self.save_new_valid_exploration(self.EXP_ID_1, self.admin_id, title='Title 2', end_state_name='End') self.save_new_valid_exploration(self.EXP_ID_7, self.admin_id, title='Title 3', end_state_name='End') self.publish_exploration(self.admin_id, self.EXP_ID_0) self.publish_exploration(self.admin_id, self.EXP_ID_1) self.publish_exploration(self.admin_id, self.EXP_ID_7) story = story_domain.Story.create_default_story( self.STORY_ID, 'Title', 'Description', self.TOPIC_ID, self.STORY_URL_FRAGMENT) exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_0, self.EXP_ID_1, self.EXP_ID_7], user=self.admin)) self.node_1 = { 'id': self.NODE_ID_1, 'title': 'Title 1', 'description': 'Description 1', 'thumbnail_filename': 'image_1.svg', 'thumbnail_bg_color': constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0], 'destination_node_ids': ['node_3'], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': self.EXP_ID_1, 'exp_summary_dict': exp_summary_dicts[1], 'completed': False } self.node_2 = { 'id': self.NODE_ID_2, 'title': 'Title 2', 'description': 'Description 2', 'thumbnail_filename': 'image_2.svg', 'thumbnail_bg_color': constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0], 'destination_node_ids': ['node_1'], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': self.EXP_ID_0, 'exp_summary_dict': exp_summary_dicts[0], 'completed': True } self.node_3 = { 'id': self.NODE_ID_3, 'title': 'Title 3', 'description': 'Description 3', 'thumbnail_filename': 'image_3.svg', 'thumbnail_bg_color': constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0], 'destination_node_ids': [], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': self.EXP_ID_7, 'exp_summary_dict': exp_summary_dicts[2], 'completed': False } story.story_contents.nodes = [ story_domain.StoryNode.from_dict(self.node_1), story_domain.StoryNode.from_dict(self.node_2), story_domain.StoryNode.from_dict(self.node_3) ] self.nodes = story.story_contents.nodes story.story_contents.initial_node_id = 'node_2' story.story_contents.next_node_id = 'node_4' story_services.save_new_story(self.admin_id, story) self.save_new_topic(self.TOPIC_ID, 'user', name='Topic', description='A new topic', canonical_story_ids=[story.id], additional_story_ids=[], uncategorized_skill_ids=[], subtopics=[], next_subtopic_id=0) topic_services.publish_topic(self.TOPIC_ID, self.admin_id) topic_services.publish_story(self.TOPIC_ID, self.STORY_ID, self.admin_id) self.logout() self.signup(self.VIEWER_EMAIL, self.VIEWER_USERNAME) self.viewer_id = self.get_user_id_from_email(self.VIEWER_EMAIL) self.login(self.VIEWER_EMAIL) self._record_completion(self.viewer_id, self.STORY_ID, self.NODE_ID_2)
def get_learner_collection_dict_by_id( collection_id, user_id, strict=True, allow_invalid_explorations=False, version=None): """Creates and returns a dictionary representation of a collection given by the provided collection ID. This dictionary contains extra information along with the dict returned by collection_domain.Collection.to_dict() which includes useful data for the collection learner view. The information includes progress in the collection, information about explorations referenced within the collection, and a slightly nicer data structure for frontend work. This raises a ValidationError if the collection retrieved using the given ID references non-existent explorations. """ collection = get_collection_by_id( collection_id, strict=strict, version=version) exp_ids = collection.exploration_ids exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) exp_summaries_dict_map = { exp_summary_dict['id']: exp_summary_dict for exp_summary_dict in exp_summary_dicts } # TODO(bhenning): Users should not be recommended explorations they have # completed outside the context of a collection (see #1461). next_exploration_ids = None completed_exploration_ids = None if user_id: completed_exploration_ids = _get_valid_completed_exploration_ids( user_id, collection_id, collection) next_exploration_ids = collection.get_next_exploration_ids( completed_exploration_ids) else: # If the user is not logged in or they have not completed any of # the explorations yet within the context of this collection, # recommend the initial explorations. next_exploration_ids = collection.init_exploration_ids completed_exploration_ids = [] collection_dict = collection.to_dict() collection_dict['skills'] = collection.skills collection_dict['playthrough_dict'] = { 'next_exploration_ids': next_exploration_ids, 'completed_exploration_ids': completed_exploration_ids } collection_dict['version'] = collection.version collection_is_public = rights_manager.is_collection_public(collection_id) # Insert an 'exploration' dict into each collection node, where the # dict includes meta information about the exploration (ID and title). for collection_node in collection_dict['nodes']: exploration_id = collection_node['exploration_id'] summary_dict = exp_summaries_dict_map.get(exploration_id) if not allow_invalid_explorations: if not summary_dict: raise utils.ValidationError( 'Expected collection to only reference valid ' 'explorations, but found an exploration with ID: %s (was ' 'the exploration deleted?)' % exploration_id) if collection_is_public and rights_manager.is_exploration_private( exploration_id): raise utils.ValidationError( 'Cannot reference a private exploration within a public ' 'collection, exploration ID: %s' % exploration_id) collection_node['exploration'] = { 'exists': bool(summary_dict) } if summary_dict: collection_node['exploration'].update(summary_dict) return collection_dict
def test_get_displayable_exp_summary_dicts_matching_ids_with_invalid_exp_id( self): displayable_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( ['invalid_exp_id'])) self.assertEqual(displayable_summaries, [])
def setUp(self): """Completes the sign up process for the various users.""" super(BaseStoryViewerControllerTests, self).setUp() self.VIEWER_EMAIL = '*****@*****.**' self.VIEWER_USERNAME = '******' 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.login(self.ADMIN_EMAIL) self.TOPIC_ID = 'topic_id' self.STORY_ID_1 = 'story_id_1' self.NODE_ID_1 = 'node_1' self.NODE_ID_2 = 'node_2' self.NODE_ID_3 = 'node_3' self.EXP_ID = 'exp_id' self.save_new_valid_exploration( self.EXP_ID, self.admin_id, title='Bridges in England', category='Architecture', language_code='en') rights_manager.publish_exploration(self.admin, self.EXP_ID) story = story_domain.Story.create_default_story( self.STORY_ID_1, 'Title', self.TOPIC_ID) story.description = ('Description') exp_summary_dict = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID], user=self.admin)[0]) self.node_1 = { 'id': self.NODE_ID_1, 'title': 'Title 1', 'destination_node_ids': ['node_3'], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': self.EXP_ID, 'exp_summary_dict': exp_summary_dict, 'completed': False } self.node_2 = { 'id': self.NODE_ID_2, 'title': 'Title 2', 'destination_node_ids': ['node_1'], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': self.EXP_ID, 'exp_summary_dict': exp_summary_dict, 'completed': True } self.node_3 = { 'id': self.NODE_ID_3, 'title': 'Title 3', 'destination_node_ids': [], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': None } story.story_contents.nodes = [ story_domain.StoryNode.from_dict(self.node_1), story_domain.StoryNode.from_dict(self.node_2), story_domain.StoryNode.from_dict(self.node_3) ] self.nodes = story.story_contents.nodes story.story_contents.initial_node_id = 'node_2' story.story_contents.next_node_id = 'node_4' story_services.save_new_story(self.admin_id, story) self.save_new_topic( self.TOPIC_ID, 'user', 'Topic', 'A new topic', [story.id], [], [], [], 0) topic_services.publish_topic(self.TOPIC_ID, self.admin_id) topic_services.publish_story( self.TOPIC_ID, self.STORY_ID_1, self.admin_id) self.logout() self.signup(self.VIEWER_EMAIL, self.VIEWER_USERNAME) self.viewer_id = self.get_user_id_from_email(self.VIEWER_EMAIL) self.login(self.VIEWER_EMAIL) self._record_completion(self.viewer_id, self.STORY_ID_1, self.NODE_ID_2)
def test_mark_topic_as_learnt_and_story_as_completed(self): self.NEW_USER_EMAIL = '*****@*****.**' self.NEW_USER_USERNAME = '******' self.save_new_valid_exploration(self.EXP_ID_3, self.admin_id, title='Title 3', end_state_name='End', correctness_feedback_enabled=True) self.publish_exploration(self.admin_id, self.EXP_ID_3) story = story_domain.Story.create_default_story( self.NEW_STORY_ID, 'Title', 'Description', self.TOPIC_ID, self.STORY_URL_FRAGMENT_TWO) story.meta_tag_content = 'story meta content' exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_3], user=self.admin)) self.node_1 = { 'id': self.NODE_ID_1, 'title': 'Title 1', 'description': 'Description 1', 'thumbnail_filename': 'image_1.svg', 'thumbnail_bg_color': constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0], 'thumbnail_size_in_bytes': 21131, 'destination_node_ids': [], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': self.EXP_ID_3, 'exp_summary_dict': exp_summary_dicts[0], 'completed': False } story.story_contents.nodes = [ story_domain.StoryNode.from_dict(self.node_1) ] self.nodes = story.story_contents.nodes story.story_contents.initial_node_id = 'node_1' story.story_contents.next_node_id = 'node_2' story_services.save_new_story(self.admin_id, story) topic_services.add_canonical_story(self.admin_id, self.TOPIC_ID, self.NEW_STORY_ID) topic_services.publish_story(self.TOPIC_ID, self.NEW_STORY_ID, self.admin_id) csrf_token = self.get_new_csrf_token() story_services.record_completed_node_in_story_context( self.viewer_id, self.STORY_ID, self.NODE_ID_2) story_services.record_completed_node_in_story_context( self.viewer_id, self.STORY_ID, self.NODE_ID_1) with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True): self.post_json('%s/staging/topic/%s/%s' % (feconf.STORY_PROGRESS_URL_PREFIX, self.STORY_URL_FRAGMENT, self.NODE_ID_3), {}, csrf_token=csrf_token) self.assertEqual( len( learner_progress_services.get_all_learnt_topic_ids( self.viewer_id)), 1) self.assertEqual( len( learner_progress_services.get_all_completed_story_ids( self.viewer_id)), 1) def _mock_none_function(_): """Mocks None.""" return None story_fetchers_swap = self.swap(story_fetchers, 'get_story_by_id', _mock_none_function) with story_fetchers_swap: with self.capture_logging( min_level=logging.ERROR) as captured_logs: self.post_json('%s/staging/topic/%s/%s' % (feconf.STORY_PROGRESS_URL_PREFIX, self.STORY_URL_FRAGMENT, self.NODE_ID_3), {}, csrf_token=csrf_token) self.assertEqual(captured_logs, [ 'Could not find a story corresponding to %s ' 'id.' % self.STORY_ID ])
def test_redirect_for_single_node_story(self): self.login(self.CURRICULUM_ADMIN_EMAIL) self.STORY_URL_FRAGMENT = 'story-two' self.NEW_USER_EMAIL = '*****@*****.**' self.NEW_USER_USERNAME = '******' self.save_new_valid_exploration(self.EXP_ID_0, self.admin_id, title='Title 1', end_state_name='End', correctness_feedback_enabled=True) self.publish_exploration(self.admin_id, self.EXP_ID_0) story = story_domain.Story.create_default_story( self.NEW_STORY_ID, 'Title', 'Description', self.NEW_TOPIC_ID, self.STORY_URL_FRAGMENT) story.meta_tag_content = 'story meta content' exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( [self.EXP_ID_0], user=self.admin)) self.node_1 = { 'id': self.NODE_ID_1, 'title': 'Title 1', 'description': 'Description 1', 'thumbnail_filename': 'image_1.svg', 'thumbnail_bg_color': constants.ALLOWED_THUMBNAIL_BG_COLORS['chapter'][0], 'thumbnail_size_in_bytes': 21131, 'destination_node_ids': [], 'acquired_skill_ids': [], 'prerequisite_skill_ids': [], 'outline': '', 'outline_is_finalized': False, 'exploration_id': self.EXP_ID_1, 'exp_summary_dict': exp_summary_dicts[0], 'completed': False } story.story_contents.nodes = [ story_domain.StoryNode.from_dict(self.node_1) ] self.nodes = story.story_contents.nodes story.story_contents.initial_node_id = 'node_1' story.story_contents.next_node_id = 'node_2' story_services.save_new_story(self.admin_id, story) subtopic_1 = topic_domain.Subtopic.create_default_subtopic( 1, 'Subtopic Title 1', 'url-frag-one') subtopic_1.skill_ids = ['skill_id_1'] subtopic_1.url_fragment = 'sub-one-frag' self.save_new_topic(self.NEW_TOPIC_ID, 'user', name='new topic', url_fragment='topic-frag', description='A new topic', canonical_story_ids=[story.id], additional_story_ids=[], uncategorized_skill_ids=[], subtopics=[subtopic_1], next_subtopic_id=2) topic_services.publish_topic(self.NEW_TOPIC_ID, self.admin_id) topic_services.publish_story(self.NEW_TOPIC_ID, self.NEW_STORY_ID, self.admin_id) self.logout() self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME) self.login(self.NEW_USER_EMAIL) with self.swap(constants, 'ENABLE_NEW_STRUCTURE_VIEWER_UPDATES', True): response = self.get_html_response( '%s/staging/topic-frag/%s/%s' % (feconf.STORY_PROGRESS_URL_PREFIX, self.STORY_URL_FRAGMENT, self.NODE_ID_1), expected_status_int=302) self.assertEqual( 'http://localhost/learn/staging/topic-frag/story/story-two', response.headers['location'])
def get_learner_collection_dict_by_id(collection_id, user_id, strict=True, allow_invalid_explorations=False, version=None): """Creates and returns a dictionary representation of a collection given by the provided collection ID. This dictionary contains extra information along with the dict returned by collection_domain.Collection.to_dict() which includes useful data for the collection learner view. The information includes progress in the collection, information about explorations referenced within the collection, and a slightly nicer data structure for frontend work. This raises a ValidationError if the collection retrieved using the given ID references non-existent explorations. """ collection = get_collection_by_id(collection_id, strict=strict, version=version) exp_ids = collection.exploration_ids exp_summary_dicts = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( exp_ids)) exp_summaries_dict_map = { exp_summary_dict['id']: exp_summary_dict for exp_summary_dict in exp_summary_dicts } # TODO(bhenning): Users should not be recommended explorations they have # completed outside the context of a collection (see #1461). next_exploration_ids = None completed_exploration_ids = None if user_id: completed_exploration_ids = _get_valid_completed_exploration_ids( user_id, collection_id, collection) next_exploration_ids = collection.get_next_exploration_ids( completed_exploration_ids) else: # If the user is not logged in or they have not completed any of # the explorations yet within the context of this collection, # recommend the initial explorations. next_exploration_ids = collection.init_exploration_ids completed_exploration_ids = [] collection_dict = collection.to_dict() collection_dict['skills'] = collection.skills collection_dict['playthrough_dict'] = { 'next_exploration_ids': next_exploration_ids, 'completed_exploration_ids': completed_exploration_ids } collection_dict['version'] = collection.version collection_is_public = rights_manager.is_collection_public(collection_id) # Insert an 'exploration' dict into each collection node, where the # dict includes meta information about the exploration (ID and title). for collection_node in collection_dict['nodes']: exploration_id = collection_node['exploration_id'] summary_dict = exp_summaries_dict_map.get(exploration_id) if not allow_invalid_explorations: if not summary_dict: raise utils.ValidationError( 'Expected collection to only reference valid ' 'explorations, but found an exploration with ID: %s (was ' 'the exploration deleted?)' % exploration_id) if collection_is_public and rights_manager.is_exploration_private( exploration_id): raise utils.ValidationError( 'Cannot reference a private exploration within a public ' 'collection, exploration ID: %s' % exploration_id) collection_node['exploration'] = {'exists': bool(summary_dict)} if summary_dict: collection_node['exploration'].update(summary_dict) return collection_dict
def post(self, story_id, node_id): story = story_fetchers.get_story_by_id(story_id) if story is None: logging.error('Could not find a story corresponding to ' '%s id.' % story_id) self.render_json({}) return topic = topic_fetchers.get_topic_by_id(story.corresponding_topic_id) completed_nodes = story_fetchers.get_completed_nodes_in_story( self.user_id, story_id) completed_node_ids = [ completed_node.id for completed_node in completed_nodes ] ordered_nodes = story.story_contents.get_ordered_nodes() (next_exp_ids, next_node_id, completed_node_ids) = (self._record_node_completion( story_id, node_id, completed_node_ids, ordered_nodes)) ready_for_review_test = False exp_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( next_exp_ids)) # If there are no questions for any of the acquired skills that the # learner has completed, do not show review tests. acquired_skills = skill_fetchers.get_multi_skills( story.get_acquired_skill_ids_for_node_ids(completed_node_ids)) acquired_skill_ids = [skill.id for skill in acquired_skills] questions_available = len( question_services.get_questions_by_skill_ids( 1, acquired_skill_ids, False)) > 0 learner_completed_story = len(completed_node_ids) == len(ordered_nodes) learner_at_review_point_in_story = ( len(exp_summaries) != 0 and (len(completed_node_ids) & constants.NUM_EXPLORATIONS_PER_REVIEW_TEST == 0)) if questions_available and (learner_at_review_point_in_story or learner_completed_story): ready_for_review_test = True # If there is no next_node_id, the story is marked as completed else # mark the story as incomplete. if next_node_id is None: learner_progress_services.mark_story_as_completed( self.user_id, story_id) else: learner_progress_services.record_story_started( self.user_id, story.id) completed_story_ids = ( learner_progress_services.get_all_completed_story_ids( self.user_id)) story_ids_in_topic = [] for story in topic.canonical_story_references: story_ids_in_topic.append(story.story_id) is_topic_completed = set(story_ids_in_topic).intersection( set(completed_story_ids)) # If at least one story in the topic is completed, # mark the topic as learnt else mark it as partially learnt. if not is_topic_completed: learner_progress_services.record_topic_started( self.user_id, topic.id) else: learner_progress_services.mark_topic_as_learnt( self.user_id, topic.id) return self.render_json({ 'summaries': exp_summaries, 'ready_for_review_test': ready_for_review_test, 'next_node_id': next_node_id })
def post(self, story_id, node_id): if not constants.ENABLE_NEW_STRUCTURE_VIEWER_UPDATES: raise self.PageNotFoundException try: story_fetchers.get_node_index_by_story_id_and_node_id( story_id, node_id) except Exception as e: raise self.PageNotFoundException(e) story = story_fetchers.get_story_by_id(story_id) completed_nodes = story_fetchers.get_completed_nodes_in_story( self.user_id, story_id) completed_node_ids = [ completed_node.id for completed_node in completed_nodes ] ordered_nodes = [ node for node in story.story_contents.get_ordered_nodes() ] next_exp_ids = [] next_node_id = None if not node_id in completed_node_ids: story_services.record_completed_node_in_story_context( self.user_id, story_id, node_id) completed_nodes = story_fetchers.get_completed_nodes_in_story( self.user_id, story_id) completed_node_ids = [ completed_node.id for completed_node in completed_nodes ] for node in ordered_nodes: if node.id not in completed_node_ids: next_exp_ids = [node.exploration_id] next_node_id = node.id break ready_for_review_test = False exp_summaries = ( summary_services.get_displayable_exp_summary_dicts_matching_ids( next_exp_ids)) # If there are no questions for any of the acquired skills that the # learner has completed, do not show review tests. acquired_skills = skill_fetchers.get_multi_skills( story.get_acquired_skill_ids_for_node_ids(completed_node_ids)) acquired_skill_ids = [skill.id for skill in acquired_skills] questions_available = len( question_services.get_questions_by_skill_ids( 1, acquired_skill_ids, False)) > 0 learner_completed_story = len(completed_nodes) == len(ordered_nodes) learner_at_review_point_in_story = ( len(exp_summaries) != 0 and (len(completed_nodes) & constants.NUM_EXPLORATIONS_PER_REVIEW_TEST == 0)) if questions_available and (learner_at_review_point_in_story or learner_completed_story): ready_for_review_test = True return self.render_json({ 'summaries': exp_summaries, 'ready_for_review_test': ready_for_review_test, 'next_node_id': next_node_id })