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, )
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
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)
def test_invalid_args(self, key_args): with self.assertRaises((InvalidKeyError, TypeError, ValueError)): BundleDefinitionLocator(**key_args)
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