def structure_from_mongo(structure): """ Converts the 'blocks' key from a list [block_data] to a map {BlockKey: block_data}. Converts 'root' from [block_type, block_id] to BlockKey. Converts 'blocks.*.fields.children' from [[block_type, block_id]] to [BlockKey]. N.B. Does not convert any other ReferenceFields (because we don't know which fields they are at this level). """ check('seq[2]', structure['root']) check('list(dict)', structure['blocks']) for block in structure['blocks']: if 'children' in block['fields']: check('list(list[2])', block['fields']['children']) structure['root'] = BlockKey(*structure['root']) new_blocks = {} for block in structure['blocks']: if 'children' in block['fields']: block['fields']['children'] = [ BlockKey(*child) for child in block['fields']['children'] ] new_blocks[BlockKey(block['block_type'], block.pop('block_id'))] = BlockData(block) structure['blocks'] = new_blocks return structure
def structure_from_mongo(structure, course_context=None): """ Converts the 'blocks' key from a list [block_data] to a map {BlockKey: block_data}. Converts 'root' from [block_type, block_id] to BlockKey. Converts 'blocks.*.fields.children' from [[block_type, block_id]] to [BlockKey]. N.B. Does not convert any other ReferenceFields (because we don't know which fields they are at this level). Arguments: structure: The document structure to convert course_context (CourseKey): For metrics gathering, the CourseKey for the course that this data is being processed for. """ with TIMER.timer('structure_from_mongo', course_context) as tagger: tagger.measure('blocks', len(structure['blocks'])) check('seq[2]', structure['root']) check('list(dict)', structure['blocks']) for block in structure['blocks']: if 'children' in block['fields']: check('list(list[2])', block['fields']['children']) structure['root'] = BlockKey(*structure['root']) new_blocks = {} for block in structure['blocks']: if 'children' in block['fields']: block['fields']['children'] = [ BlockKey(*child) for child in block['fields']['children'] ] new_blocks[BlockKey(block['block_type'], block.pop('block_id'))] = BlockData(**block) structure['blocks'] = new_blocks return structure
def revert_to_published(self, location, user_id): """ Reverts an item to its last published version (recursively traversing all of its descendants). If no published version exists, a VersionConflictError is thrown. If a published version exists but there is no draft version of this item or any of its descendants, this method is a no-op. :raises InvalidVersionError: if no published version exists for the location specified """ if location.category in DIRECT_ONLY_CATEGORIES: return draft_course_key = location.course_key.for_branch(ModuleStoreEnum.BranchName.draft) with self.bulk_operations(draft_course_key): # get head version of Published branch published_course_structure = self._lookup_course( location.course_key.for_branch(ModuleStoreEnum.BranchName.published) ).structure published_block = self._get_block_from_structure( published_course_structure, BlockKey.from_usage_key(location) ) if published_block is None: raise InvalidVersionError(location) # create a new versioned draft structure draft_course_structure = self._lookup_course(draft_course_key).structure new_structure = self.version_structure(draft_course_key, draft_course_structure, user_id) # remove the block and its descendants from the new structure self._remove_subtree(BlockKey.from_usage_key(location), new_structure['blocks']) # copy over the block and its descendants from the published branch def copy_from_published(root_block_id): """ copies root_block_id and its descendants from published_course_structure to new_structure """ self._update_block_in_structure( new_structure, root_block_id, self._get_block_from_structure(published_course_structure, root_block_id) ) block = self._get_block_from_structure(new_structure, root_block_id) original_parent_location = location.course_key.make_usage_key(root_block_id.type, root_block_id.id) for child_block_id in block.fields.get('children', []): item_location = location.course_key.make_usage_key(child_block_id.type, child_block_id.id) self.update_parent_if_moved(item_location, original_parent_location, new_structure, user_id) copy_from_published(child_block_id) copy_from_published(BlockKey.from_usage_key(location)) # update course structure and index self.update_structure(draft_course_key, new_structure) index_entry = self._get_index_if_valid(draft_course_key) if index_entry is not None: self._update_head(draft_course_key, index_entry, ModuleStoreEnum.BranchName.draft, new_structure['_id'])
def revert_to_published(self, location, user_id): """ Reverts an item to its last published version (recursively traversing all of its descendants). If no published version exists, a VersionConflictError is thrown. If a published version exists but there is no draft version of this item or any of its descendants, this method is a no-op. :raises InvalidVersionError: if no published version exists for the location specified """ if location.block_type in DIRECT_ONLY_CATEGORIES: return draft_course_key = location.course_key.for_branch(ModuleStoreEnum.BranchName.draft) with self.bulk_operations(draft_course_key): # get head version of Published branch published_course_structure = self._lookup_course( location.course_key.for_branch(ModuleStoreEnum.BranchName.published) ).structure published_block = self._get_block_from_structure( published_course_structure, BlockKey.from_usage_key(location) ) if published_block is None: raise InvalidVersionError(location) # create a new versioned draft structure draft_course_structure = self._lookup_course(draft_course_key).structure new_structure = self.version_structure(draft_course_key, draft_course_structure, user_id) # remove the block and its descendants from the new structure self._remove_subtree(BlockKey.from_usage_key(location), new_structure['blocks']) # copy over the block and its descendants from the published branch def copy_from_published(root_block_id): """ copies root_block_id and its descendants from published_course_structure to new_structure """ self._update_block_in_structure( new_structure, root_block_id, self._get_block_from_structure(published_course_structure, root_block_id) ) block = self._get_block_from_structure(new_structure, root_block_id) original_parent_location = location.course_key.make_usage_key(root_block_id.type, root_block_id.id) for child_block_id in block.fields.get('children', []): item_location = location.course_key.make_usage_key(child_block_id.type, child_block_id.id) self.update_parent_if_moved(item_location, original_parent_location, new_structure, user_id) copy_from_published(child_block_id) copy_from_published(BlockKey.from_usage_key(location)) # update course structure and index self.update_structure(draft_course_key, new_structure) index_entry = self._get_index_if_valid(draft_course_key) if index_entry is not None: self._update_head(draft_course_key, index_entry, ModuleStoreEnum.BranchName.draft, new_structure['_id'])
def _get_version_xblock(self): course_key = self.usage_key.course_key block_key = BlockKey.from_usage_key(self.usage_key) store = modulestore() for s in store.modulestores: if isinstance(s, DraftVersioningModuleStore): try: entry = s.get_structure(course_key, self.version_guid) course = CourseEnvelope( course_key.replace(version_guid=self.version_guid), entry) course_entry = course runtime = s._get_cache(course_entry.structure['_id']) if runtime is None: runtime = s.create_runtime(course_entry, lazy=True) item = runtime.load_item(block_key, course_entry) self.xblock = item except Exception: raise GetItemError
def _load_item(self, usage_key, course_entry_override=None, **kwargs): # usage_key is either a UsageKey or just the block_key. if a usage_key, if isinstance(usage_key, BlockUsageLocator): # trust the passed in key to know the caller's expectations of which fields are filled in. # particularly useful for strip_keys so may go away when we're version aware course_key = usage_key.course_key if isinstance(usage_key.block_id, LocalId): try: return self.local_modules[usage_key] except KeyError: raise ItemNotFoundError else: block_key = BlockKey.from_usage_key(usage_key) else: block_key = usage_key course_info = course_entry_override or self.course_entry course_key = CourseLocator( version_guid=course_info['structure']['_id'], org=course_info.get('org'), course=course_info.get('course'), run=course_info.get('run'), branch=course_info.get('branch'), ) json_data = self.get_module_data(block_key, course_key) class_ = self.load_block_type(json_data.get('block_type')) # pass None for inherited_settings to signal that it should get the settings from cache new_item = self.xblock_from_json(class_, course_key, block_key, json_data, None, course_entry_override, **kwargs) return new_item
def import_xblock(self, user_id, course_key, block_type, block_id, fields=None, runtime=None, **kwargs): """ Split-based modulestores need to import published blocks to both branches """ with self.bulk_operations(course_key): # hardcode course root block id if block_type == 'course': block_id = self.DEFAULT_ROOT_COURSE_BLOCK_ID elif block_type == 'library': block_id = self.DEFAULT_ROOT_LIBRARY_BLOCK_ID new_usage_key = course_key.make_usage_key(block_type, block_id) if self.get_branch_setting() == ModuleStoreEnum.Branch.published_only: # override existing draft (PLAT-297, PLAT-299). NOTE: this has the effect of removing # any local changes w/ the import. draft_course = course_key.for_branch(ModuleStoreEnum.BranchName.draft) with self.branch_setting(ModuleStoreEnum.Branch.draft_preferred, draft_course): draft_block = self.import_xblock(user_id, draft_course, block_type, block_id, fields, runtime) return self.publish(draft_block.location.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs) # do the import partitioned_fields = self.partition_fields_by_scope(block_type, fields) course_key = self._map_revision_to_branch(course_key) # cast to branch_setting return self._update_item_from_fields( user_id, course_key, BlockKey(block_type, block_id), partitioned_fields, None, allow_not_found=True, force=True ) or self.get_item(new_usage_key)
def _load_item(self, usage_key, course_entry_override=None, **kwargs): # usage_key is either a UsageKey or just the block_key. if a usage_key, if isinstance(usage_key, BlockUsageLocator): # trust the passed in key to know the caller's expectations of which fields are filled in. # particularly useful for strip_keys so may go away when we're version aware course_key = usage_key.course_key if isinstance(usage_key.block_id, LocalId): try: return self.local_modules[usage_key] except KeyError: raise ItemNotFoundError else: block_key = BlockKey.from_usage_key(usage_key) else: block_key = usage_key course_info = course_entry_override or self.course_entry course_key = course_info.course_key if course_entry_override: structure_id = course_entry_override.structure.get('_id') else: structure_id = self.course_entry.structure.get('_id') json_data = self.get_module_data(block_key, course_key) class_ = self.load_block_type(json_data.get('block_type')) return self.xblock_from_json(class_, course_key, block_key, json_data, course_entry_override, **kwargs)
def has_changes(self, xblock): """ Checks if the given block has unpublished changes :param xblock: the block to check :return: True if the draft and published versions differ """ def get_course(branch_name): return self._lookup_course(xblock.location.course_key.for_branch(branch_name)).structure def get_block(course_structure, block_key): return self._get_block_from_structure(course_structure, block_key) draft_course = get_course(ModuleStoreEnum.BranchName.draft) published_course = get_course(ModuleStoreEnum.BranchName.published) def has_changes_subtree(block_key): draft_block = get_block(draft_course, block_key) published_block = get_block(published_course, block_key) if not published_block: return True # check if the draft has changed since the published was created if self._get_version(draft_block) != self._get_version(published_block): return True # check the children in the draft if 'children' in draft_block.setdefault('fields', {}): return any( [has_changes_subtree(child_block_id) for child_block_id in draft_block['fields']['children']] ) return False return has_changes_subtree(BlockKey.from_usage_key(xblock.location))
def import_xblock(self, user_id, course_key, block_type, block_id, fields=None, runtime=None, **kwargs): """ Split-based modulestores need to import published blocks to both branches """ with self.bulk_operations(course_key): # hardcode course root block id if block_type == 'course': block_id = self.DEFAULT_ROOT_BLOCK_ID new_usage_key = course_key.make_usage_key(block_type, block_id) if self.get_branch_setting() == ModuleStoreEnum.Branch.published_only: # if importing a direct only, override existing draft if block_type in DIRECT_ONLY_CATEGORIES: draft_course = course_key.for_branch(ModuleStoreEnum.BranchName.draft) with self.branch_setting(ModuleStoreEnum.Branch.draft_preferred, draft_course): draft = self.import_xblock(user_id, draft_course, block_type, block_id, fields, runtime) self._auto_publish_no_children(draft.location, block_type, user_id) return self.get_item(new_usage_key.for_branch(ModuleStoreEnum.BranchName.published)) # if new to published elif not self.has_item(new_usage_key.for_branch(ModuleStoreEnum.BranchName.published)): # check whether it's new to draft if not self.has_item(new_usage_key.for_branch(ModuleStoreEnum.BranchName.draft)): # add to draft too draft_course = course_key.for_branch(ModuleStoreEnum.BranchName.draft) with self.branch_setting(ModuleStoreEnum.Branch.draft_preferred, draft_course): draft = self.import_xblock(user_id, draft_course, block_type, block_id, fields, runtime) return self.publish(draft.location, user_id, blacklist=EXCLUDE_ALL) # do the import partitioned_fields = self.partition_fields_by_scope(block_type, fields) course_key = self._map_revision_to_branch(course_key) # cast to branch_setting return self._update_item_from_fields( user_id, course_key, BlockKey(block_type, block_id), partitioned_fields, None, allow_not_found=True, force=True ) or self.get_item(new_usage_key)
def import_xblock(self, user_id, course_key, block_type, block_id, fields=None, runtime=None, **kwargs): """ Split-based modulestores need to import published blocks to both branches """ with self.bulk_operations(course_key): # hardcode course root block id if block_type == 'course': block_id = self.DEFAULT_ROOT_COURSE_BLOCK_ID elif block_type == 'library': block_id = self.DEFAULT_ROOT_LIBRARY_BLOCK_ID new_usage_key = course_key.make_usage_key(block_type, block_id) # Both the course and library import process calls import_xblock(). # If importing a course -and- the branch setting is published_only, # then the non-draft course blocks are being imported. is_course = isinstance(course_key, CourseLocator) if is_course and self.get_branch_setting() == ModuleStoreEnum.Branch.published_only: # Override any existing drafts (PLAT-297, PLAT-299). This import/publish step removes # any local changes during the course import. draft_course = course_key.for_branch(ModuleStoreEnum.BranchName.draft) with self.branch_setting(ModuleStoreEnum.Branch.draft_preferred, draft_course): # Importing the block and publishing the block links the draft & published blocks' version history. draft_block = self.import_xblock(user_id, draft_course, block_type, block_id, fields, runtime, **kwargs) return self.publish(draft_block.location.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs) # lint-amnesty, pylint: disable=line-too-long # do the import partitioned_fields = self.partition_fields_by_scope(block_type, fields) course_key = self._map_revision_to_branch(course_key) # cast to branch_setting return self._update_item_from_fields( user_id, course_key, BlockKey(block_type, block_id), partitioned_fields, None, allow_not_found=True, force=True, **kwargs ) or self.get_item(new_usage_key)
def _get_head(self, xblock, branch): """ Gets block at the head of specified branch """ try: course_structure = self._lookup_course(xblock.location.course_key.for_branch(branch)).structure except ItemNotFoundError: # There is no published version xblock container, e.g. Library return None return self._get_block_from_structure(course_structure, BlockKey.from_usage_key(xblock.location))
def get_subtree_edited_on(self, xblock): """ See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin """ if not hasattr(xblock, "_subtree_edited_on"): json_data = self.module_data[BlockKey.from_usage_key(xblock.location)] if "_subtree_edited_on" not in json_data.setdefault("edit_info", {}): self._compute_subtree_edited_internal(xblock.location.block_id, json_data, xblock.location.course_key) setattr(xblock, "_subtree_edited_on", json_data["edit_info"]["_subtree_edited_on"]) return getattr(xblock, "_subtree_edited_on")
def get_subtree_edited_on(self, xblock): """ See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin """ if not hasattr(xblock, '_subtree_edited_on'): json_data = self.module_data[BlockKey.from_usage_key(xblock.location)] if '_subtree_edited_on' not in json_data.setdefault('edit_info', {}): self._compute_subtree_edited_internal( xblock.location.block_id, json_data, xblock.location.course_key ) setattr(xblock, '_subtree_edited_on', json_data['edit_info']['_subtree_edited_on']) return getattr(xblock, '_subtree_edited_on')
def get_subtree_edited_by(self, xblock): """ See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin """ if not hasattr(xblock, '_subtree_edited_by'): json_data = self.module_data[BlockKey.from_usage_key(xblock.location)] if '_subtree_edited_by' not in json_data.setdefault('edit_info', {}): self._compute_subtree_edited_internal( xblock.location.block_id, json_data, xblock.location.course_key ) setattr(xblock, '_subtree_edited_by', json_data['edit_info']['_subtree_edited_by']) return getattr(xblock, '_subtree_edited_by')
def get_definition_id(self, usage_id): if isinstance(usage_id.block_id, LocalId): # a LocalId indicates that this block hasn't been persisted yet, and is instead stored # in-memory in the local_modules dictionary. return self._cds.local_modules[usage_id].scope_ids.def_id else: block_key = BlockKey.from_usage_key(usage_id) module_data = self._cds.get_module_data(block_key, usage_id.course_key) if module_data.definition is not None: return DefinitionLocator(usage_id.block_type, module_data.definition) else: raise ValueError("All non-local blocks should have a definition specified")
def get_subtree_edited_on(self, xblock): """ See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin """ # pylint: disable=protected-access if not hasattr(xblock, '_subtree_edited_on'): block_data = self.module_data[BlockKey.from_usage_key(xblock.location)] if block_data.edit_info._subtree_edited_on is None: self._compute_subtree_edited_internal( block_data, xblock.location.course_key ) xblock._subtree_edited_on = block_data.edit_info._subtree_edited_on return xblock._subtree_edited_on
def get_subtree_edited_on(self, xblock): """ See :class: cms.lib.xblock.runtime.EditInfoRuntimeMixin """ # pylint: disable=protected-access if not hasattr(xblock, '_subtree_edited_on'): block_data = self.module_data[BlockKey.from_usage_key( xblock.location)] if block_data.edit_info._subtree_edited_on is None: self._compute_subtree_edited_internal( block_data, xblock.location.course_key) xblock._subtree_edited_on = block_data.edit_info._subtree_edited_on return xblock._subtree_edited_on
def _load_item(self, usage_key, course_entry_override=None, **kwargs): """ Instantiate the xblock fetching it either from the cache or from the structure :param course_entry_override: the course_info with the course_key to use (defaults to cached) """ # usage_key is either a UsageKey or just the block_key. if a usage_key, if isinstance(usage_key, BlockUsageLocator): # trust the passed in key to know the caller's expectations of which fields are filled in. # particularly useful for strip_keys so may go away when we're version aware course_key = usage_key.course_key if isinstance(usage_key.block_id, LocalId): try: return self.local_modules[usage_key] except KeyError: raise ItemNotFoundError # lint-amnesty, pylint: disable=raise-missing-from else: block_key = BlockKey.from_usage_key(usage_key) version_guid = self.course_entry.course_key.version_guid else: block_key = usage_key course_info = course_entry_override or self.course_entry course_key = course_info.course_key version_guid = course_key.version_guid # look in cache cached_module = self.modulestore.get_cached_block( course_key, version_guid, block_key) if cached_module: return cached_module block_data = self.get_module_data(block_key, course_key) class_ = self.load_block_type(block_data.block_type) block = self.xblock_from_json(class_, course_key, block_key, block_data, course_entry_override, **kwargs) # TODO Once TNL-5092 is implemented, we can expose the course version # information within the key identifier of the block. Until then, set # the course_version as a field on the returned block so higher layers # can use it when needed. block.course_version = version_guid self.modulestore.cache_block(course_key, version_guid, block_key, block) return block
def update_parent_if_moved(self, item_location, original_parent_location, course_structure, user_id): """ Update parent of an item if it has moved. Arguments: item_location (BlockUsageLocator) : Locator of item. original_parent_location (BlockUsageLocator) : Original parent block locator. course_structure (dict) : course structure of the course. user_id (int) : User id """ parent_block_keys = self._get_parents_from_structure(BlockKey.from_usage_key(item_location), course_structure) for block_key in parent_block_keys: # Item's parent is different than its new parent - so it has moved. if block_key.id != original_parent_location.block_id: old_parent_location = original_parent_location.course_key.make_usage_key(block_key.type, block_key.id) self.update_item_parent(item_location, original_parent_location, old_parent_location, user_id)
def get_definition_id(self, usage_id): if isinstance(usage_id.block_id, LocalId): # a LocalId indicates that this block hasn't been persisted yet, and is instead stored # in-memory in the local_modules dictionary. return self._cds.local_modules[usage_id].scope_ids.def_id else: block_key = BlockKey.from_usage_key(usage_id) module_data = self._cds.get_module_data(block_key, usage_id.course_key) if module_data.definition is not None: return DefinitionLocator(usage_id.block_type, module_data.definition) else: raise ValueError( "All non-local blocks should have a definition specified")
def _load_item(self, usage_key, course_entry_override=None, **kwargs): """ Instantiate the xblock fetching it either from the cache or from the structure :param course_entry_override: the course_info with the course_key to use (defaults to cached) """ # usage_key is either a UsageKey or just the block_key. if a usage_key, if isinstance(usage_key, BlockUsageLocator): # trust the passed in key to know the caller's expectations of which fields are filled in. # particularly useful for strip_keys so may go away when we're version aware course_key = usage_key.course_key if isinstance(usage_key.block_id, LocalId): try: return self.local_modules[usage_key] except KeyError: raise ItemNotFoundError else: block_key = BlockKey.from_usage_key(usage_key) version_guid = self.course_entry.course_key.version_guid else: block_key = usage_key course_info = course_entry_override or self.course_entry course_key = course_info.course_key version_guid = course_key.version_guid # look in cache cached_module = self.modulestore.get_cached_block(course_key, version_guid, block_key) if cached_module: return cached_module block_data = self.get_module_data(block_key, course_key) class_ = self.load_block_type(block_data.block_type) block = self.xblock_from_json(class_, course_key, block_key, block_data, course_entry_override, **kwargs) # TODO Once TNL-5092 is implemented, we can expose the course version # information within the key identifier of the block. Until then, set # the course_version as a field on the returned block so higher layers # can use it when needed. block.course_version = version_guid self.modulestore.cache_block(course_key, version_guid, block_key, block) return block
def _compute_subtree_edited_internal(self, block_id, json_data, course_key): """ Recurse the subtree finding the max edited_on date and its concomitant edited_by. Cache it """ max_date = json_data['edit_info']['edited_on'] max_by = json_data['edit_info']['edited_by'] for child in json_data.get('fields', {}).get('children', []): child_data = self.get_module_data(BlockKey(*child), course_key) if '_subtree_edited_on' not in json_data.setdefault('edit_info', {}): self._compute_subtree_edited_internal(child, child_data, course_key) if child_data['edit_info']['_subtree_edited_on'] > max_date: max_date = child_data['edit_info']['_subtree_edited_on'] max_by = child_data['edit_info']['_subtree_edited_by'] json_data['edit_info']['_subtree_edited_on'] = max_date json_data['edit_info']['_subtree_edited_by'] = max_by
def _compute_subtree_edited_internal(self, block_data, course_key): """ Recurse the subtree finding the max edited_on date and its corresponding edited_by. Cache it. """ # pylint: disable=protected-access max_date = block_data.edit_info.edited_on max_date_by = block_data.edit_info.edited_by for child in block_data.fields.get('children', []): child_data = self.get_module_data(BlockKey(*child), course_key) if block_data.edit_info._subtree_edited_on is None: self._compute_subtree_edited_internal(child_data, course_key) if child_data.edit_info._subtree_edited_on > max_date: max_date = child_data.edit_info._subtree_edited_on max_date_by = child_data.edit_info._subtree_edited_by block_data.edit_info._subtree_edited_on = max_date block_data.edit_info._subtree_edited_by = max_date_by
def _load_item(self, usage_key, course_entry_override=None, **kwargs): """ Instantiate the xblock fetching it either from the cache or from the structure :param course_entry_override: the course_info with the course_key to use (defaults to cached) """ # usage_key is either a UsageKey or just the block_key. if a usage_key, if isinstance(usage_key, BlockUsageLocator): # trust the passed in key to know the caller's expectations of which fields are filled in. # particularly useful for strip_keys so may go away when we're version aware course_key = usage_key.course_key if isinstance(usage_key.block_id, LocalId): try: return self.local_modules[usage_key] except KeyError: raise ItemNotFoundError else: block_key = BlockKey.from_usage_key(usage_key) version_guid = self.course_entry.course_key.version_guid else: block_key = usage_key course_info = course_entry_override or self.course_entry course_key = course_info.course_key version_guid = course_key.version_guid # look in cache cached_module = self.modulestore.get_cached_block( course_key, version_guid, block_key) if cached_module: return cached_module block_data = self.get_module_data(block_key, course_key) class_ = self.load_block_type(block_data.block_type) block = self.xblock_from_json(class_, course_key, block_key, block_data, course_entry_override, **kwargs) self.modulestore.cache_block(course_key, version_guid, block_key, block) return block
def _load_item(self, usage_key, course_entry_override=None, **kwargs): """ Instantiate the xblock fetching it either from the cache or from the structure :param course_entry_override: the course_info with the course_key to use (defaults to cached) """ # usage_key is either a UsageKey or just the block_key. if a usage_key, if isinstance(usage_key, BlockUsageLocator): # trust the passed in key to know the caller's expectations of which fields are filled in. # particularly useful for strip_keys so may go away when we're version aware course_key = usage_key.course_key if isinstance(usage_key.block_id, LocalId): try: return self.local_modules[usage_key] except KeyError: raise ItemNotFoundError else: block_key = BlockKey.from_usage_key(usage_key) version_guid = self.course_entry.course_key.version_guid else: block_key = usage_key course_info = course_entry_override or self.course_entry course_key = course_info.course_key version_guid = course_key.version_guid # look in cache cached_module = self.modulestore.get_cached_block(course_key, version_guid, block_key) if cached_module: return cached_module json_data = self.get_module_data(block_key, course_key) class_ = self.load_block_type(json_data.get('block_type')) block = self.xblock_from_json(class_, course_key, block_key, json_data, course_entry_override, **kwargs) self.modulestore.cache_block(course_key, version_guid, block_key, block) return block
def xblock_from_json(self, class_, course_key, block_key, json_data, course_entry_override=None, **kwargs): if course_entry_override is None: course_entry_override = self.course_entry else: # most recent retrieval is most likely the right one for next caller (see comment above fn) self.course_entry = CourseEnvelope( course_entry_override.course_key, self.course_entry.structure) definition_id = json_data.get('definition') # If no usage id is provided, generate an in-memory id if block_key is None: block_key = BlockKey(json_data['block_type'], LocalId()) if definition_id is not None and not json_data.get( 'definition_loaded', False): definition_loader = DefinitionLazyLoader( self.modulestore, course_key, block_key.type, definition_id, lambda fields: self.modulestore.convert_references_to_keys( course_key, self.load_block_type(block_key.type), fields, self.course_entry.structure['blocks'], )) else: definition_loader = None # If no definition id is provide, generate an in-memory id if definition_id is None: definition_id = LocalId() block_locator = BlockUsageLocator( course_key, block_type=block_key.type, block_id=block_key.id, ) converted_fields = self.modulestore.convert_references_to_keys( block_locator.course_key, class_, json_data.get('fields', {}), self.course_entry.structure['blocks'], ) if block_key in self._parent_map: parent_key = self._parent_map[block_key] parent = course_key.make_usage_key(parent_key.type, parent_key.id) else: parent = None kvs = SplitMongoKVS(definition_loader, converted_fields, parent=parent, field_decorator=kwargs.get('field_decorator')) if InheritanceMixin in self.modulestore.xblock_mixins: field_data = inheriting_field_data(kvs) else: field_data = KvsFieldData(kvs) try: module = self.construct_xblock_from_class( class_, ScopeIds(None, block_key.type, definition_id, block_locator), field_data, ) except Exception: log.warning("Failed to load descriptor", exc_info=True) return ErrorDescriptor.from_json( json_data, self, course_entry_override.course_key.make_usage_key( block_type='error', block_id=block_key.id), error_msg=exc_info_to_str(sys.exc_info())) edit_info = json_data.get('edit_info', {}) module._edited_by = edit_info.get('edited_by') module._edited_on = edit_info.get('edited_on') module.previous_version = edit_info.get('previous_version') module.update_version = edit_info.get('update_version') module.source_version = edit_info.get('source_version', None) module.definition_locator = DefinitionLocator(block_key.type, definition_id) # decache any pending field settings module.save() # If this is an in-memory block, store it in this system if isinstance(block_locator.block_id, LocalId): self.local_modules[block_locator] = module return module
def _get_head(self, xblock, branch): course_structure = self._lookup_course(xblock.location.course_key.for_branch(branch)).structure return self._get_block_from_structure(course_structure, BlockKey.from_usage_key(xblock.location))
def xblock_from_json(self, class_, course_key, block_key, block_data, course_entry_override=None, **kwargs): """ Load and return block info. """ if course_entry_override is None: course_entry_override = self.course_entry else: # most recent retrieval is most likely the right one for next caller (see comment above fn) self.course_entry = CourseEnvelope( course_entry_override.course_key, self.course_entry.structure) definition_id = block_data.definition # If no usage id is provided, generate an in-memory id if block_key is None: block_key = BlockKey(block_data.block_type, LocalId()) convert_fields = lambda field: self.modulestore.convert_references_to_keys( course_key, class_, field, self.course_entry.structure['blocks'], ) if definition_id is not None and not block_data.definition_loaded: definition_loader = DefinitionLazyLoader( self.modulestore, course_key, block_key.type, definition_id, convert_fields, ) else: definition_loader = None # If no definition id is provide, generate an in-memory id if definition_id is None: definition_id = LocalId() # Construct the Block Usage Locator: block_locator = course_key.make_usage_key( block_type=block_key.type, block_id=block_key.id, ) converted_fields = convert_fields(block_data.fields) converted_defaults = convert_fields(block_data.defaults) if block_key in self._parent_map: parent_key = self._parent_map[block_key] parent = course_key.make_usage_key(parent_key.type, parent_key.id) else: parent = None try: kvs = SplitMongoKVS(definition_loader, converted_fields, converted_defaults, parent=parent, field_decorator=kwargs.get('field_decorator')) if InheritanceMixin in self.modulestore.xblock_mixins: field_data = inheriting_field_data(kvs) else: field_data = KvsFieldData(kvs) module = self.construct_xblock_from_class( class_, ScopeIds(None, block_key.type, definition_id, block_locator), field_data, for_parent=kwargs.get('for_parent')) except Exception: # pylint: disable=broad-except log.warning("Failed to load descriptor", exc_info=True) return ErrorDescriptor.from_json( block_data, self, course_entry_override.course_key.make_usage_key( block_type='error', block_id=block_key.id), error_msg=exc_info_to_str(sys.exc_info())) edit_info = block_data.edit_info module._edited_by = edit_info.edited_by # pylint: disable=protected-access module._edited_on = edit_info.edited_on # pylint: disable=protected-access module.previous_version = edit_info.previous_version module.update_version = edit_info.update_version module.source_version = edit_info.source_version module.definition_locator = DefinitionLocator(block_key.type, definition_id) # decache any pending field settings module.save() # If this is an in-memory block, store it in this system if isinstance(block_locator.block_id, LocalId): self.local_modules[block_locator] = module return module