示例#1
0
 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
示例#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 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
示例#4
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