def test_cannot_get_question_from_model_with_invalid_schema_version(self): # Delete all question models. all_question_models = question_models.QuestionModel.get_all() question_models.QuestionModel.delete_multi( [question_model.id for question_model in all_question_models], feconf.SYSTEM_COMMITTER_ID, '', force_deletion=True) all_question_models = question_models.QuestionModel.get_all() self.assertEqual(all_question_models.count(), 0) question_id = question_services.get_new_question_id() question_model = question_models.QuestionModel( id=question_id, question_state_data=( self._create_valid_question_data('ABC').to_dict()), language_code='en', version=0, question_state_data_schema_version=0) question_model.commit( self.editor_id, 'question model created', [{'cmd': question_domain.CMD_CREATE_NEW}]) all_question_models = question_models.QuestionModel.get_all() self.assertEqual(all_question_models.count(), 1) question_model = all_question_models.get() with self.assertRaisesRegexp( Exception, 'Sorry, we can only process v25-v%d state schemas at present.' % feconf.CURRENT_STATE_SCHEMA_VERSION): question_fetchers.get_question_from_model(question_model)
def test_post_with_valid_images(self): """Test question creation with valid images.""" self.login(self.CURRICULUM_ADMIN_EMAIL) csrf_token = self.get_new_csrf_token() filename = 'img.png' question_dict = self.question.to_dict() question_dict['id'] = None question_dict['version'] = 0 content_html = ( '<oppia-noninteractive-image filepath-with-value=' '""img.png"" caption-with-value="""" ' 'alt-with-value=""Image""></oppia-noninteractive-image>') question_dict['question_state_data']['content']['html'] = content_html post_data = { 'question_dict': question_dict, 'skill_ids': [self.skill_id], 'skill_difficulties': [0.6] } with python_utils.open_file(os.path.join(feconf.TESTS_DATA_DIR, 'img.png'), 'rb', encoding=None) as f: raw_image = f.read() self.post_json(feconf.NEW_QUESTION_URL, post_data, csrf_token=csrf_token, upload_files=((filename, filename, raw_image), )) all_models = question_models.QuestionModel.get_all() questions = [ question_fetchers.get_question_from_model(model) for model in all_models ] self.assertEqual(len(questions), 2) self.logout()
def map(item): if item.deleted: yield (QuestionMigrationOneOffJob._DELETED_KEY, 1) return # Note: the read will bring the question up to the newest version. question = question_fetchers.get_question_from_model(item) try: question.validate() except Exception as e: logging.exception( 'Question %s failed validation: %s' % (item.id, e)) yield ( QuestionMigrationOneOffJob._ERROR_KEY, 'Question %s failed validation: %s' % (item.id, e)) return # Write the new question into the datastore if it's different from # the old version. if (item.question_state_data_schema_version <= feconf.CURRENT_STATE_SCHEMA_VERSION): commit_cmds = [question_domain.QuestionChange({ 'cmd': question_domain.CMD_MIGRATE_STATE_SCHEMA_TO_LATEST_VERSION, # pylint: disable=line-too-long 'from_version': item.question_state_data_schema_version, 'to_version': feconf.CURRENT_STATE_SCHEMA_VERSION })] question_services.update_question( feconf.MIGRATION_BOT_USERNAME, item.id, commit_cmds, 'Update question state schema version to %d.' % ( feconf.CURRENT_STATE_SCHEMA_VERSION)) yield (QuestionMigrationOneOffJob._MIGRATED_KEY, 1)
def map(item): if item.deleted: yield (FixQuestionImagesStorageOneOffJob._DELETED_KEY, 1) return question = question_fetchers.get_question_from_model(item) html_list = question.question_state_data.get_all_html_content_strings() image_filenames = html_cleaner.get_image_filenames_from_html_strings( html_list) file_system_class = fs_services.get_entity_file_system_class() question_fs = fs_domain.AbstractFileSystem(file_system_class( feconf.ENTITY_TYPE_QUESTION, question.id)) success_count = 0 # For each image filename, check if it exists in the correct path. If # not, copy the image file to the correct path else continue. for image_filename in image_filenames: if not question_fs.isfile('image/%s' % image_filename): for skill_id in question.linked_skill_ids: skill_fs = fs_domain.AbstractFileSystem(file_system_class( feconf.ENTITY_TYPE_SKILL, skill_id)) if skill_fs.isfile('image/%s' % image_filename): fs_services.copy_images( feconf.ENTITY_TYPE_SKILL, skill_id, feconf.ENTITY_TYPE_QUESTION, question.id, [image_filename]) success_count += 1 break if success_count > 0: yield ( FixQuestionImagesStorageOneOffJob._IMAGE_COPIED, '%s image paths were fixed for question id %s with ' 'linked_skill_ids: %r' % ( success_count, question.id, question.linked_skill_ids))
def test_get_question_from_model_with_current_valid_schema_version(self): # Delete all question models. all_question_models = question_models.QuestionModel.get_all() question_models.QuestionModel.delete_multi( [question_model.id for question_model in all_question_models], feconf.SYSTEM_COMMITTER_ID, '', force_deletion=True) all_question_models = question_models.QuestionModel.get_all() self.assertEqual(all_question_models.count(), 0) question_id = question_services.get_new_question_id() question_model = question_models.QuestionModel( id=question_id, question_state_data=( self._create_valid_question_data('ABC').to_dict()), language_code='en', version=0, question_state_data_schema_version=48) question_model.commit(self.editor_id, 'question model created', [{ 'cmd': question_domain.CMD_CREATE_NEW }]) all_question_models = question_models.QuestionModel.get_all() self.assertEqual(all_question_models.count(), 1) question_model = all_question_models.get() updated_question_model = question_fetchers.get_question_from_model( question_model) self.assertEqual( updated_question_model.question_state_data_schema_version, feconf.CURRENT_STATE_SCHEMA_VERSION)
def test_post_with_topic_manager_email_allows_question_creation(self): self.login(self.TOPIC_MANAGER_EMAIL) csrf_token = self.get_new_csrf_token() question_dict = self.question.to_dict() question_dict['id'] = None self.post_json( feconf.NEW_QUESTION_URL, { 'question_dict': question_dict, 'skill_ids': [self.skill_id], 'skill_difficulties': [0.6] }, csrf_token=csrf_token) all_models = question_models.QuestionModel.get_all() questions = [ question_fetchers.get_question_from_model(model) for model in all_models ] self.assertEqual(len(questions), 2) self.logout()
def get_question_by_id(question_id, strict=True): """Returns a domain object representing a question. Args: question_id: str. ID of the question. strict: bool. Whether to fail noisily if no question with the given id exists in the datastore. Returns: Question or None. The domain object representing a question with the given id, or None if it does not exist. """ question_model = question_models.QuestionModel.get( question_id, strict=strict) if question_model: question = question_fetchers.get_question_from_model(question_model) return question else: return None
def test_post_with_admin_email_allows_question_creation(self): self.login(self.ADMIN_EMAIL) csrf_token = self.get_new_csrf_token() question_dict = self.question.to_dict() question_dict['id'] = None question_dict['version'] = 0 self.post_json( feconf.NEW_QUESTION_URL, { 'question_dict': question_dict, 'skill_ids': [self.skill_id], 'skill_difficulties': [0.6] }, csrf_token=csrf_token, expected_status_int=200) all_models = question_models.QuestionModel.get_all() questions = [ question_fetchers.get_question_from_model(model) for model in all_models ] self.assertEqual(len(questions), 2) self.logout()
def test_get_question_from_model_with_current_valid_schema_version( self) -> None: # Delete all question models. all_question_models = question_models.QuestionModel.get_all() question_models.QuestionModel.delete_multi( [question_model.id for question_model in all_question_models], feconf.SYSTEM_COMMITTER_ID, '', force_deletion=True) all_question_models = question_models.QuestionModel.get_all() self.assertEqual(all_question_models.count(), 0) question_id = question_services.get_new_question_id( ) # type: ignore[no-untyped-call] question_model = question_models.QuestionModel( id=question_id, question_state_data=(self._create_valid_question_data( 'ABC').to_dict()), # type: ignore[no-untyped-call] language_code='en', version=0, question_state_data_schema_version=48) question_model.commit(self.editor_id, 'question model created', [{ 'cmd': question_domain.CMD_CREATE_NEW }]) all_question_models = question_models.QuestionModel.get_all() self.assertEqual(all_question_models.count(), 1) fetched_question_models = all_question_models.get() # Ruling out the possibility of None for mypy type checking. assert fetched_question_models is not None updated_question_model = question_fetchers.get_question_from_model( fetched_question_models) self.assertEqual( updated_question_model.question_state_data_schema_version, feconf.CURRENT_STATE_SCHEMA_VERSION)
def test_migrate_question_state_from_v29_to_v30(self): answer_group = { 'outcome': { 'dest': 'abc', 'feedback': { 'content_id': 'feedback_1', 'html': '<p>Feedback</p>' }, 'labelled_as_correct': True, 'param_changes': [], 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None }, 'rule_specs': [{ 'inputs': { 'x': 'Test' }, 'rule_type': 'Contains' }], 'training_data': [], 'tagged_misconception_id': None } question_state_dict = { 'content': { 'content_id': 'content_1', 'html': 'Question 1' }, 'recorded_voiceovers': { 'voiceovers_mapping': {} }, 'written_translations': { 'translations_mapping': { 'explanation': {} } }, 'interaction': { 'answer_groups': [answer_group], 'confirmed_unclassified_answers': [], 'customization_args': {}, 'default_outcome': { 'dest': None, 'feedback': { 'content_id': 'feedback_1', 'html': 'Correct Answer' }, 'param_changes': [], 'refresher_exploration_id': None, 'labelled_as_correct': True, 'missing_prerequisite_skill_id': None }, 'hints': [{ 'hint_content': { 'content_id': 'hint_1', 'html': 'Hint 1' } }], 'solution': { 'correct_answer': 'This is the correct answer', 'answer_is_exclusive': False, 'explanation': { 'content_id': 'explanation_1', 'html': 'Solution explanation' } }, 'id': 'TextInput' }, 'param_changes': [], 'solicit_answer_details': False, 'classifier_model_id': None } question_model = question_models.QuestionModel( id='question_id', question_state_data=question_state_dict, language_code='en', version=0, linked_skill_ids=['skill_id'], question_state_data_schema_version=29) commit_cmd = question_domain.QuestionChange( {'cmd': question_domain.CMD_CREATE_NEW}) commit_cmd_dicts = [commit_cmd.to_dict()] question_model.commit('user_id_admin', 'question model created', commit_cmd_dicts) current_schema_version_swap = self.swap( feconf, 'CURRENT_STATE_SCHEMA_VERSION', 30) with current_schema_version_swap: question = question_fetchers.get_question_from_model( question_model) self.assertEqual(question.question_state_data_schema_version, 30) answer_groups = question.question_state_data.interaction.answer_groups self.assertEqual(answer_groups[0].tagged_skill_misconception_id, None)
def test_migrate_question_state_from_v33_to_v34(self): feedback_html_content = ( '<p>Feedback</p><oppia-noninteractive-math raw_latex-with-value="' '&quot;+,-,-,+&quot;"></oppia-noninteractive-math>') answer_group = { 'outcome': { 'dest': 'abc', 'feedback': { 'content_id': 'feedback_1', 'html': feedback_html_content }, 'labelled_as_correct': True, 'param_changes': [], 'refresher_exploration_id': None, 'missing_prerequisite_skill_id': None }, 'rule_specs': [{ 'inputs': { 'x': ['A'] }, 'rule_type': 'Equals' }], 'training_data': [], 'tagged_skill_misconception_id': None } question_state_dict = { 'content': { 'content_id': 'content_1', 'html': 'Question 1' }, 'recorded_voiceovers': { 'voiceovers_mapping': {} }, 'written_translations': { 'translations_mapping': { 'explanation': {} } }, 'interaction': { 'answer_groups': [answer_group], 'confirmed_unclassified_answers': [], 'customization_args': { 'choices': { 'value': '' }, 'showChoicesInShuffledOrder': { 'value': True } }, 'default_outcome': { 'dest': None, 'feedback': { 'content_id': 'feedback_1', 'html': 'Correct Answer' }, 'param_changes': [], 'refresher_exploration_id': None, 'labelled_as_correct': True, 'missing_prerequisite_skill_id': None }, 'hints': [{ 'hint_content': { 'content_id': 'hint_1', 'html': 'Hint 1' } }], 'solution': {}, 'id': 'MultipleChoiceInput' }, 'param_changes': [], 'solicit_answer_details': False, 'classifier_model_id': None } expected_feeedback_html_content = ( '<p>Feedback</p><oppia-noninteractive-math math_content-with-val' 'ue="{&quot;raw_latex&quot;: &quot;+,-,-,+&quot;,' ' &quot;svg_filename&quot;: &quot;&quot;}"></oppi' 'a-noninteractive-math>') question_model = (question_models.QuestionModel( id='question_id', question_state_data=question_state_dict, language_code='en', version=0, linked_skill_ids=['skill_id'], question_state_data_schema_version=33)) commit_cmd = (question_domain.QuestionChange( {'cmd': question_domain.CMD_CREATE_NEW})) commit_cmd_dicts = [commit_cmd.to_dict()] question_model.commit('user_id_admin', 'question model created', commit_cmd_dicts) current_schema_version_swap = self.swap( feconf, 'CURRENT_STATE_SCHEMA_VERSION', 34) with current_schema_version_swap: question = question_fetchers.get_question_from_model( question_model) self.assertEqual(question.question_state_data_schema_version, 34) migrated_answer_group = ( question.question_state_data.interaction.answer_groups[0]) self.assertEqual(migrated_answer_group.outcome.feedback.html, expected_feeedback_html_content)
def _get_model_domain_object_instance(cls, item): return question_fetchers.get_question_from_model(item)