def get_last_published_time(self): """ Return the timestamp when the current library was last published. If the current draft has never been published, return 0. """ cache_key = ("last_published_time", ) usages_found = self.cache.get(cache_key) if usages_found is not None: return usages_found version = get_bundle_version_number(self.bundle_uuid) if version == 0: return None created_at_str = blockstore_api.get_bundle_version(self.bundle_uuid, version)['snapshot']['created_at'] last_published_time = dateutil.parser.parse(created_at_str) self.cache.set(cache_key, last_published_time) return last_published_time
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 get_bundle_links(library_key): """ Get the list of bundles/libraries linked to this content library. Returns LibraryBundleLink objects (defined above). Because every content library is a blockstore bundle, it can have "links" to other bundles, which may or may not be content libraries. This allows using XBlocks (or perhaps even static assets etc.) from another bundle without needing to duplicate/copy the data. Links always point to a specific published version of the target bundle. Links are identified by a slug-like ID, e.g. "link1" """ ref = ContentLibrary.objects.get_by_key(library_key) links = blockstore_cache.get_bundle_draft_direct_links_cached( ref.bundle_uuid, DRAFT_NAME) results = [] # To be able to quickly get the library ID from the bundle ID for links which point to other libraries, build a map: bundle_uuids = set(link_data.bundle_uuid for link_data in links.values()) libraries_linked = { lib.bundle_uuid: lib for lib in ContentLibrary.objects.select_related('org').filter( bundle_uuid__in=bundle_uuids) } for link_name, link_data in links.items(): # Is this linked bundle a content library? try: opaque_key = libraries_linked[link_data.bundle_uuid].library_key except KeyError: opaque_key = None # Append the link information: results.append( LibraryBundleLink( id=link_name, bundle_uuid=link_data.bundle_uuid, version=link_data.version, latest_version=blockstore_cache.get_bundle_version_number( link_data.bundle_uuid), opaque_key=opaque_key, )) return results
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