def test_cannot_get_story_from_model_with_invalid_schema_version(self): story_model = story_models.StoryModel.get(self.STORY_ID) story_model.story_contents_schema_version = 0 story_model.commit(self.USER_ID, 'change schema version', []) with self.assertRaisesRegexp( Exception, 'Sorry, we can only process v1-v%d story schemas at ' 'present.' % feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION): story_fetchers.get_story_from_model(story_model)
def test_migrate_story_contents_to_latest_schema(self): story_id = story_services.get_new_story_id() topic_id = topic_services.get_new_topic_id() user_id = 'user_id' self.save_new_topic(topic_id, user_id, name='Topic', abbreviated_name='abbrev', thumbnail_filename=None, description='A new topic', canonical_story_ids=[], additional_story_ids=[], uncategorized_skill_ids=[], subtopics=[], next_subtopic_id=0) self.save_new_story(story_id, user_id, 'Title', 'Description', 'Notes', topic_id) story_model = story_models.StoryModel.get(story_id) self.assertEqual(story_model.story_contents_schema_version, 1) swap_story_object = self.swap(story_domain, 'Story', MockStoryObject) current_schema_version_swap = self.swap( feconf, 'CURRENT_STORY_CONTENTS_SCHEMA_VERSION', 2) with swap_story_object, current_schema_version_swap: story = story_fetchers.get_story_from_model(story_model) self.assertEqual(story.story_contents_schema_version, 2)
def delete_story(committer_id, story_id, force_deletion=False): """Deletes the story with the given story_id. Args: committer_id: str. ID of the committer. story_id: str. ID of the story to be deleted. force_deletion: bool. If true, the story and its history are fully deleted and are unrecoverable. Otherwise, the story and all its history are marked as deleted, but the corresponding models are still retained in the datastore. This last option is the preferred one. """ story_model = story_models.StoryModel.get(story_id) story = story_fetchers.get_story_from_model(story_model) exp_ids = story.story_contents.get_all_linked_exp_ids() story_model.delete(committer_id, feconf.COMMIT_MESSAGE_STORY_DELETED, force_deletion=force_deletion) # This must come after the story is retrieved. Otherwise the memcache # key will be reinstated. story_memcache_key = story_fetchers.get_story_memcache_key(story_id) memcache_services.delete(story_memcache_key) # Delete the summary of the story (regardless of whether # force_deletion is True or not). delete_story_summary(story_id) # Delete the opportunities available related to the exploration used in the # story. opportunity_services.delete_exploration_opportunities(exp_ids)
def test_thumbnail_size_job_thumbnail_size_is_not_present(self): self.save_new_story_with_story_contents_schema_v1( self.STORY_ID, 'image.svg', '#F8BF74', None, self.albert_id, 'A title', 'A description', 'A note', self.TOPIC_ID) topic_services.add_canonical_story(self.albert_id, self.TOPIC_ID, self.STORY_ID) story_model = story_models.StoryModel.get(self.STORY_ID) story = story_fetchers.get_story_from_model(story_model) self.assertEqual(None, story.thumbnail_size_in_bytes) # Start migration job. job_id = (story_jobs_one_off.PopulateStoryThumbnailSizeOneOffJob. create_new()) story_jobs_one_off.PopulateStoryThumbnailSizeOneOffJob.enqueue(job_id) self.process_and_flush_pending_mapreduce_tasks() output = (story_jobs_one_off.PopulateStoryThumbnailSizeOneOffJob. get_output(job_id)) expected = [[ u'thumbnail_size_update_error', [ u'Thumbnail image.svg for story story_id not' ' found on the filesystem' ] ]] self.assertEqual(expected, [ast.literal_eval(x) for x in output])
def _migrate_story( story_id: str, story_model: story_models.StoryModel, # This must have a default value of None. Otherwise, Beam won't # execute this code. topic_id_to_topic: Optional[Dict[str, topic_domain.Topic]] = None ) -> result.Result[Tuple[str, story_domain.Story], Tuple[str, Exception]]: """Migrates story and transform story model into story object. Args: story_id: str. The id of the story. story_model: StoryModel. The story model to migrate. topic_id_to_topic: dict(str, Topic). The mapping from topic ID to topic. Returns: Result((str, Story), (str, Exception)). Result containing tuple that consists of story ID and either story object or Exception. Story object is returned when the migration was successful and Exception is returned otherwise. """ try: story = story_fetchers.get_story_from_model(story_model) story.validate() assert topic_id_to_topic is not None corresponding_topic = ( topic_id_to_topic[story.corresponding_topic_id]) story_services.validate_prerequisite_skills_in_story_contents( corresponding_topic.get_all_skill_ids(), story.story_contents) except Exception as e: logging.exception(e) return result.Err((story_id, e)) return result.Ok((story_id, story))
def map(item): if item.deleted: return story = story_fetchers.get_story_from_model(item) for node in story.story_contents.nodes: thumbnail_filename_and_size_value = '%s %s' % ( node.thumbnail_filename, node.thumbnail_size_in_bytes) yield (item.id, thumbnail_filename_and_size_value)
def delete_story(committer_id, story_id, force_deletion=False): """Deletes the story with the given story_id. Args: committer_id: str. ID of the committer. story_id: str. ID of the story to be deleted. force_deletion: bool. If true, the story and its history are fully deleted and are unrecoverable. Otherwise, the story and all its history are marked as deleted, but the corresponding models are still retained in the datastore. This last option is the preferred one. """ story_model = story_models.StoryModel.get(story_id, strict=False) if story_model is not None: story = story_fetchers.get_story_from_model(story_model) exp_ids = story.story_contents.get_all_linked_exp_ids() story_model.delete( committer_id, feconf.COMMIT_MESSAGE_STORY_DELETED, force_deletion=force_deletion ) # Reject the suggestions related to the exploration used in # the story. suggestion_services.auto_reject_translation_suggestions_for_exp_ids( exp_ids) exploration_context_models = ( exp_models.ExplorationContextModel.get_all().filter( exp_models.ExplorationContextModel.story_id == story_id ).fetch() ) exp_models.ExplorationContextModel.delete_multi( exploration_context_models ) # This must come after the story is retrieved. Otherwise the memcache # key will be reinstated. caching_services.delete_multi( caching_services.CACHE_NAMESPACE_STORY, None, [story_id]) # Delete the summary of the story (regardless of whether # force_deletion is True or not). delete_story_summary(story_id) # Delete the opportunities available. opportunity_services.delete_exp_opportunities_corresponding_to_story( story_id)
def _validate_chapter_title( cls, item, field_name_to_external_model_references): """Validate that chapter_title matches the title of the corresponding node of StoryModel. Args: item: datastore_services.Model. ExplorationOpportunitySummaryModel to validate. field_name_to_external_model_references: dict(str, (list(base_model_validators.ExternalModelReference))). A dict keyed by field name. The field name represents a unique identifier provided by the storage model to which the external model is associated. Each value contains a list of ExternalModelReference objects corresponding to the field_name. For examples, all the external Exploration Models corresponding to a storage model can be associated with the field name 'exp_ids'. This dict is used for validation of External Model properties linked to the storage model. """ story_model_references = ( field_name_to_external_model_references['story_ids']) for story_model_reference in story_model_references: story_model = story_model_reference.model_instance if story_model is None or story_model.deleted: model_class = story_model_reference.model_class model_id = story_model_reference.model_id cls._add_error( 'story_ids %s' % ( base_model_validators.ERROR_CATEGORY_FIELD_CHECK), 'Entity id %s: based on field story_ids having' ' value %s, expected model %s with id %s but it doesn\'t' ' exist' % ( item.id, model_id, model_class.__name__, model_id)) continue story = story_fetchers.get_story_from_model(story_model) corresponding_story_node = ( story.story_contents.get_node_with_corresponding_exp_id( item.id)) if item.chapter_title != corresponding_story_node.title: cls._add_error( 'chapter title check', 'Entity id %s: Chapter title: %s does not match the ' 'chapter title of external story model: %s' % ( item.id, item.chapter_title, corresponding_story_node.title))
def test_migrate_story_contents_to_latest_schema(self): story_id = story_services.get_new_story_id() topic_id = topic_services.get_new_topic_id() user_id = 'user_id' self.save_new_topic(topic_id, user_id, 'Topic', 'abbrev', None, 'A new topic', [], [], [], [], 0) self.save_new_story(story_id, user_id, 'Title', 'Description', 'Notes', topic_id) story_model = story_models.StoryModel.get(story_id) self.assertEqual(story_model.story_contents_schema_version, 1) swap_story_object = self.swap(story_domain, 'Story', MockStoryObject) current_schema_version_swap = self.swap( feconf, 'CURRENT_STORY_CONTENTS_SCHEMA_VERSION', 2) with swap_story_object, current_schema_version_swap: story = story_fetchers.get_story_from_model(story_model) self.assertEqual(story.story_contents_schema_version, 2)
def test_get_story_from_model(self): story_model = story_models.StoryModel.get(self.STORY_ID) story = story_fetchers.get_story_from_model(story_model) self.assertEqual(story.to_dict(), self.story.to_dict())
def _get_model_domain_object_instance(cls, item): return story_fetchers.get_story_from_model(item)
def test_get_story_from_model(self): schema_version = feconf.CURRENT_STORY_CONTENTS_SCHEMA_VERSION - 1 story_model = story_models.StoryModel.get(self.STORY_ID) story_model.story_contents_schema_version = schema_version story = story_fetchers.get_story_from_model(story_model) self.assertEqual(story.to_dict(), self.story.to_dict())