def test_migration_job_skips_deleted_collection(self): """Tests that the collection migration job skips deleted collection and does not attempt to migrate. """ collection = collection_domain.Collection.create_default_collection( self.COLLECTION_ID, 'A title', 'A Category', 'An Objective') collection_services.save_new_collection(self.albert_id, collection) # Note: This creates a summary based on the upgraded model (which is # fine). A summary is needed to delete the collection. collection_services.create_collection_summary( self.COLLECTION_ID, None) # Delete the exploration before migration occurs. collection_services.delete_collection( self.albert_id, self.COLLECTION_ID) # Ensure the exploration is deleted. with self.assertRaisesRegexp(Exception, 'Entity .* not found'): collection_services.get_collection_by_id(self.COLLECTION_ID) # Start migration job on sample collection. job_id = ( collection_jobs_one_off.CollectionMigrationJob.create_new()) collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id) # This running without errors indicates the deleted collection is # being ignored. self.process_and_flush_pending_tasks() # Ensure the exploration is still deleted. with self.assertRaisesRegexp(Exception, 'Entity .* not found'): collection_services.get_collection_by_id(self.COLLECTION_ID)
def _get_exploration_player_data( exploration_id, version, collection_id, can_edit): try: exploration = exp_services.get_exploration_by_id( exploration_id, version=version) except Exception: raise Exception collection_title = None if collection_id: try: collection = collection_services.get_collection_by_id( collection_id) collection_title = collection.title except Exception: raise Exception version = exploration.version # TODO(sll): Cache these computations. gadget_types = exploration.get_gadget_types() interaction_ids = exploration.get_interaction_ids() dependency_ids = ( interaction_registry.Registry.get_deduplicated_dependency_ids( interaction_ids)) dependencies_html, additional_angular_modules = ( dependency_registry.Registry.get_deps_html_and_angular_modules( dependency_ids)) gadget_templates = ( gadget_registry.Registry.get_gadget_html(gadget_types)) interaction_templates = ( rte_component_registry.Registry.get_html_for_all_components() + interaction_registry.Registry.get_interaction_html( interaction_ids)) return { 'GADGET_SPECS': gadget_registry.Registry.get_all_specs(), 'INTERACTION_SPECS': interaction_registry.Registry.get_all_specs(), 'DEFAULT_TWITTER_SHARE_MESSAGE_PLAYER': ( DEFAULT_TWITTER_SHARE_MESSAGE_PLAYER.value), 'additional_angular_modules': additional_angular_modules, 'can_edit': can_edit, 'dependencies_html': jinja2.utils.Markup( dependencies_html), 'exploration_title': exploration.title, 'exploration_version': version, 'collection_id': collection_id, 'collection_title': collection_title, 'gadget_templates': jinja2.utils.Markup(gadget_templates), 'interaction_templates': jinja2.utils.Markup( interaction_templates), 'is_private': rights_manager.is_exploration_private( exploration_id), # Note that this overwrites the value in base.py. 'meta_name': exploration.title, # Note that this overwrites the value in base.py. 'meta_description': utils.capitalize_string(exploration.objective), 'nav_mode': feconf.NAV_MODE_EXPLORE, }
def get(self, collection_id): """Handles GET requests.""" collection = collection_services.get_collection_by_id( collection_id, strict=False) if (collection is None or not rights_manager.Actor(self.user_id).can_view( rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id)): self.redirect('/') return can_edit = ( bool(self.user_id) and self.username not in config_domain.BANNED_USERNAMES.value and rights_manager.Actor(self.user_id).can_edit( rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id)) self.values.update({ 'is_public': rights_manager.is_collection_public(collection_id), 'can_edit': can_edit, 'collection_id': collection.id, 'title': collection.title }) self.render_template('collection_editor/collection_editor.html')
def test_migration_job_does_not_convert_up_to_date_collection(self): """Tests that the collection migration job does not convert an collection that is already the latest collection content schema version. """ # Create a new, collection that should not be affected by the # job. collection = collection_domain.Collection.create_default_collection( self.COLLECTION_ID, 'A title', 'A Category', 'An Objective') collection_services.save_new_collection(self.albert_id, collection) self.assertEqual( collection.schema_version, feconf.CURRENT_COLLECTION_SCHEMA_VERSION) yaml_before_migration = collection.to_yaml() # Start migration job. job_id = ( collection_jobs_one_off.CollectionMigrationJob.create_new()) collection_jobs_one_off.CollectionMigrationJob.enqueue(job_id) self.process_and_flush_pending_tasks() # Verify the collection is exactly the same after migration. updated_collection = ( collection_services.get_collection_by_id(self.COLLECTION_ID)) self.assertEqual( updated_collection.schema_version, feconf.CURRENT_COLLECTION_SCHEMA_VERSION) after_converted_yaml = updated_collection.to_yaml() self.assertEqual(after_converted_yaml, yaml_before_migration)
def map(item): if item.deleted: yield (CollectionMigrationJob._DELETED_KEY, 'Encountered deleted collection.') return # Note: the read will bring the collection up to the newest version. collection = collection_services.get_collection_by_id(item.id) try: collection.validate(strict=False) except Exception as e: logging.error( 'Collection %s failed validation: %s' % (item.id, e)) yield (CollectionMigrationJob._ERROR_KEY, 'Collection %s failed validation: %s' % (item.id, e)) return # Write the new collection into the datastore if it's different from # the old version. if item.schema_version <= feconf.CURRENT_COLLECTION_SCHEMA_VERSION: commit_cmds = [{ 'cmd': collection_domain.CMD_MIGRATE_SCHEMA_TO_LATEST_VERSION, 'from_version': item.schema_version, 'to_version': str( feconf.CURRENT_COLLECTION_SCHEMA_VERSION) }] collection_services.update_collection( feconf.MIGRATION_BOT_USERNAME, item.id, commit_cmds, 'Update collection schema version to %d.' % ( feconf.CURRENT_COLLECTION_SCHEMA_VERSION)) yield (CollectionMigrationJob._MIGRATED_KEY, 'Collection successfully migrated.')
def get(self, collection_id): """Populates the data on the individual collection page.""" try: collection = collection_services.get_collection_by_id( collection_id) except Exception as e: raise self.PageNotFoundException(e) exp_ids = collection.exploration_ids exp_summaries = ( exp_services.get_exploration_summaries_matching_ids(exp_ids)) exp_titles_dict = {} for (ind, exp_id) in enumerate(exp_ids): exp_summary = exp_summaries[ind] exp_titles_dict[exp_id] = exp_summary.title if exp_summary else '' # TODO(bhenning): Users should not be recommended explorations they # have completed outside the context of a collection. next_exploration_ids = None completed_exploration_ids = None if self.user_id: completed_exploration_ids = ( collection_services.get_completed_exploration_ids( self.user_id, collection_id)) 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['next_exploration_ids'] = next_exploration_ids collection_dict['completed_exploration_ids'] = ( completed_exploration_ids) # 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']: collection_node['exploration'] = { 'id': collection_node['exploration_id'], 'title': exp_titles_dict[collection_node['exploration_id']] } self.values.update({ 'can_edit': ( self.user_id and rights_manager.Actor(self.user_id).can_edit( rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id)), 'collection': collection_dict, 'info_card_image_url': utils.get_info_card_url_for_category( collection.category), 'is_logged_in': bool(self.user_id), 'session_id': utils.generate_new_session_id(), }) self.render_json(self.values)
def get(self, collection_id): """Handles GET requests.""" try: collection = collection_services.get_collection_by_id( collection_id) except Exception as e: raise self.PageNotFoundException(e) whitelisted_usernames = ( config_domain.WHITELISTED_COLLECTION_EDITOR_USERNAMES.value) self.values.update({ 'nav_mode': feconf.NAV_MODE_COLLECTION, 'can_edit': ( bool(self.username) and self.username in whitelisted_usernames and self.username not in config_domain.BANNED_USERNAMES.value and rights_manager.Actor(self.user_id).can_edit( feconf.ACTIVITY_TYPE_COLLECTION, collection_id) ), 'is_logged_in': bool(self.user_id), 'collection_id': collection_id, 'collection_title': collection.title, 'collection_skills': collection.skills, 'is_private': rights_manager.is_collection_private(collection_id), 'meta_name': collection.title, 'meta_description': utils.capitalize_string(collection.objective) }) self.render_template('collection_player/collection_player.html')
def put(self, collection_id): """Updates properties of the given collection.""" collection = collection_services.get_collection_by_id(collection_id) version = self.payload.get('version') self._require_valid_version(version, collection.version) commit_message = self.payload.get('commit_message') change_list = self.payload.get('change_list') try: collection_services.update_collection( self.user_id, collection_id, change_list, commit_message) except utils.ValidationError as e: raise self.InvalidInputException(e) collection_dict = ( summary_services.get_learner_collection_dict_by_id( collection_id, self.user_id, allow_invalid_explorations=True)) # Send the updated collection back to the frontend. self.values.update({ 'collection': collection_dict }) self.render_json(self.values)
def put(self, collection_id): """Updates the editing rights for the given collection.""" collection = collection_services.get_collection_by_id(collection_id) version = self.payload.get('version') _require_valid_version(version, collection.version) # TODO(bhenning): Implement other rights changes here. is_public = self.payload.get('is_public') if is_public is not None: if is_public: try: collection.validate(strict=True) collection_services.validate_exps_in_collection_are_public( collection) except utils.ValidationError as e: raise self.InvalidInputException(e) collection_services.publish_collection_and_update_user_profiles( self.user_id, collection_id) collection_services.index_collections_given_ids([ collection_id]) elif not self.is_admin: raise self.InvalidInputException( 'Cannot unpublish a collection.') self.render_json({ 'rights': rights_manager.get_collection_rights( collection_id).to_dict() })
def setUp(self): super(CollectionDomainUnitTests, self).setUp() self.save_new_valid_collection( self.COLLECTION_ID, '*****@*****.**', title='Title', category='Category', objective='Objective', exploration_id=self.EXPLORATION_ID) self.collection = collection_services.get_collection_by_id( self.COLLECTION_ID)
def setUp(self): super(CollectionDomainUnitTests, self).setUp() self.save_new_valid_collection(self.COLLECTION_ID, '*****@*****.**', title='Title', category='Category', objective='Objective', exploration_id=self.EXPLORATION_ID) self.collection = collection_services.get_collection_by_id( self.COLLECTION_ID)
def test_migration_job_skips_deleted_collection(self): """Tests that the collection migration job skips deleted collection and does not attempt to migrate. """ collection = collection_domain.Collection.create_default_collection( self.COLLECTION_ID, title='A title', category='A Category', objective='An Objective') collection_services.save_new_collection(self.albert_id, collection) # Note: This creates a summary based on the upgraded model (which is # fine). A summary is needed to delete the collection. collection_services.regenerate_collection_and_contributors_summaries( self.COLLECTION_ID) # Delete the exploration before migration occurs. collection_services.delete_collection(self.albert_id, self.COLLECTION_ID) # Ensure the exploration is deleted. with self.assertRaisesRegexp(Exception, 'Entity .* not found'): collection_services.get_collection_by_id(self.COLLECTION_ID) # Start migration job on sample collection. job_id = ( collection_jobs_one_off.CollectionMigrationOneOffJob.create_new()) collection_jobs_one_off.CollectionMigrationOneOffJob.enqueue(job_id) # This running without errors indicates the deleted collection is # being ignored. self.process_and_flush_pending_mapreduce_tasks() # Ensure the exploration is still deleted. with self.assertRaisesRegexp(Exception, 'Entity .* not found'): collection_services.get_collection_by_id(self.COLLECTION_ID) output = (collection_jobs_one_off.CollectionMigrationOneOffJob. get_output(job_id)) expected = [[ u'collection_deleted', [u'Encountered 1 deleted collections.'] ]] self.assertEqual(expected, [ast.literal_eval(x) for x in output])
def setUp(self): super(CollectionDomainUnitTests, self).setUp() self.save_new_valid_collection( self.COLLECTION_ID, "*****@*****.**", title="Title", category="Category", objective="Objective", exploration_id=self.EXPLORATION_ID, ) self.collection = collection_services.get_collection_by_id(self.COLLECTION_ID)
def test_can_not_unpublish_collection_with_invalid_payload_version(self): self.set_collection_editors([self.OWNER_USERNAME]) # Login as owner and publish a collection with a public exploration. self.login(self.OWNER_EMAIL) collection_id = collection_services.get_new_collection_id() exploration_id = exp_services.get_new_exploration_id() self.save_new_valid_exploration(exploration_id, self.owner_id) self.save_new_valid_collection(collection_id, self.owner_id, exploration_id=exploration_id) rights_manager.publish_exploration(self.owner, exploration_id) collection = collection_services.get_collection_by_id(collection_id) response = self.get_html_response( '%s/%s' % (feconf.COLLECTION_URL_PREFIX, self.COLLECTION_ID)) csrf_token = self.get_csrf_token_from_response(response) response_dict = self.put_json('/collection_editor_handler/publish/%s' % collection_id, {'version': collection.version}, csrf_token=csrf_token) self.assertFalse(response_dict['is_private']) self.logout() # Login as admin and try to unpublish the collection. self.login(self.ADMIN_EMAIL) response = self.get_html_response( '%s/%s' % (feconf.COLLECTION_URL_PREFIX, self.COLLECTION_ID)) csrf_token = self.get_csrf_token_from_response(response) # Raises error as version is None. response_dict = self.put_json( '/collection_editor_handler/unpublish/%s' % collection_id, {'version': None}, csrf_token=csrf_token, expected_status_int=400) self.assertEqual(response_dict['error'], 'Invalid POST request: a version must be specified.') # Raises error as version from payload does not match the collection # version. response_dict = self.put_json( '/collection_editor_handler/unpublish/%s' % collection_id, {'version': 2}, csrf_token=csrf_token, expected_status_int=400) self.assertEqual( response_dict['error'], 'Trying to update version 1 of collection from version 2, ' 'which is too old. Please reload the page and try again.') self.logout()
def _get_exploration_player_data( exploration_id, version, collection_id, can_edit): """Returns a dict of exploration player data. Args: exploration_id: str. The ID of the exploration. version: int or None. The version of the exploration. collection_id: str. ID of the collection. can_edit: bool. Whether the given user can edit this activity. Returns: dict. A dict of exploration player data. The keys and values of the dict are as follows: - 'can_edit': bool. Whether the given user can edit this activity. - 'exploration_title': str. Title of exploration. - 'exploration_version': int. The version of the exploration. - 'collection_id': str. ID of the collection. - 'collection_title': str. Title of collection. required by the given exploration ID. - 'is_private': bool. Whether the exploration is private or not. - 'meta_name': str. Title of exploration. - 'meta_description': str. Objective of exploration. """ try: exploration = exp_fetchers.get_exploration_by_id( exploration_id, version=version) except Exception: raise Exception collection_title = None if collection_id: try: collection = collection_services.get_collection_by_id( collection_id) collection_title = collection.title except Exception: raise Exception version = exploration.version return { 'can_edit': can_edit, 'exploration_title': exploration.title, 'exploration_version': version, 'collection_id': collection_id, 'collection_title': collection_title, 'is_private': rights_manager.is_exploration_private( exploration_id), # Note that this overwrites the value in base.py. 'meta_name': exploration.title, # Note that this overwrites the value in base.py. 'meta_description': utils.capitalize_string(exploration.objective), }
def test_collection_editor(self, collection_id, **kwargs): """Gets the user and collection id if the user can edit it. Args: self: the handler instance collection_id: the collection id **kwargs: any other arguments passed to the handler Returns: The relevant handler, if the user is authorized to edit this collection. Raises: self.PageNotFoundException: if no such collection exists. self.UnauthorizedUserException: if the user exists but does not have the right credentials. """ if not self.user_id: self.redirect(current_user_services.create_login_url( self.request.uri)) return if (self.username in config_domain.BANNED_USERNAMES.value or self.username not in config_domain.WHITELISTED_COLLECTION_EDITOR_USERNAMES.value): raise self.UnauthorizedUserException( 'You do not have the credentials to access this page.') try: collection_services.get_collection_by_id(collection_id) except: raise self.PageNotFoundException if not rights_manager.Actor(self.user_id).can_edit( feconf.ACTIVITY_TYPE_COLLECTION, collection_id): raise self.UnauthorizedUserException( 'You do not have the credentials to edit this collection.', self.user_id) return handler(self, collection_id, **kwargs)
def test_collection_editor(self, collection_id, **kwargs): """Gets the user and collection id if the user can edit it. Args: self: the handler instance collection_id: the collection id **kwargs: any other arguments passed to the handler Returns: The relevant handler, if the user is authorized to edit this collection. Raises: self.PageNotFoundException: if no such collection exists. self.UnauthorizedUserException: if the user exists but does not have the right credentials. """ if not self.user_id: self.redirect( current_user_services.create_login_url(self.request.uri)) return if (self.username in config_domain.BANNED_USERNAMES.value or self.username not in config_domain.WHITELISTED_COLLECTION_EDITOR_USERNAMES.value): raise self.UnauthorizedUserException( 'You do not have the credentials to access this page.') try: collection_services.get_collection_by_id(collection_id) except: raise self.PageNotFoundException if not rights_manager.Actor(self.user_id).can_edit( rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id): raise self.UnauthorizedUserException( 'You do not have the credentials to edit this collection.', self.user_id) return handler(self, collection_id, **kwargs)
def test_add_question_id_to_skill(self): """Test to verify add_skill.""" collection_id = 'col1' exp_id = '0_exploration_id' owner_id = self.get_user_id_from_email(self.OWNER_EMAIL) # Create a new collection and exploration. self.save_new_valid_collection(collection_id, owner_id, exploration_id=exp_id) # Add a skill. collection_services.update_collection( owner_id, collection_id, [{ 'cmd': collection_domain.CMD_ADD_COLLECTION_SKILL, 'name': 'skill0' }], 'Add a new skill') state = exp_domain.State.create_default_state('ABC') question_data = state.to_dict() question_dict = { 'question_id': 'col1.random', 'title': 'abc', 'question_data': question_data, 'question_data_schema_version': 1, 'collection_id': 'col1', 'language_code': 'en' } collection = collection_services.get_collection_by_id(collection_id) skill_id = collection.get_skill_id_from_skill_name('skill0') question = question_domain.Question.from_dict(question_dict) question_services.add_question_id_to_skill(question.question_id, collection_id, skill_id, owner_id) collection = collection_services.get_collection_by_id(collection_id) self.assertIn(question.question_id, collection.skills[skill_id].question_ids)
def test_get_question_batch(self): coll_id_0 = '0_collection_id' exp_id_0 = '0_exploration_id' self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL) # Create a new collection and exploration. self.save_new_valid_collection(coll_id_0, self.owner_id, exploration_id=exp_id_0) # Add a skill. collection_services.update_collection( self.owner_id, coll_id_0, [{ 'cmd': collection_domain.CMD_ADD_COLLECTION_SKILL, 'name': 'skill0' }], 'Add a new skill') collection = collection_services.get_collection_by_id(coll_id_0) skill_id = collection.get_skill_id_from_skill_name('skill0') collection_node = collection.get_node(exp_id_0) collection_node.update_acquired_skill_ids([skill_id]) # Update the acquired skill IDs for the exploration. collection_services.update_collection( self.owner_id, coll_id_0, [{ 'cmd': collection_domain.CMD_EDIT_COLLECTION_NODE_PROPERTY, 'property_name': (collection_domain.COLLECTION_NODE_PROPERTY_ACQUIRED_SKILL_IDS ), # pylint: disable=line-too-long 'exploration_id': exp_id_0, 'new_value': [skill_id] }], 'Update skill') question = question_domain.Question( 'dummy', 'A Question', exp_domain.State.create_default_state('ABC').to_dict(), 1, coll_id_0, 'en') question_id = question_services.add_question(self.owner_id, question) question = question_services.get_question_by_id(question_id) question_services.add_question_id_to_skill(question.question_id, coll_id_0, skill_id, self.owner_id) collection_services.record_played_exploration_in_collection_context( self.owner_id, coll_id_0, exp_id_0) question_batch = question_services.get_questions_batch( coll_id_0, [skill_id], self.owner_id, 1) self.assertEqual(question_batch[0].title, question.title)
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, 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, collection_id): """Gets the editing rights for the given collection.""" collection = collection_services.get_collection_by_id(collection_id) self.values.update({ 'can_edit': True, 'can_unpublish': rights_manager.Actor( self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'owner_names': rights_manager.get_collection_owner_names( collection_id) }) self.render_json(self.values)
def test_can_create_collections(self): self.set_admins([self.OWNER_USERNAME]) self.login(self.OWNER_EMAIL) csrf_token = self.get_new_csrf_token() collection_id = self.post_json( feconf.NEW_COLLECTION_URL, {}, csrf_token=csrf_token)[ creator_dashboard.COLLECTION_ID_KEY] collection = collection_services.get_collection_by_id(collection_id) self.assertEqual(collection.id, collection_id) self.assertEqual(collection.title, feconf.DEFAULT_COLLECTION_TITLE) self.assertEqual( collection.objective, feconf.DEFAULT_COLLECTION_CATEGORY) self.assertEqual( collection.category, feconf.DEFAULT_COLLECTION_OBJECTIVE) self.assertEqual( collection.language_code, constants.DEFAULT_LANGUAGE_CODE) self.logout()
def get(self, collection_id): """Handles GET requests.""" collection = collection_services.get_collection_by_id( collection_id, strict=False) self.values.update({ 'collection_id': collection.id, 'nav_mode': feconf.NAV_MODE_CREATE, 'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY), 'SHOW_COLLECTION_NAVIGATION_TAB_STATS': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS), 'TAG_REGEX': feconf.TAG_REGEX, }) self.render_template('pages/collection_editor/collection_editor.html')
def put(self, collection_id): """Updates the editing rights for the given collection.""" collection = collection_services.get_collection_by_id(collection_id) version = self.payload.get('version') _require_valid_version(version, collection.version) # TODO(bhenning): Implement other rights changes here. is_public = self.payload.get('is_public') if is_public is not None: if is_public: try: collection.validate(strict=True) collection_services.validate_exps_in_collection_are_public( collection) except utils.ValidationError as e: raise self.InvalidInputException(e) collection_services.publish_collection_and_update_user_profiles( self.user_id, collection_id) collection_services.index_collections_given_ids( [collection_id]) elif rights_manager.Actor(self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id): rights_manager.unpublish_collection(self.user_id, collection_id) collection_services.delete_documents_from_search_index( [collection_id]) else: raise self.InvalidInputException( 'Cannot unpublish a collection.') self.values.update({ 'can_edit': True, 'can_unpublish': rights_manager.Actor(self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'owner_names': rights_manager.get_collection_owner_names(collection_id) })
def get(self, collection_id): """Handles GET requests.""" collection = collection_services.get_collection_by_id( collection_id, strict=False) self.values.update({ 'collection_id': collection.id, 'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY), 'SHOW_COLLECTION_NAVIGATION_TAB_STATS': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS), 'TAG_REGEX': feconf.TAG_REGEX, 'title': collection.title, 'INTERACTION_SPECS': interaction_registry.Registry.get_all_specs(), }) self.render_template('dist/collection_editor.html')
def get(self, collection_id): """Gets the editing rights for the given collection.""" collection = collection_services.get_collection_by_id(collection_id) self.values.update({ 'can_edit': True, 'can_unpublish': rights_manager.Actor(self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'owner_names': rights_manager.get_collection_owner_names(collection_id) }) self.render_json(self.values)
def get(self, collection_id): """Handles GET requests.""" collection = collection_services.get_collection_by_id(collection_id, strict=False) if (collection is None or not rights_manager.Actor(self.user_id).can_view( feconf.ACTIVITY_TYPE_COLLECTION, collection_id)): self.redirect('/') return can_edit = (bool(self.user_id) and self.username not in config_domain.BANNED_USERNAMES.value and rights_manager.Actor(self.user_id).can_edit( feconf.ACTIVITY_TYPE_COLLECTION, collection_id)) self.values.update({ 'can_edit': can_edit, 'can_unpublish': rights_manager.Actor(self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'nav_mode': feconf.NAV_MODE_CREATE, 'title': collection.title, 'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': (feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY), 'SHOW_COLLECTION_NAVIGATION_TAB_FEEDBACK': (feconf.SHOW_COLLECTION_NAVIGATION_TAB_FEEDBACK), 'SHOW_COLLECTION_NAVIGATION_TAB_STATS': (feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS), 'TAG_REGEX': feconf.TAG_REGEX, }) self.render_template('collection_editor/collection_editor.html')
def put(self, collection_id): """Updates properties of the given collection.""" collection = collection_services.get_collection_by_id(collection_id) version = self.payload.get('version') _require_valid_version(version, collection.version) commit_message = self.payload.get('commit_message') change_list = self.payload.get('change_list') collection_services.update_collection(self.user_id, collection_id, change_list, commit_message) collection_dict = (summary_services.get_learner_collection_dict_by_id( collection_id, self.user, allow_invalid_explorations=True)) # Send the updated collection back to the frontend. self.values.update({'collection': collection_dict}) self.render_json(self.values)
def put(self, collection_id): """Updates the editing rights for the given collection.""" collection = collection_services.get_collection_by_id(collection_id) version = self.payload.get('version') _require_valid_version(version, collection.version) # TODO(bhenning): Implement other rights changes here. is_public = self.payload.get('is_public') if is_public is not None: if is_public: try: collection.validate(strict=True) collection_services.validate_exps_in_collection_are_public( collection) except utils.ValidationError as e: raise self.InvalidInputException(e) collection_services.publish_collection_and_update_user_profiles( self.user_id, collection_id) collection_services.index_collections_given_ids([ collection_id]) elif rights_manager.Actor(self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id): rights_manager.unpublish_collection(self.user_id, collection_id) collection_services.delete_documents_from_search_index([ collection_id]) else: raise self.InvalidInputException( 'Cannot unpublish a collection.') self.values.update({ 'can_edit': True, 'can_unpublish': rights_manager.Actor( self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'owner_names': rights_manager.get_collection_owner_names( collection_id) })
def setUp(self): super(QuestionsHandlersTest, self).setUp() self.collection_id = 'coll_0' self.exp_id = 'exp_1' self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL) self.random_email = '*****@*****.**' self.signup(self.random_email, 'abc') self.signup(self.NEW_USER_EMAIL, self.NEW_USER_USERNAME) self.new_user_id = self.get_user_id_from_email(self.NEW_USER_EMAIL) self.set_moderators([self.NEW_USER_USERNAME]) # Create a new collection and exploration. self.save_new_valid_collection( self.collection_id, self.new_user_id, exploration_id=self.exp_id) # Add a skill. collection_services.update_collection( self.new_user_id, self.collection_id, [{ 'cmd': collection_domain.CMD_ADD_COLLECTION_SKILL, 'name': 'test' }], 'Add a new skill') collection = collection_services.get_collection_by_id( self.collection_id) self.skill_id = collection.get_skill_id_from_skill_name('test') collection_node = collection.get_node(self.exp_id) collection_node.update_acquired_skill_ids([self.skill_id]) # Update the acquired skill IDs for the exploration. collection_services.update_collection( self.new_user_id, self.collection_id, [{ 'cmd': collection_domain.CMD_EDIT_COLLECTION_NODE_PROPERTY, 'property_name': ( collection_domain.COLLECTION_NODE_PROPERTY_ACQUIRED_SKILL_IDS), # pylint: disable=line-too-long 'exploration_id': self.exp_id, 'new_value': [self.skill_id] }], 'Update skill') self.question = question_domain.Question( 'dummy', 'A Question', exp_domain.State.create_default_state('ABC').to_dict(), 1, self.collection_id, 'en')
def get(self, collection_id): """Gets the editing rights for the given collection.""" collection = collection_services.get_collection_by_id(collection_id) collection_rights = rights_manager.get_collection_rights(collection_id, strict=False) self.values.update({ 'can_edit': True, 'can_unpublish': rights_manager.check_can_unpublish_activity( self.user, collection_rights), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'owner_names': rights_manager.get_collection_owner_names(collection_id) }) self.render_json(self.values)
def put(self, collection_id): """Updates properties of the given collection.""" collection = collection_services.get_collection_by_id(collection_id) version = self.payload.get('version') self._require_valid_version(version, collection.version) commit_message = self.payload.get('commit_message') change_list = self.payload.get('change_list') try: collection_services.update_collection(self.user_id, collection_id, change_list, commit_message) except utils.ValidationError as e: raise self.InvalidInputException(e) # Retrieve the updated collection. collection_dict = (summary_services.get_learner_collection_dict_by_id( collection_id, self.user_id, allow_invalid_explorations=True)) # Send the updated collection back to the frontend. self.values.update(collection_dict) self.render_json(self.values)
def get_question_summaries_for_collection(collection_id): """Gets a list of question summaries for a collection. Args: collection_id: str. ID of the collection. Returns: list(QuestionSummary). A list of Question Summary objects. """ collection = collection_services.get_collection_by_id(collection_id) questions_to_skill_names = collections.defaultdict(list) for skill in collection.skills.values(): for question_id in skill.question_ids: questions_to_skill_names[question_id].append(skill.name) questions = get_questions_by_ids(questions_to_skill_names.keys()) question_summaries = [] for question in questions: question_summaries.append( question_domain.QuestionSummary( question.question_id, question.title, (questions_to_skill_names[question.question_id]))) return question_summaries
def get(self, collection_id): """Handles GET requests.""" collection = collection_services.get_collection_by_id( collection_id, strict=False) self.values.update({ 'can_edit': True, 'can_unpublish': rights_manager.Actor( self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'nav_mode': feconf.NAV_MODE_CREATE, 'title': collection.title, 'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY), 'SHOW_COLLECTION_NAVIGATION_TAB_STATS': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS), 'TAG_REGEX': feconf.TAG_REGEX, }) self.render_template('pages/collection_editor/collection_editor.html')
def put(self, collection_id): """Unpublishes the given collection.""" collection = collection_services.get_collection_by_id(collection_id) version = self.payload.get('version') _require_valid_version(version, collection.version) rights_manager.unpublish_collection(self.user, collection_id) search_services.delete_collections_from_search_index([ collection_id]) collection_rights = rights_manager.get_collection_rights( collection_id, strict=False) self.values.update({ 'can_edit': True, 'can_unpublish': rights_manager.check_can_unpublish_activity( self.user, collection_rights), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'owner_names': rights_manager.get_collection_owner_names( collection_id) }) self.render_json(self.values)
def get(self, collection_id): """Handles GET requests.""" collection = collection_services.get_collection_by_id( collection_id, strict=False) if (collection is None or not rights_manager.Actor(self.user_id).can_view( feconf.ACTIVITY_TYPE_COLLECTION, collection_id)): self.redirect('/') return can_edit = ( bool(self.user_id) and self.username not in config_domain.BANNED_USERNAMES.value and rights_manager.Actor(self.user_id).can_edit( feconf.ACTIVITY_TYPE_COLLECTION, collection_id)) self.values.update({ 'can_edit': can_edit, 'can_unpublish': rights_manager.Actor( self.user_id).can_unpublish( feconf.ACTIVITY_TYPE_COLLECTION, collection_id), 'collection_id': collection.id, 'is_private': rights_manager.is_collection_private(collection_id), 'nav_mode': feconf.NAV_MODE_CREATE, 'title': collection.title, 'SHOW_COLLECTION_NAVIGATION_TAB_HISTORY': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_HISTORY), 'SHOW_COLLECTION_NAVIGATION_TAB_FEEDBACK': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_FEEDBACK), 'SHOW_COLLECTION_NAVIGATION_TAB_STATS': ( feconf.SHOW_COLLECTION_NAVIGATION_TAB_STATS), 'TAG_REGEX': feconf.TAG_REGEX, }) self.render_template('collection_editor/collection_editor.html')
def get(self, collection_id): """Handles GET requests.""" try: collection = collection_services.get_collection_by_id( collection_id) except Exception as e: raise self.PageNotFoundException(e) self.values.update({ 'can_edit': ( bool(self.username) and self.username not in config_domain.BANNED_USERNAMES.value and rights_manager.Actor(self.user_id).can_edit( rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id) ), 'is_logged_in': bool(self.user_id), 'collection_id': collection_id, 'collection_title': collection.title, 'is_private': rights_manager.is_collection_private(collection_id), 'meta_name': collection.title, 'meta_description': utils.capitalize_string(collection.objective) }) self.render_template('collection_player/collection_player.html')
def test_migration_job_does_not_convert_up_to_date_collection(self): """Tests that the collection migration job does not convert an collection that is already the latest collection content schema version. """ # Create a new collection that should not be affected by the # job. collection = collection_domain.Collection.create_default_collection( self.COLLECTION_ID, title='A title', category='A Category', objective='An Objective') collection_services.save_new_collection(self.albert_id, collection) self.assertEqual(collection.schema_version, feconf.CURRENT_COLLECTION_SCHEMA_VERSION) yaml_before_migration = collection.to_yaml() # Start migration job. job_id = ( collection_jobs_one_off.CollectionMigrationOneOffJob.create_new()) collection_jobs_one_off.CollectionMigrationOneOffJob.enqueue(job_id) self.process_and_flush_pending_tasks() # Verify the collection is exactly the same after migration. updated_collection = (collection_services.get_collection_by_id( self.COLLECTION_ID)) self.assertEqual(updated_collection.schema_version, feconf.CURRENT_COLLECTION_SCHEMA_VERSION) after_converted_yaml = updated_collection.to_yaml() self.assertEqual(after_converted_yaml, yaml_before_migration) output = (collection_jobs_one_off.CollectionMigrationOneOffJob. get_output(job_id)) expected = [[ u'collection_migrated', [u'1 collections successfully migrated.'] ]] self.assertEqual(expected, [ast.literal_eval(x) for x in output])
def _does_exploration_exist(exploration_id, version, collection_id): """Returns if an exploration exists. Args: exploration_id: str. The ID of the exploration. version: int or None. The version of the exploration. collection_id: str. ID of the collection. Returns: bool. True if the exploration exists False otherwise. """ exploration = exp_fetchers.get_exploration_by_id( exploration_id, strict=False, version=version) if exploration is None: return False if collection_id: collection = collection_services.get_collection_by_id( collection_id, strict=False) if collection is None: return False return True
def _get_exploration_player_data( exploration_id, version, collection_id, can_edit): """Returns a dict of exploration player data. Args: exploration_id: str. The ID of the exploration. version: int or None. The version of the exploration. collection_id: str. ID of the collection. can_edit: bool. Whether the given user can edit this activity. Returns: dict. A dict of exploration player data. The keys and values of the dict are as follows: - 'INTERACTION_SPECS': dict. A dict containing the full specs of each interaction. Contains interaction ID and a list of instances of all interactions. - 'additional_angular_modules': list. A de-duplicated list of strings, each representing an additional angular module that should be loaded. - 'can_edit': bool. Whether the given user can edit this activity. - 'dependencies_html': str. The additional HTML to insert on the page. - 'exploration_title': str. Title of exploration. - 'exploration_version': int. The version of the exploration. - 'collection_id': str. ID of the collection. - 'collection_title': str. Title of collection. - 'interaction_templates': str. The HTML bodies of the interactions required by the given exploration ID. - 'is_private': bool. Whether the exploration is private or not. - 'meta_name': str. Title of exploration. - 'meta_description': str. Objective of exploration. """ try: exploration = exp_fetchers.get_exploration_by_id( exploration_id, version=version) except Exception: raise Exception collection_title = None if collection_id: try: collection = collection_services.get_collection_by_id( collection_id) collection_title = collection.title except Exception: raise Exception version = exploration.version # TODO(sll): Cache these computations. interaction_ids = exploration.get_interaction_ids() for interaction_id in feconf.ALLOWED_QUESTION_INTERACTION_IDS: if interaction_id not in interaction_ids: interaction_ids.append(interaction_id) dependency_ids = ( interaction_registry.Registry.get_deduplicated_dependency_ids( interaction_ids)) dependencies_html, additional_angular_modules = ( dependency_registry.Registry.get_deps_html_and_angular_modules( dependency_ids)) interaction_templates = ( interaction_registry.Registry.get_interaction_html( interaction_ids)) return { 'INTERACTION_SPECS': interaction_registry.Registry.get_all_specs(), 'additional_angular_modules': additional_angular_modules, 'can_edit': can_edit, 'dependencies_html': jinja2.utils.Markup( dependencies_html), 'exploration_title': exploration.title, 'exploration_version': version, 'collection_id': collection_id, 'collection_title': collection_title, 'interaction_templates': jinja2.utils.Markup( interaction_templates), 'is_private': rights_manager.is_exploration_private( exploration_id), # Note that this overwrites the value in base.py. 'meta_name': exploration.title, # Note that this overwrites the value in base.py. 'meta_description': utils.capitalize_string(exploration.objective), }
def _get_exploration_player_data(exploration_id, version, collection_id, can_edit): try: exploration = exp_services.get_exploration_by_id(exploration_id, version=version) except Exception: raise Exception collection_title = None if collection_id: try: collection = collection_services.get_collection_by_id( collection_id) collection_title = collection.title except Exception: raise Exception version = exploration.version # TODO(sll): Cache these computations. gadget_types = exploration.get_gadget_types() interaction_ids = exploration.get_interaction_ids() dependency_ids = (interaction_registry.Registry. get_deduplicated_dependency_ids(interaction_ids)) dependencies_html, additional_angular_modules = ( dependency_registry.Registry.get_deps_html_and_angular_modules( dependency_ids)) gadget_templates = (gadget_registry.Registry.get_gadget_html(gadget_types)) interaction_templates = ( rte_component_registry.Registry.get_html_for_all_components() + interaction_registry.Registry.get_interaction_html(interaction_ids)) return { 'GADGET_SPECS': gadget_registry.Registry.get_all_specs(), 'INTERACTION_SPECS': interaction_registry.Registry.get_all_specs(), 'DEFAULT_TWITTER_SHARE_MESSAGE_PLAYER': (DEFAULT_TWITTER_SHARE_MESSAGE_PLAYER.value), 'additional_angular_modules': additional_angular_modules, 'can_edit': can_edit, 'dependencies_html': jinja2.utils.Markup(dependencies_html), 'exploration_title': exploration.title, 'exploration_version': version, 'collection_id': collection_id, 'collection_title': collection_title, 'gadget_templates': jinja2.utils.Markup(gadget_templates), 'interaction_templates': jinja2.utils.Markup(interaction_templates), 'is_private': rights_manager.is_exploration_private(exploration_id), # Note that this overwrites the value in base.py. 'meta_name': exploration.title, # Note that this overwrites the value in base.py. 'meta_description': utils.capitalize_string(exploration.objective), 'nav_mode': feconf.NAV_MODE_EXPLORE, }
def get_learner_collection_dict_by_id(collection_id, user, strict=True, allow_invalid_explorations=False, version=None): """Gets a dictionary representation of a collection given by the provided collection ID. This dict includes user-specific playthrough information. Args: collection_id: str. The id of the collection. user: UserActionsInfo. Object having user_id, role and actions for given user. strict: bool. Whether to fail noisily if no collection with the given id exists in the datastore. allow_invalid_explorations: bool. Whether to also return explorations that are invalid, such as deleted/private explorations. version: str or None. The version number of the collection to be retrieved. If it is None, the latest version will be retrieved. Returns: dict. A dictionary that 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. Raises: ValidationError. If the collection retrieved using the given ID references non-existent explorations. """ collection = collection_services.get_collection_by_id(collection_id, strict=strict, version=version) exp_ids = collection.exploration_ids exp_summary_dicts = get_displayable_exp_summary_dicts_matching_ids( exp_ids, user=user) 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_id = None completed_exp_ids = None if user.user_id: completed_exp_ids = ( collection_services.get_valid_completed_exploration_ids( user.user_id, collection)) next_exploration_id = collection.get_next_exploration_id( completed_exp_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 exploration. next_exploration_id = collection.first_exploration_id completed_exp_ids = [] collection_dict = collection.to_dict() collection_dict['nodes'] = [node.to_dict() for node in collection.nodes] collection_dict['playthrough_dict'] = { 'next_exploration_id': next_exploration_id, 'completed_exploration_ids': completed_exp_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 or is it a private exploration ' 'that you do not have edit access to?)' % 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) if summary_dict: collection_node['exploration_summary'] = summary_dict else: collection_node['exploration_summary'] = None return collection_dict
def get(self, exploration_id): """Handles GET requests.""" version_str = self.request.get('v') version = int(version_str) if version_str else None # Note: this is an optional argument and will be None when the # exploration is being played outside the context of a collection. collection_id = self.request.get('collection_id') try: exploration = exp_services.get_exploration_by_id( exploration_id, version=version) except Exception as e: raise self.PageNotFoundException(e) collection_title = None if collection_id: try: collection = collection_services.get_collection_by_id( collection_id) collection_title = collection.title except Exception as e: raise self.PageNotFoundException(e) version = exploration.version if not rights_manager.Actor(self.user_id).can_view( rights_manager.ACTIVITY_TYPE_EXPLORATION, exploration_id): raise self.PageNotFoundException is_iframed = (self.request.get('iframed') == 'true') # TODO(sll): Cache these computations. gadget_types = exploration.get_gadget_types() interaction_ids = exploration.get_interaction_ids() dependency_ids = ( interaction_registry.Registry.get_deduplicated_dependency_ids( interaction_ids)) dependencies_html, additional_angular_modules = ( dependency_registry.Registry.get_deps_html_and_angular_modules( dependency_ids)) gadget_templates = ( gadget_registry.Registry.get_gadget_html(gadget_types)) interaction_templates = ( rte_component_registry.Registry.get_html_for_all_components() + interaction_registry.Registry.get_interaction_html( interaction_ids)) self.values.update({ 'GADGET_SPECS': gadget_registry.Registry.get_all_specs(), 'INTERACTION_SPECS': interaction_registry.Registry.get_all_specs(), 'SHARING_OPTIONS': SHARING_OPTIONS.value, 'SHARING_OPTIONS_TWITTER_TEXT': SHARING_OPTIONS_TWITTER_TEXT.value, 'additional_angular_modules': additional_angular_modules, 'can_edit': ( bool(self.username) and self.username not in config_domain.BANNED_USERNAMES.value and rights_manager.Actor(self.user_id).can_edit( rights_manager.ACTIVITY_TYPE_EXPLORATION, exploration_id) ), 'dependencies_html': jinja2.utils.Markup( dependencies_html), 'exploration_title': exploration.title, 'exploration_version': version, 'collection_id': collection_id, 'collection_title': collection_title, 'gadget_templates': jinja2.utils.Markup(gadget_templates), 'iframed': is_iframed, 'interaction_templates': jinja2.utils.Markup( interaction_templates), 'is_private': rights_manager.is_exploration_private( exploration_id), # Note that this overwrites the value in base.py. 'meta_name': exploration.title, # Note that this overwrites the value in base.py. 'meta_description': utils.capitalize_string(exploration.objective), 'nav_mode': feconf.NAV_MODE_EXPLORE, }) if is_iframed: self.render_template( 'player/exploration_player.html', iframe_restriction=None) else: self.render_template('player/exploration_player.html')
def get(self, exploration_id): """Handles GET requests.""" version_str = self.request.get('v') version = int(version_str) if version_str else None # Note: this is an optional argument and will be None when the # exploration is being played outside the context of a collection. collection_id = self.request.get('collection_id') try: exploration = exp_services.get_exploration_by_id(exploration_id, version=version) except Exception as e: raise self.PageNotFoundException(e) collection_title = None if collection_id: try: collection = collection_services.get_collection_by_id( collection_id) collection_title = collection.title except Exception as e: raise self.PageNotFoundException(e) version = exploration.version if not rights_manager.Actor(self.user_id).can_view( feconf.ACTIVITY_TYPE_EXPLORATION, exploration_id): raise self.PageNotFoundException is_iframed = (self.request.get('iframed') == 'true') # TODO(sll): Cache these computations. gadget_types = exploration.get_gadget_types() interaction_ids = exploration.get_interaction_ids() dependency_ids = (interaction_registry.Registry. get_deduplicated_dependency_ids(interaction_ids)) dependencies_html, additional_angular_modules = ( dependency_registry.Registry.get_deps_html_and_angular_modules( dependency_ids)) gadget_templates = ( gadget_registry.Registry.get_gadget_html(gadget_types)) interaction_templates = ( rte_component_registry.Registry.get_html_for_all_components() + interaction_registry.Registry.get_interaction_html(interaction_ids) ) self.values.update({ 'GADGET_SPECS': gadget_registry.Registry.get_all_specs(), 'INTERACTION_SPECS': interaction_registry.Registry.get_all_specs(), 'DEFAULT_TWITTER_SHARE_MESSAGE_PLAYER': (DEFAULT_TWITTER_SHARE_MESSAGE_PLAYER.value), 'additional_angular_modules': additional_angular_modules, 'can_edit': (bool(self.username) and self.username not in config_domain.BANNED_USERNAMES.value and rights_manager.Actor(self.user_id).can_edit( feconf.ACTIVITY_TYPE_EXPLORATION, exploration_id)), 'dependencies_html': jinja2.utils.Markup(dependencies_html), 'exploration_title': exploration.title, 'exploration_version': version, 'collection_id': collection_id, 'collection_title': collection_title, 'gadget_templates': jinja2.utils.Markup(gadget_templates), 'iframed': is_iframed, 'interaction_templates': jinja2.utils.Markup(interaction_templates), 'is_private': rights_manager.is_exploration_private(exploration_id), # Note that this overwrites the value in base.py. 'meta_name': exploration.title, # Note that this overwrites the value in base.py. 'meta_description': utils.capitalize_string(exploration.objective), 'nav_mode': feconf.NAV_MODE_EXPLORE, }) if is_iframed: self.render_template('player/exploration_player.html', iframe_restriction=None) else: self.render_template('player/exploration_player.html')
def get(self, collection_id): """Populates the data on the individual collection page.""" try: collection = collection_services.get_collection_by_id( collection_id) except Exception as e: raise self.PageNotFoundException(e) exp_ids = collection.exploration_ids exp_summaries = ( exp_services.get_exploration_summaries_matching_ids(exp_ids)) exp_summaries_dict = { exp_id: exp_summaries[ind] for (ind, exp_id) in enumerate(exp_ids) } # TODO(bhenning): Users should not be recommended explorations they # have completed outside the context of a collection. next_exploration_ids = None completed_exploration_ids = None if self.user_id: completed_exploration_ids = ( collection_services.get_completed_exploration_ids( self.user_id, collection_id)) 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['next_exploration_ids'] = next_exploration_ids collection_dict['completed_exploration_ids'] = ( completed_exploration_ids) # 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']: summary = exp_summaries_dict.get(collection_node['exploration_id']) collection_node['exploration'] = { 'id': collection_node['exploration_id'], 'title': summary.title if summary else None, 'category': summary.category if summary else None, 'objective': summary.objective if summary else None, 'ratings': summary.ratings if summary else None, 'last_updated_msec': utils.get_time_in_millisecs( summary.exploration_model_last_updated) if summary else None, 'thumbnail_icon_url': utils.get_thumbnail_icon_url_for_category(summary.category), 'thumbnail_bg_color': utils.get_hex_color_for_category(summary.category), } self.values.update({ 'can_edit': (self.user_id and rights_manager.Actor(self.user_id).can_edit( rights_manager.ACTIVITY_TYPE_COLLECTION, collection_id)), 'collection': collection_dict, 'info_card_image_url': utils.get_info_card_url_for_category(collection.category), 'is_logged_in': bool(self.user_id), 'session_id': utils.generate_new_session_id(), }) self.render_json(self.values)
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. which includes useful data for the collection learner view. """ collection = collection_services.get_collection_by_id(collection_id, strict=strict, version=version) exp_ids = collection.exploration_ids exp_summary_dicts = get_displayable_exp_summary_dicts_matching_ids( exp_ids, editor_user_id=user_id) 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_exp_ids = None if user_id: completed_exp_ids = ( collection_services.get_valid_completed_exploration_ids( user_id, collection_id, collection)) next_exploration_ids = collection.get_next_exploration_ids( completed_exp_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_exp_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_exp_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 or is it a private exploration ' 'that you do not have edit access to?)' % 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) if summary_dict: collection_node['exploration_summary'] = summary_dict else: collection_node['exploration_summary'] = None return collection_dict
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. which includes useful data for the collection learner view. """ collection = collection_services.get_collection_by_id( collection_id, strict=strict, version=version) exp_ids = collection.exploration_ids exp_summary_dicts = get_displayable_exp_summary_dicts_matching_ids( exp_ids, editor_user_id=user_id) 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_exp_ids = None if user_id: completed_exp_ids = ( collection_services.get_valid_completed_exploration_ids( user_id, collection_id, collection)) next_exploration_ids = collection.get_next_exploration_ids( completed_exp_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_exp_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_exp_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 or is it a private exploration ' 'that you do not have edit access to?)' % 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) if summary_dict: collection_node['exploration_summary'] = summary_dict else: collection_node['exploration_summary'] = None return collection_dict