示例#1
0
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
示例#2
0
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
示例#3
0
    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'])
示例#4
0
    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
示例#6
0
    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 _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
示例#8
0
    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)
示例#10
0
    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 _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)
示例#12
0
    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)
示例#13
0
    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)
示例#14
0
 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))
示例#15
0
 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')
示例#19
0
    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
示例#23
0
    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)
示例#24
0
    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")
示例#25
0
    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 _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
示例#32
0
 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