def test_migration_job_skips_deleted_story(self): """Tests that the story migration job skips deleted story and does not attempt to migrate. """ story = story_domain.Story.create_default_story( self.STORY_ID, title='A title') story_services.save_new_story(self.albert_id, story) # Delete the story before migration occurs. story_services.delete_story( self.albert_id, self.STORY_ID) # Ensure the story is deleted. with self.assertRaisesRegexp(Exception, 'Entity .* not found'): story_services.get_story_by_id(self.STORY_ID) # Start migration job on sample story. job_id = ( story_jobs_one_off.StoryMigrationOneOffJob.create_new()) story_jobs_one_off.StoryMigrationOneOffJob.enqueue(job_id) # This running without errors indicates the deleted story is # being ignored. self.process_and_flush_pending_tasks() # Ensure the story is still deleted. with self.assertRaisesRegexp(Exception, 'Entity .* not found'): story_services.get_story_by_id(self.STORY_ID) output = story_jobs_one_off.StoryMigrationOneOffJob.get_output(job_id) # pylint: disable=line-too-long expected = [[u'story_deleted', [u'Encountered 1 deleted stories.']]] self.assertEqual(expected, [ast.literal_eval(x) for x in output])
def test_migration_job_converts_old_story(self): """Tests that the schema conversion functions work correctly and an old story is converted to new version. """ # Generate story with old(v1) story contents data. self.save_new_story_with_story_contents_schema_v1( self.STORY_ID, self.albert_id, 'A title', 'A description', 'A note') story = ( story_services.get_story_by_id(self.STORY_ID)) self.assertEqual(story.story_contents_schema_version, 1) # Start migration job. job_id = ( story_jobs_one_off.StoryMigrationOneOffJob.create_new()) story_jobs_one_off.StoryMigrationOneOffJob.enqueue(job_id) self.process_and_flush_pending_tasks() # Verify the story migrates correctly. updated_story = ( story_services.get_story_by_id(self.STORY_ID)) self.assertEqual( updated_story.story_contents_schema_version, feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION) output = story_jobs_one_off.StoryMigrationOneOffJob.get_output(job_id) # pylint: disable=line-too-long expected = [[u'story_migrated', [u'1 stories successfully migrated.']]] self.assertEqual(expected, [ast.literal_eval(x) for x in output])
def put(self, topic_id, story_id): """Updates properties of the given story.""" story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None or story_id not in topic.canonical_story_ids: raise self.PageNotFoundException version = self.payload.get('version') self._require_valid_version(version, story.version) commit_message = self.payload.get('commit_message') change_dicts = self.payload.get('change_dicts') change_list = [ story_domain.StoryChange(change_dict) for change_dict in change_dicts ] try: story_services.update_story(self.user_id, story_id, change_list, commit_message) except utils.ValidationError as e: raise self.InvalidInputException(e) story_dict = story_services.get_story_by_id(story_id).to_dict() self.values.update({'story': story_dict}) self.render_json(self.values)
def test_update_story_node_properties(self): changelist = [ story_domain.StoryChange({ 'cmd': story_domain.CMD_ADD_STORY_NODE, 'node_id': self.NODE_ID_2 }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': ( story_domain.STORY_NODE_PROPERTY_DESTINATION_NODE_IDS), 'node_id': self.NODE_ID_2, 'old_value': [], 'new_value': [self.NODE_ID_1] }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_CONTENTS_PROPERTY, 'property_name': ( story_domain.INITIAL_NODE_ID), 'old_value': self.NODE_ID_1, 'new_value': self.NODE_ID_2 }) ] story_services.update_story( self.USER_ID, self.STORY_ID, changelist, 'Added story node.') story = story_services.get_story_by_id(self.STORY_ID) self.assertEqual( story.story_contents.nodes[1].destination_node_ids, [self.NODE_ID_1]) self.assertEqual(story.story_contents.initial_node_id, self.NODE_ID_2) self.assertEqual(story.story_contents.next_node_id, 'node_3') self.assertEqual(story.version, 2) story_summary = story_services.get_story_summary_by_id(self.STORY_ID) self.assertEqual(story_summary.node_count, 2) changelist = [ story_domain.StoryChange({ 'cmd': story_domain.CMD_DELETE_STORY_NODE, 'node_id': self.NODE_ID_1 }) ] story_services.update_story( self.USER_ID, self.STORY_ID, changelist, 'Removed a story node.') story_summary = story_services.get_story_summary_by_id(self.STORY_ID) story = story_services.get_story_by_id(self.STORY_ID) self.assertEqual(story_summary.node_count, 1) self.assertEqual(story.story_contents.nodes[0].destination_node_ids, [])
def test_update_story_properties(self): changelist = [ story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_PROPERTY, 'property_name': story_domain.STORY_PROPERTY_TITLE, 'old_value': 'Title', 'new_value': 'New Title' }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_PROPERTY, 'property_name': story_domain.STORY_PROPERTY_DESCRIPTION, 'old_value': 'Description', 'new_value': 'New Description' }) ] story_services.update_story(self.USER_ID, self.STORY_ID, changelist, 'Updated Title and Description.') story = story_services.get_story_by_id(self.STORY_ID) self.assertEqual(story.title, 'New Title') self.assertEqual(story.description, 'New Description') self.assertEqual(story.version, 3) story_summary = story_services.get_story_summary_by_id(self.STORY_ID) self.assertEqual(story_summary.title, 'New Title') self.assertEqual(story_summary.node_count, 1)
def test_migration_job_does_not_convert_up_to_date_story(self): """Tests that the story migration job does not convert a story that is already the latest schema version. """ # Create a new story that should not be affected by the # job. story = story_domain.Story.create_default_story( self.STORY_ID, title='A title') story_services.save_new_story(self.albert_id, story) self.assertEqual( story.story_contents_schema_version, feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION) # Start migration job. job_id = ( story_jobs_one_off.StoryMigrationOneOffJob.create_new()) story_jobs_one_off.StoryMigrationOneOffJob.enqueue(job_id) self.process_and_flush_pending_tasks() # Verify the story is exactly the same after migration. updated_story = ( story_services.get_story_by_id(self.STORY_ID)) self.assertEqual( updated_story.story_contents_schema_version, feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION) output = story_jobs_one_off.StoryMigrationOneOffJob.get_output(job_id) # pylint: disable=line-too-long expected = [[u'story_migrated', [u'1 stories successfully migrated.']]] self.assertEqual(expected, [ast.literal_eval(x) for x in output])
def get(self, topic_id, story_id): """Handles GET requests.""" if not constants.ENABLE_NEW_STRUCTURES: raise self.PageNotFoundException story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None or story_id not in topic.canonical_story_ids: raise self.PageNotFoundException self.values.update({ 'story_id': story.id, 'story_title': story.title, 'nav_mode': feconf.NAV_MODE_STORY_EDITOR }) self.render_template( 'pages/story_editor/story_editor.html', redirect_url_on_logout='/')
def map(item): if not constants.ENABLE_NEW_STRUCTURES: return if item.deleted: yield (StoryMigrationOneOffJob._DELETED_KEY, 1) return # Note: the read will bring the story up to the newest version. story = story_services.get_story_by_id(item.id) try: story.validate() except Exception as e: logging.error( 'Story %s failed validation: %s' % (item.id, e)) yield ( StoryMigrationOneOffJob._ERROR_KEY, 'Story %s failed validation: %s' % (item.id, e)) return # Write the new story into the datastore if it's different from # the old version. if item.schema_version <= feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION: commit_cmds = [story_domain.StoryChange({ 'cmd': story_domain.CMD_MIGRATE_SCHEMA_TO_LATEST_VERSION, 'from_version': item.schema_version, 'to_version': feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION })] story_services.update_story( feconf.MIGRATION_BOT_USERNAME, item.id, commit_cmds, 'Update story schema version to %d.' % ( feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION)) yield (StoryMigrationOneOffJob._MIGRATED_KEY, 1)
def get(self, exploration_id): """Handles GET request.""" start_cursor = self.request.get('cursor') story_id = self.request.get('story_id') story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.InvalidInputException if not story.has_exploration(exploration_id): raise self.InvalidInputException pretest_questions, next_start_cursor = ( question_services.get_questions_by_skill_ids( feconf.NUM_PRETEST_QUESTIONS, story.get_prerequisite_skill_ids_for_exp_id(exploration_id), start_cursor)) pretest_question_dicts = [ question.to_dict() for question in pretest_questions ] self.values.update({ 'pretest_question_dicts': pretest_question_dicts, 'next_start_cursor': next_start_cursor }) self.render_json(self.values)
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_nodes = [ completed_node.to_dict() for completed_node in story_services.get_completed_nodes_in_story( self.user_id, story_id) ] pending_nodes = [ pending_node.to_dict() for pending_node in story_services.get_pending_nodes_in_story( self.user_id, story_id) ] self.values.update({ 'story_title': story.title, 'story_description': story.description, 'completed_nodes': completed_nodes, 'pending_nodes': pending_nodes }) self.render_json(self.values)
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) interaction_ids = feconf.ALLOWED_QUESTION_INTERACTION_IDS interaction_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( interaction_dependency_ids)) interaction_templates = (interaction_registry.Registry. get_interaction_html(interaction_ids)) self.values.update({ 'DEFAULT_OBJECT_VALUES': obj_services.get_default_object_values(), 'additional_angular_modules': additional_angular_modules, 'INTERACTION_SPECS': interaction_registry.Registry.get_all_specs(), 'interaction_templates': jinja2.utils.Markup(interaction_templates), 'dependencies_html': jinja2.utils.Markup(dependencies_html), 'story_name': story.title }) self.render_template('dist/review-test-page.mainpage.html')
def setUp(self): super(StoryServicesUnitTests, self).setUp() self.STORY_ID = story_services.get_new_story_id() self.save_new_story(self.STORY_ID, self.USER_ID, 'Title', 'Description', 'Notes') changelist = [ story_domain.StoryChange({ 'cmd': story_domain.CMD_ADD_STORY_NODE, 'node_id': self.NODE_ID_1, 'title': 'Title 1' }) ] story_services.update_story(self.USER_ID, self.STORY_ID, changelist, 'Added node.') self.story = story_services.get_story_by_id(self.STORY_ID) self.signup('*****@*****.**', 'A') self.signup('*****@*****.**', 'B') self.signup(self.ADMIN_EMAIL, username=self.ADMIN_USERNAME) self.user_id_a = self.get_user_id_from_email('*****@*****.**') self.user_id_b = self.get_user_id_from_email('*****@*****.**') self.user_id_admin = self.get_user_id_from_email(self.ADMIN_EMAIL) self.set_admins([self.ADMIN_USERNAME]) self.set_topic_managers([user_services.get_username(self.user_id_a)]) self.user_a = user_services.UserActionsInfo(self.user_id_a) self.user_b = user_services.UserActionsInfo(self.user_id_b) self.user_admin = user_services.UserActionsInfo(self.user_id_admin)
def test_delete_story(self): story_services.delete_story(self.USER_ID, self.STORY_ID) self.assertEqual( story_services.get_story_by_id(self.STORY_ID, strict=False), None) self.assertEqual( story_services.get_story_summary_by_id(self.STORY_ID, strict=False), None)
def test_story_creation(self): self.login(self.ADMIN_EMAIL) csrf_token = self.get_new_csrf_token() json_response = self.post_json( '%s/%s' % (feconf.TOPIC_EDITOR_STORY_URL, self.topic_id), {'title': 'Story title'}, csrf_token=csrf_token) story_id = json_response['storyId'] self.assertEqual(len(story_id), 12) self.assertIsNotNone( story_services.get_story_by_id(story_id, strict=False)) self.logout()
def delete(self, topic_id, story_id): """Handles Delete requests.""" story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException story_services.delete_story(self.user_id, story_id) self.render_json(self.values)
def get(self, topic_id, story_id): """Handles GET requests.""" story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None or story_id not in topic.canonical_story_ids: raise self.PageNotFoundException self.render_template('dist/story-editor-page.mainpage.html')
def test_story_creation(self): self.login(self.ADMIN_EMAIL) with self.swap(constants, 'ENABLE_NEW_STRUCTURES', True): response = self.testapp.get( '%s/%s' % (feconf.TOPIC_EDITOR_URL_PREFIX, self.topic_id)) csrf_token = self.get_csrf_token_from_response(response) json_response = self.post_json( '%s/%s' % (feconf.TOPIC_EDITOR_STORY_URL, self.topic_id), {'title': 'Story title'}, csrf_token=csrf_token) story_id = json_response['storyId'] self.assertEqual(len(story_id), 12) self.assertIsNotNone( story_services.get_story_by_id(story_id, strict=False)) self.logout()
def get(self, topic_id, story_id): """Populates the data on the individual story page.""" story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None or story_id not in topic.canonical_story_ids: raise self.PageNotFoundException self.values.update({ 'story': story.to_dict(), 'topic_name': topic.name }) self.render_json(self.values)
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) latest_completed_node_ids = ( story_services.get_latest_completed_node_ids( self.user_id, story_id)) if len(latest_completed_node_ids) == 0: raise self.PageNotFoundException try: skills = skill_services.get_multi_skills( story.get_acquired_skill_ids_for_node_ids( latest_completed_node_ids)) except Exception, e: raise self.PageNotFoundException(e)
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.""" if not constants.ENABLE_NEW_STRUCTURE_PLAYERS: raise self.PageNotFoundException story = story_services.get_story_by_id(story_id) latest_completed_node_ids = ( story_services.get_latest_completed_node_ids( self.user_id, story_id)) if story is None: raise self.PageNotFoundException( Exception('The story with the given id doesn\'t exist.')) if len(latest_completed_node_ids) == 0: raise self.PageNotFoundException acquired_skill_ids = story.get_acquired_skill_ids_for_node_ids( latest_completed_node_ids) self.values.update({'skill_ids': acquired_skill_ids}) self.render_json(self.values)
def delete(self, topic_id, story_id): """Handles Delete requests.""" story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None: raise self.PageNotFoundException( Exception('The topic with the given id doesn\'t exist.')) story_services.delete_story(self.user_id, story_id) self.render_json(self.values)
def get(self, topic_id, story_id): """Handles GET requests.""" if not constants.ENABLE_NEW_STRUCTURE_EDITORS: raise self.PageNotFoundException story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None or story_id not in topic.canonical_story_ids: raise self.PageNotFoundException self.values.update({ 'story_id': story.id, }) self.render_template('dist/story_editor.html')
def delete(self, topic_id, story_id): """Handles Delete requests.""" if not feconf.ENABLE_NEW_STRUCTURES: raise self.PageNotFoundException story_domain.Story.require_valid_story_id(story_id) topic_domain.Topic.require_valid_topic_id(topic_id) story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None or story_id not in topic.canonical_story_ids: raise self.PageNotFoundException topic = topic_services.get_topic_by_id(topic_id, strict=False) if topic is None: raise self.PageNotFoundException( Exception('The topic with the given id doesn\'t exist.')) story_services.delete_story(self.user_id, story_id) topic_services.delete_story(self.user_id, topic_id, story_id)
def test_update_story_node_properties(self): changelist = [ story_domain.StoryChange({ 'cmd': story_domain.CMD_ADD_STORY_NODE, 'node_id': self.NODE_ID_2, 'title': 'Title 2' }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': (story_domain.STORY_NODE_PROPERTY_DESTINATION_NODE_IDS), 'node_id': self.NODE_ID_2, 'old_value': [], 'new_value': [self.NODE_ID_1] }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_NODE_OUTLINE_STATUS, 'node_id': self.NODE_ID_2, 'old_value': False, 'new_value': True }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_CONTENTS_PROPERTY, 'property_name': (story_domain.INITIAL_NODE_ID), 'old_value': self.NODE_ID_1, 'new_value': self.NODE_ID_2 }) ] story_services.update_story(self.USER_ID, self.STORY_ID, changelist, 'Added story node.') story = story_services.get_story_by_id(self.STORY_ID) self.assertEqual(story.story_contents.nodes[1].destination_node_ids, [self.NODE_ID_1]) self.assertEqual(story.story_contents.nodes[1].outline_is_finalized, True) self.assertEqual(story.story_contents.nodes[1].title, 'Title 2') self.assertEqual(story.story_contents.initial_node_id, self.NODE_ID_2) self.assertEqual(story.story_contents.next_node_id, 'node_3') self.assertEqual(story.version, 3) story_summary = story_services.get_story_summary_by_id(self.STORY_ID) self.assertEqual(story_summary.node_count, 2) changelist = [ story_domain.StoryChange({ 'cmd': story_domain.CMD_DELETE_STORY_NODE, 'node_id': self.NODE_ID_1 }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_NODE_OUTLINE_STATUS, 'node_id': self.NODE_ID_2, 'old_value': True, 'new_value': False }), story_domain.StoryChange({ 'cmd': story_domain.CMD_UPDATE_STORY_NODE_PROPERTY, 'property_name': (story_domain.STORY_NODE_PROPERTY_TITLE), 'node_id': self.NODE_ID_2, 'old_value': 'Title 2', 'new_value': 'Modified title 2' }), ] story_services.update_story(self.USER_ID, self.STORY_ID, changelist, 'Removed a story node.') story_summary = story_services.get_story_summary_by_id(self.STORY_ID) story = story_services.get_story_by_id(self.STORY_ID) self.assertEqual(story_summary.node_count, 1) self.assertEqual(story.story_contents.nodes[0].title, 'Modified title 2') self.assertEqual(story.story_contents.nodes[0].destination_node_ids, []) self.assertEqual(story.story_contents.nodes[0].outline_is_finalized, False)
def test_get_story_by_id(self): expected_story = self.story.to_dict() story = story_services.get_story_by_id(self.STORY_ID) self.assertEqual(story.to_dict(), expected_story)
def get(self, exploration_id): """Populates the data on the individual exploration page. Args: exploration_id: str. The ID of the exploration. """ version = self.request.get('v') story_id = self.request.get('story_id') version = int(version) if version else None try: exploration = exp_services.get_exploration_by_id( exploration_id, version=version) except Exception as e: raise self.PageNotFoundException(e) exploration_rights = rights_manager.get_exploration_rights( exploration_id, strict=False) user_settings = user_services.get_user_settings(self.user_id) preferred_audio_language_code = None if user_settings is not None: preferred_audio_language_code = ( user_settings.preferred_audio_language_code) # Retrieve all classifiers for the exploration. state_classifier_mapping = {} classifier_training_jobs = ( classifier_services.get_classifier_training_jobs( exploration_id, exploration.version, exploration.states)) for index, state_name in enumerate(exploration.states): if classifier_training_jobs[index] is not None: classifier_data = classifier_training_jobs[ index].classifier_data algorithm_id = classifier_training_jobs[index].algorithm_id data_schema_version = ( classifier_training_jobs[index].data_schema_version) state_classifier_mapping[state_name] = { 'algorithm_id': algorithm_id, 'classifier_data': classifier_data, 'data_schema_version': data_schema_version } pretest_question_dicts = [] next_cursor = None if story_id: story = story_services.get_story_by_id(story_id, strict=False) if story is None: raise self.InvalidInputException if not story.has_exploration(exploration_id): raise self.InvalidInputException pretest_questions, next_cursor = ( question_services.get_questions_by_skill_ids( feconf.NUM_PRETEST_QUESTIONS, story.get_prerequisite_skill_ids_for_exp_id(exploration_id), '') ) pretest_question_dicts = [ question.to_dict() for question in pretest_questions ] self.values.update({ 'can_edit': ( rights_manager.check_can_edit_activity( self.user, exploration_rights)), 'exploration': exploration.to_player_dict(), 'exploration_id': exploration_id, 'pretest_question_dicts': pretest_question_dicts, 'next_cursor_for_pretests': next_cursor, 'is_logged_in': bool(self.user_id), 'session_id': utils.generate_new_session_id(), 'version': exploration.version, 'preferred_audio_language_code': preferred_audio_language_code, 'state_classifier_mapping': state_classifier_mapping, 'auto_tts_enabled': exploration.auto_tts_enabled, 'correctness_feedback_enabled': ( exploration.correctness_feedback_enabled), }) self.render_json(self.values)