예제 #1
0
def definition_for_include(parsed_include, parent_definition_key):
    """
    Given a parsed <xblock-include /> element as a XBlockInclude tuple, get the
    definition (OLX file) that it is pointing to.

    Arguments:

    parsed_include: An XBlockInclude tuple

    parent_definition_key: The BundleDefinitionLocator for the XBlock whose OLX
        contained the <xblock-include /> (i.e. the parent).

    Returns: a BundleDefinitionLocator
    """
    if parsed_include.link_id:
        links = get_bundle_direct_links_with_cache(
            parent_definition_key.bundle_uuid,
            # And one of the following will be set:
            bundle_version=parent_definition_key.bundle_version,
            draft_name=parent_definition_key.draft_name,
        )
        try:
            link = links[parsed_include.link_id]
        except KeyError:
            raise BundleFormatException("Link not found: {}".format(
                parsed_include.link_id))  # lint-amnesty, pylint: disable=raise-missing-from
        return BundleDefinitionLocator(
            bundle_uuid=link.bundle_uuid,
            block_type=parsed_include.block_type,
            olx_path="{}/{}/definition.xml".format(
                parsed_include.block_type, parsed_include.definition_id),
            bundle_version=link.version,
        )
    else:
        return BundleDefinitionLocator(
            bundle_uuid=parent_definition_key.bundle_uuid,
            block_type=parsed_include.block_type,
            olx_path="{}/{}/definition.xml".format(
                parsed_include.block_type, parsed_include.definition_id),
            bundle_version=parent_definition_key.bundle_version,
            draft_name=parent_definition_key.draft_name,
        )
예제 #2
0
    def definition_for_usage(self, usage_key):
        """
        Given the usage key for an XBlock in this library bundle, return the
        BundleDefinitionLocator which specifies the actual XBlock definition (as
        a path to an OLX in a specific blockstore bundle).

        Must return a BundleDefinitionLocator if the XBlock exists in this
        context, or None otherwise.

        For a content library, the rules are simple:
        * If the usage key points to a block in this library, the filename
          (definition) of the OLX file is always
            {block_type}/{usage_id}/definition.xml
          Each library has exactly one usage per definition for its own blocks.
        * However, block definitions from other content libraries may be linked
          into this library via <xblock-include ... /> directives. In that case,
          it's necessary to inspect every OLX file in this library that might
          have an <xblock-include /> directive in order to find what external
          block the usage ID refers to.
        """
        # Now that we know the library/bundle, find the block's definition
        if self.draft_name:
            version_arg = {"draft_name": self.draft_name}
        else:
            version_arg = {
                "bundle_version": get_bundle_version_number(self.bundle_uuid)
            }
        olx_path = "{}/{}/definition.xml".format(usage_key.block_type,
                                                 usage_key.usage_id)
        try:
            get_bundle_file_metadata_with_cache(self.bundle_uuid, olx_path,
                                                **version_arg)
            return BundleDefinitionLocator(self.bundle_uuid,
                                           usage_key.block_type, olx_path,
                                           **version_arg)
        except blockstore_api.BundleFileNotFound:
            # This must be a usage of a block from a linked bundle. One of the
            # OLX files in this bundle contains an <xblock-include usage="..."/>
            bundle_includes = self.get_bundle_includes()
            try:
                return bundle_includes[usage_key]
            except KeyError:
                return None
예제 #3
0
 def test_roundtrip_from_key(self, key_args):
     key = BundleDefinitionLocator(**key_args)
     serialized = text_type(key)
     deserialized = DefinitionKey.from_string(serialized)
     self.assertEqual(key, deserialized)
예제 #4
0
 def test_invalid_args(self, key_args):
     with self.assertRaises((InvalidKeyError, TypeError, ValueError)):
         BundleDefinitionLocator(**key_args)
예제 #5
0
    def get_bundle_includes(self):
        """
        Scan through the bundle and all linked bundles as needed to generate
        a complete list of all the blocks that are included as
        child/grandchild/... blocks of the blocks in this bundle.

        Returns a dict of {usage_key -> BundleDefinitionLocator}

        Blocks in the bundle that have no parent are not included.
        """
        cache_key = ("bundle_includes", )
        usages_found = self.cache.get(cache_key)
        if usages_found is not None:
            return usages_found

        usages_found = {}

        def add_definitions_children(usage_key, def_key):
            """
            Recursively add any children of the given XBlock usage+definition to
            usages_found.
            """
            if not does_block_type_support_children(def_key.block_type):
                return
            try:
                xml_node = xml_for_definition(def_key)
            except:  # pylint:disable=bare-except
                log.exception("Unable to load definition {}".format(def_key))
                return

            for child in xml_node:
                if child.tag != 'xblock-include':
                    continue
                try:
                    parsed_include = parse_xblock_include(child)
                    child_usage = usage_for_child_include(
                        usage_key, def_key, parsed_include)
                    child_def_key = definition_for_include(
                        parsed_include, def_key)
                except BundleFormatException:
                    log.exception(
                        "Unable to parse a child of {}".format(def_key))
                    continue
                usages_found[child_usage] = child_def_key
                add_definitions_children(child_usage, child_def_key)

        # Find all the definitions in this bundle and recursively add all their descendants:
        bundle_files = get_bundle_files_cached(self.bundle_uuid,
                                               draft_name=self.draft_name)
        if self.draft_name:
            version_arg = {"draft_name": self.draft_name}
        else:
            version_arg = {
                "bundle_version": get_bundle_version_number(self.bundle_uuid)
            }
        for bfile in bundle_files:
            if not bfile.path.endswith(
                    "/definition.xml") or bfile.path.count('/') != 2:
                continue  # Not an OLX file.
            block_type, usage_id, _unused = bfile.path.split('/')
            def_key = BundleDefinitionLocator(bundle_uuid=self.bundle_uuid,
                                              block_type=block_type,
                                              olx_path=bfile.path,
                                              **version_arg)
            usage_key = LibraryUsageLocatorV2(self.library_key, block_type,
                                              usage_id)
            add_definitions_children(usage_key, def_key)

        self.cache.set(cache_key, usages_found)
        return usages_found