def test_get_multi_skills(self): example_1 = skill_domain.WorkedExample( state_domain.SubtitledHtml('2', '<p>Example Question 1</p>'), state_domain.SubtitledHtml('3', '<p>Example Explanation 1</p>')) self.save_new_skill( 'skill_a', self.user_id_admin, description='Description A', misconceptions=[], skill_contents=skill_domain.SkillContents( state_domain.SubtitledHtml('1', '<p>Explanation</p>'), [example_1], state_domain.RecordedVoiceovers.from_dict( {'voiceovers_mapping': { '1': {}, '2': {}, '3': {} }}), state_domain.WrittenTranslations.from_dict( {'translations_mapping': { '1': {}, '2': {}, '3': {} }}))) self.save_new_skill( 'skill_b', self.user_id_admin, description='Description B', misconceptions=[], skill_contents=skill_domain.SkillContents( state_domain.SubtitledHtml('1', '<p>Explanation</p>'), [example_1], state_domain.RecordedVoiceovers.from_dict( {'voiceovers_mapping': { '1': {}, '2': {}, '3': {} }}), state_domain.WrittenTranslations.from_dict( {'translations_mapping': { '1': {}, '2': {}, '3': {} }}))) skills = skill_fetchers.get_multi_skills(['skill_a', 'skill_b']) self.assertEqual(len(skills), 2) self.assertEqual(skills[0].id, 'skill_a') self.assertEqual(skills[0].description, 'Description A') self.assertEqual(skills[0].misconceptions, []) self.assertEqual(skills[1].id, 'skill_b') self.assertEqual(skills[1].description, 'Description B') self.assertEqual(skills[1].misconceptions, []) with self.assertRaisesRegexp(Exception, 'No skill exists for ID skill_c'): skill_fetchers.get_multi_skills(['skill_a', 'skill_c'])
def get(self): """Handles GET requests.""" comma_separated_skill_ids = ( self.request.get('comma_separated_skill_ids')) if not comma_separated_skill_ids: raise self.InvalidInputException( 'Expected request to contain parameter ' 'comma_separated_skill_ids.') skill_ids = comma_separated_skill_ids.split(',') try: for skill_id in skill_ids: skill_domain.Skill.require_valid_skill_id(skill_id) except utils.ValidationError: raise self.InvalidInputException('Invalid skill ID %s' % skill_id) try: skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) degrees_of_mastery = skill_services.get_multi_user_skill_mastery( self.user_id, skill_ids) self.values.update({ 'degrees_of_mastery': degrees_of_mastery }) self.render_json(self.values)
def put(self): """Handles PUT requests.""" mastery_change_per_skill = ( self.payload.get('mastery_change_per_skill')) if (not mastery_change_per_skill or not isinstance(mastery_change_per_skill, dict)): raise self.InvalidInputException( 'Expected payload to contain mastery_change_per_skill ' 'as a dict.') skill_ids = list(mastery_change_per_skill.keys()) current_degrees_of_mastery = ( skill_services.get_multi_user_skill_mastery(self.user_id, skill_ids) ) new_degrees_of_mastery = {} for skill_id in skill_ids: try: skill_domain.Skill.require_valid_skill_id(skill_id) except utils.ValidationError: raise self.InvalidInputException( 'Invalid skill ID %s' % skill_id) # float(bool) will not raise an error. if isinstance(mastery_change_per_skill[skill_id], bool): raise self.InvalidInputException( 'Expected degree of mastery of skill %s to be a number, ' 'received %s.' % (skill_id, mastery_change_per_skill[skill_id])) try: mastery_change_per_skill[skill_id] = ( float(mastery_change_per_skill[skill_id])) except (TypeError, ValueError): raise self.InvalidInputException( 'Expected degree of mastery of skill %s to be a number, ' 'received %s.' % (skill_id, mastery_change_per_skill[skill_id])) if current_degrees_of_mastery[skill_id] is None: current_degrees_of_mastery[skill_id] = 0.0 new_degrees_of_mastery[skill_id] = ( current_degrees_of_mastery[skill_id] + mastery_change_per_skill[skill_id]) if new_degrees_of_mastery[skill_id] < 0.0: new_degrees_of_mastery[skill_id] = 0.0 elif new_degrees_of_mastery[skill_id] > 1.0: new_degrees_of_mastery[skill_id] = 1.0 try: skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) skill_services.create_multi_user_skill_mastery( self.user_id, new_degrees_of_mastery) self.render_json({})
def get(self, comma_separated_skill_ids): """Handles GET requests.""" start_cursor = self.request.get('cursor') skill_ids = comma_separated_skill_ids.split(',') skill_ids = list(set(skill_ids)) try: _require_valid_skill_ids(skill_ids) except utils.ValidationError: raise self.InvalidInputException('Invalid skill id') try: skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) (question_summaries, merged_question_skill_links, next_start_cursor) = ( question_services.get_displayable_question_skill_link_details( constants.NUM_QUESTIONS_PER_PAGE, skill_ids, start_cursor)) return_dicts = [] for index, summary in enumerate(question_summaries): if summary is not None: if len(skill_ids) == 1: return_dicts.append({ 'summary': summary.to_dict(), 'skill_id': merged_question_skill_links[index].skill_ids[0], 'skill_description': (merged_question_skill_links[index]. skill_descriptions[0]), 'skill_difficulty': (merged_question_skill_links[index]. skill_difficulties[0]) }) else: return_dicts.append({ 'summary': summary.to_dict(), 'skill_ids': merged_question_skill_links[index].skill_ids, 'skill_descriptions': (merged_question_skill_links[index].skill_descriptions ), 'skill_difficulties': (merged_question_skill_links[index].skill_difficulties) }) self.values.update({ 'question_summary_dicts': return_dicts, 'next_start_cursor': next_start_cursor }) self.render_json(self.values)
def get(self, topic_name): # Topic cannot be None as an exception will be thrown from its decorator # if so. topic = topic_fetchers.get_topic_by_name(topic_name) selected_subtopic_ids = ( self.normalized_request.get('selected_subtopic_ids')) selected_skill_ids = [] for subtopic in topic.subtopics: # An error is not thrown here, since it's fine to just ignore the # passed in subtopic IDs, if they don't exist, which would be the # case if the creator deletes subtopics after the learner has # loaded the topic viewer page. if subtopic.id in selected_subtopic_ids: selected_skill_ids.extend(subtopic.skill_ids) try: skills = skill_fetchers.get_multi_skills(selected_skill_ids) except Exception as e: raise self.PageNotFoundException(e) skill_ids_to_descriptions_map = {} for skill in skills: skill_ids_to_descriptions_map[skill.id] = skill.description self.values.update({ 'topic_name': topic.name, 'skill_ids_to_descriptions_map': skill_ids_to_descriptions_map }) self.render_json(self.values)
def get(self, story_id): """Handles GET requests.""" story = story_fetchers.get_story_by_id(story_id) latest_completed_node_ids = ( story_fetchers.get_latest_completed_node_ids(self.user_id, story_id) ) if len(latest_completed_node_ids) == 0: raise self.PageNotFoundException try: skills = skill_fetchers.get_multi_skills( story.get_acquired_skill_ids_for_node_ids( latest_completed_node_ids )) except Exception as e: raise self.PageNotFoundException(e) skill_descriptions = {} for skill in skills: skill_descriptions[skill.id] = skill.description self.values.update({ 'skill_descriptions': skill_descriptions, 'story_name': story.title }) self.render_json(self.values)
def _get_target_id_to_skill_opportunity_dict(suggestions): """Returns a dict of target_id to skill opportunity summary dict. Args: suggestions: list(BaseSuggestion). A list of suggestions to retrieve opportunity dicts. Returns: dict. Dict mapping target_id to corresponding skill opportunity dict. """ target_ids = set([s.target_id for s in suggestions]) opportunity_id_to_opportunity_dict = { opp_id: (opp.to_dict() if opp is not None else None) for opp_id, opp in opportunity_services.get_skill_opportunities_by_ids( list(target_ids)).items() } opportunity_id_to_skill = { skill.id: skill for skill in skill_fetchers.get_multi_skills([ opp['id'] for opp in opportunity_id_to_opportunity_dict.values() if opp is not None ]) } for opp_id, skill in opportunity_id_to_skill.items(): if skill is not None: opportunity_id_to_opportunity_dict[opp_id]['skill_rubrics'] = [ rubric.to_dict() for rubric in skill.rubrics ] return opportunity_id_to_opportunity_dict
def get(self, topic_name): if not constants.ENABLE_NEW_STRUCTURE_PLAYERS: raise self.PageNotFoundException # Topic cannot be None as an exception will be thrown from its decorator # if so. topic = topic_fetchers.get_topic_by_name(topic_name) comma_separated_subtopic_ids = self.request.get('selected_subtopic_ids') selected_subtopic_ids = comma_separated_subtopic_ids.split(',') selected_skill_ids = [] for subtopic in topic.subtopics: # An error is not thrown here, since it's fine to just ignore the # passed in subtopic IDs, if they don't exist, which would be the # case if the creator deletes subtopics after the learner has # loaded the topic viewer page. if python_utils.UNICODE(subtopic.id) in selected_subtopic_ids: selected_skill_ids.extend(subtopic.skill_ids) try: skills = skill_fetchers.get_multi_skills(selected_skill_ids) except Exception as e: raise self.PageNotFoundException(e) skill_ids_to_descriptions_map = {} for skill in skills: skill_ids_to_descriptions_map[skill.id] = skill.description self.values.update({ 'topic_name': topic.name, 'skill_ids_to_descriptions_map': skill_ids_to_descriptions_map }) self.render_json(self.values)
def get_rubrics_of_skills(skill_ids): """Returns a list of rubrics corresponding to given skills. Args: skill_ids: list(str). The list of skill IDs. Returns: dict, list(str). The skill rubrics of skills keyed by their corresponding ids and the list of deleted skill ids, if any. """ skills = skill_fetchers.get_multi_skills(skill_ids, strict=False) skill_id_to_rubrics_dict = {} for skill in skills: if skill is not None: rubric_dicts = [rubric.to_dict() for rubric in skill.rubrics] skill_id_to_rubrics_dict[skill.id] = rubric_dicts deleted_skill_ids = [] for skill_id in skill_ids: if skill_id not in skill_id_to_rubrics_dict: skill_id_to_rubrics_dict[skill_id] = None deleted_skill_ids.append(skill_id) return skill_id_to_rubrics_dict, deleted_skill_ids
def _get_target_id_to_skill_opportunity_dict(suggestions): """Returns a dict of target_id to skill opportunity summary dict. Args: suggestions: list(BaseSuggestion). A list of suggestions to retrieve opportunity dicts. Returns: dict. Dict mapping target_id to corresponding skill opportunity dict. """ target_ids = set([s.target_id for s in suggestions]) opportunities = (opportunity_services.get_skill_opportunities_by_ids( list(target_ids))) opportunity_skill_ids = [opp.id for opp in opportunities] opportunity_id_to_skill = { skill.id: skill for skill in skill_fetchers.get_multi_skills(opportunity_skill_ids) } opportunity_id_to_opportunity = {} for opp in opportunities: opp_dict = opp.to_dict() skill = opportunity_id_to_skill.get(opp.id) if skill is not None: opp_dict['skill_rubrics'] = [ rubric.to_dict() for rubric in skill.rubrics ] opportunity_id_to_opportunity[opp.id] = opp_dict return opportunity_id_to_opportunity
def get(self): """Returns all skill IDs linked to some topic.""" skill_ids = topic_fetchers.get_all_skill_ids_assigned_to_some_topic() skills = skill_fetchers.get_multi_skills(skill_ids, strict=False) skill_dicts = [skill.to_dict() for skill in skills] self.values.update({'skills': skill_dicts}) self.render_json(self.values)
def get(self): """Handles GET requests.""" skill_ids = (self.normalized_request.get('selected_skill_ids')) try: for skill_id in skill_ids: skill_domain.Skill.require_valid_skill_id(skill_id) except utils.ValidationError as e: raise self.InvalidInputException('Invalid skill ID %s' % skill_id) from e try: skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) from e degrees_of_mastery = skill_services.get_multi_user_skill_mastery( self.user_id, skill_ids) self.values.update({'degrees_of_mastery': degrees_of_mastery}) self.render_json(self.values)
def get_skills_linked_to_question(question_id): """Returns a list of skills linked to a particular question. Args: question_id: str. ID of the question. Returns: list(Skill). The list of skills that are linked to the question. """ question = get_question_by_id(question_id) skills = skill_fetchers.get_multi_skills(question.linked_skill_ids) return skills
def get(self, comma_separated_skill_ids): """Handles GET requests.""" skill_ids = comma_separated_skill_ids.split(',') skills = skill_fetchers.get_multi_skills(skill_ids) concept_card_dicts = [] for skill in skills: concept_card_dicts.append(skill.skill_contents.to_dict()) self.values.update({'concept_card_dicts': concept_card_dicts}) self.render_json(self.values)
def put(self): """Handles PUT requests.""" mastery_change_per_skill = ( self.normalized_payload.get('mastery_change_per_skill')) skill_ids = list(mastery_change_per_skill.keys()) current_degrees_of_mastery = ( skill_services.get_multi_user_skill_mastery( self.user_id, skill_ids)) new_degrees_of_mastery = {} for skill_id in skill_ids: try: skill_domain.Skill.require_valid_skill_id(skill_id) except utils.ValidationError as e: raise self.InvalidInputException('Invalid skill ID %s' % skill_id) from e if current_degrees_of_mastery[skill_id] is None: current_degrees_of_mastery[skill_id] = 0.0 new_degrees_of_mastery[skill_id] = ( current_degrees_of_mastery[skill_id] + mastery_change_per_skill[skill_id]) if new_degrees_of_mastery[skill_id] < 0.0: new_degrees_of_mastery[skill_id] = 0.0 elif new_degrees_of_mastery[skill_id] > 1.0: new_degrees_of_mastery[skill_id] = 1.0 try: skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) from e skill_services.create_multi_user_skill_mastery(self.user_id, new_degrees_of_mastery) self.render_json({})
def get(self): """Returns all skill IDs linked to some topic.""" skill_ids = topic_services.get_all_skill_ids_assigned_to_some_topic() try: skills = skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) skill_dicts = [skill.to_dict() for skill in skills] self.values.update({'skills': skill_dicts}) self.render_json(self.values)
def get(self, question_id): """Gets the data for the question overview page.""" question = question_services.get_question_by_id( question_id, strict=False) associated_skill_dicts = [ skill.to_dict() for skill in skill_fetchers.get_multi_skills( question.linked_skill_ids)] self.values.update({ 'question_dict': question.to_dict(), 'associated_skill_dicts': associated_skill_dicts }) self.render_json(self.values)
def get(self, selected_skill_ids): """Handles GET requests. Args: selected_skill_ids: list(str). List of skill ids. """ skills = skill_fetchers.get_multi_skills(selected_skill_ids) concept_card_dicts = [] for skill in skills: concept_card_dicts.append(skill.skill_contents.to_dict()) self.values.update({ 'concept_card_dicts': concept_card_dicts }) self.render_json(self.values)
def get(self, comma_separated_skill_ids): """Handles GET requests.""" if not constants.ENABLE_NEW_STRUCTURE_PLAYERS: raise self.PageNotFoundException skill_ids = comma_separated_skill_ids.split(',') skills = skill_fetchers.get_multi_skills(skill_ids) concept_card_dicts = [] for skill in skills: concept_card_dicts.append(skill.skill_contents.to_dict()) self.values.update({ 'concept_card_dicts': concept_card_dicts }) self.render_json(self.values)
def get(self, comma_separated_skill_ids): """Populates the data on skill pages of the skill ids.""" skill_ids = comma_separated_skill_ids.split(',') try: for skill_id in skill_ids: skill_domain.Skill.require_valid_skill_id(skill_id) except utils.ValidationError: raise self.PageNotFoundException('Invalid skill id.') try: skills = skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) skill_dicts = [skill.to_dict() for skill in skills] self.values.update({'skills': skill_dicts}) self.render_json(self.values)
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 _validate_inapplicable_skill_misconception_ids(cls, item): """Validate that inapplicable skill misconception ids are valid. Args: item: datastore_services.Model. QuestionModel to validate. """ inapplicable_skill_misconception_ids = ( item.inapplicable_skill_misconception_ids) skill_misconception_id_mapping = {} skill_ids = [] for skill_misconception_id in inapplicable_skill_misconception_ids: skill_id, misconception_id = skill_misconception_id.split('-') skill_misconception_id_mapping[skill_id] = misconception_id skill_ids.append(skill_id) skills = skill_fetchers.get_multi_skills(skill_ids, strict=False) for skill in skills: if skill is not None: misconception_ids = [ misconception.id for misconception in skill.misconceptions ] expected_misconception_id = ( skill_misconception_id_mapping[skill.id]) if int(expected_misconception_id) not in misconception_ids: cls._add_error( 'misconception id', 'Entity id %s: misconception with the id %s does ' 'not exist in the skill with id %s' % (item.id, expected_misconception_id, skill.id)) missing_skill_ids = utils.compute_list_difference( skill_ids, [skill.id for skill in skills if skill is not None]) for skill_id in missing_skill_ids: cls._add_error( 'skill id', 'Entity id %s: skill with the following id does not exist:' ' %s' % (item.id, skill_id))
def get(self, comma_separated_skill_ids): """Handles GET requests.""" try: offset = int(self.request.get('offset')) except Exception: raise self.InvalidInputException('Invalid offset') skill_ids = comma_separated_skill_ids.split(',') skill_ids = list(set(skill_ids)) try: _require_valid_skill_ids(skill_ids) except utils.ValidationError: raise self.InvalidInputException('Invalid skill id') try: skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) question_summaries, merged_question_skill_links = ( question_services.get_displayable_question_skill_link_details( constants.NUM_QUESTIONS_PER_PAGE + 1, skill_ids, offset)) if len(question_summaries) <= constants.NUM_QUESTIONS_PER_PAGE: more = False else: more = True question_summaries.pop() merged_question_skill_links.pop() return_dicts = [] for index, summary in enumerate(question_summaries): if summary is not None: if len(skill_ids) == 1: return_dicts.append({ 'summary': summary.to_dict(), 'skill_id': merged_question_skill_links[index].skill_ids[0], 'skill_description': (merged_question_skill_links[index]. skill_descriptions[0]), 'skill_difficulty': (merged_question_skill_links[index]. skill_difficulties[0]) }) else: return_dicts.append({ 'summary': summary.to_dict(), 'skill_ids': merged_question_skill_links[index].skill_ids, 'skill_descriptions': (merged_question_skill_links[index].skill_descriptions ), 'skill_difficulties': (merged_question_skill_links[index].skill_difficulties) }) self.values.update({ 'question_summary_dicts': return_dicts, 'more': more }) self.render_json(self.values)
def post(self): """Handles POST requests.""" skill_ids = self.payload.get('skill_ids') if not skill_ids: raise self.InvalidInputException( 'skill_ids parameter isn\'t present in the payload') if len(skill_ids) > constants.MAX_SKILLS_PER_QUESTION: raise self.InvalidInputException( 'More than %d QuestionSkillLinks for one question ' 'is not supported.' % constants.MAX_SKILLS_PER_QUESTION) try: for skill_id in skill_ids: skill_domain.Skill.require_valid_skill_id(skill_id) except Exception as e: raise self.InvalidInputException('Skill ID(s) aren\'t valid: ', e) try: skill_fetchers.get_multi_skills(skill_ids) except Exception as e: raise self.PageNotFoundException(e) question_dict = self.payload.get('question_dict') if ((question_dict['id'] is not None) or ('question_state_data' not in question_dict) or ('language_code' not in question_dict) or (question_dict['version'] != 0)): raise self.InvalidInputException( 'Question Data should contain id, state data, language code, ' + 'and its version should be set as 0') question_dict['question_state_data_schema_version'] = ( feconf.CURRENT_STATE_SCHEMA_VERSION) question_dict['id'] = question_services.get_new_question_id() question_dict['linked_skill_ids'] = skill_ids try: question = question_domain.Question.from_dict(question_dict) except Exception as e: raise self.InvalidInputException('Question structure is invalid:', e) skill_difficulties = self.payload.get('skill_difficulties') if not skill_difficulties: raise self.InvalidInputException( 'skill_difficulties not present in the payload') if len(skill_ids) != len(skill_difficulties): raise self.InvalidInputException( 'Skill difficulties don\'t match up with skill IDs') try: skill_difficulties = [ float(difficulty) for difficulty in skill_difficulties ] except (ValueError, TypeError) as e: raise self.InvalidInputException( 'Skill difficulties must be a float value') from e if any((difficulty < 0 or difficulty > 1) for difficulty in skill_difficulties): raise self.InvalidInputException( 'Skill difficulties must be between 0 and 1') question_services.add_question(self.user_id, question) question_services.link_multiple_skills_for_question( self.user_id, question.id, skill_ids, skill_difficulties) html_list = question.question_state_data.get_all_html_content_strings() filenames = ( html_cleaner.get_image_filenames_from_html_strings(html_list)) image_validation_error_message_suffix = ( 'Please go to the question editor for question with id %s and edit ' 'the image.' % question.id) for filename in filenames: image = self.request.get(filename) if not image: logging.exception( 'Image not provided for file with name %s when the question' ' with id %s was created.' % (filename, question.id)) raise self.InvalidInputException( 'No image data provided for file with name %s. %s' % (filename, image_validation_error_message_suffix)) try: file_format = ( image_validation_services.validate_image_and_filename( image, filename)) except utils.ValidationError as e: e = '%s %s' % (e, image_validation_error_message_suffix) raise self.InvalidInputException(e) image_is_compressible = (file_format in feconf.COMPRESSIBLE_IMAGE_FORMATS) fs_services.save_original_and_compressed_versions_of_image( filename, feconf.ENTITY_TYPE_QUESTION, question.id, image, 'image', image_is_compressible) self.values.update({'question_id': question.id}) self.render_json(self.values)
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 })
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 })