Example #1
0
    def has_changes(self):
        """
        Helper method to check if this OLX bundle has any pending changes,
        including any deleted blocks.

        Returns a tuple of (
            has_unpublished_changes,
            has_unpublished_deletes,
        )
        Where has_unpublished_changes is true if there is any type of change,
        including deletes, and has_unpublished_deletes is only true if one or
        more blocks has been deleted since the last publish.
        """
        if not self.draft_name:
            return (False, False)
        cached_result = self.cache.get(('has_changes', ))
        if cached_result is not None:
            return cached_result
        draft_files = get_bundle_files_cached(self.bundle_uuid,
                                              draft_name=self.draft_name)

        has_unpublished_changes = False
        has_unpublished_deletes = False

        for file_ in draft_files:
            if getattr(file_, 'modified', False):
                has_unpublished_changes = True
                break

        if not has_unpublished_changes:
            # Check if any links have changed:
            old_links = set(
                get_bundle_direct_links_with_cache(self.bundle_uuid).items())
            new_links = set(
                get_bundle_direct_links_with_cache(
                    self.bundle_uuid, draft_name=self.draft_name).items())
            has_unpublished_changes = new_links != old_links

        published_file_paths = {
            f.path
            for f in get_bundle_files_cached(self.bundle_uuid)
        }
        draft_file_paths = {f.path for f in draft_files}
        for file_path in published_file_paths:
            if file_path not in draft_file_paths:
                has_unpublished_changes = True
                if file_path.endswith('/definition.xml'):
                    # only set 'has_unpublished_deletes' if the actual main definition XML
                    # file was deleted, not if only some asset file was deleted, etc.
                    has_unpublished_deletes = True
                    break

        result = (has_unpublished_changes, has_unpublished_deletes)
        self.cache.set(('has_changes', ), result)
        return result
Example #2
0
    def get_olx_files(self):
        """
        Get the list of OLX files in this bundle (using a heuristic)

        Because this uses a heuristic, it will only return files with filenames
        that seem like OLX files that are in the expected locations of OLX
        files. They are not guaranteed to be valid OLX nor will OLX files in
        nonstandard locations be returned.

        Example return value: [
            'html/intro/definition.xml',
            'unit/unit1/definition.xml',
        ]
        """
        bundle_files = get_bundle_files_cached(self.bundle_uuid, draft_name=self.draft_name)
        return [f.path for f in bundle_files if f.path.endswith("/definition.xml")]
Example #3
0
    def get_static_files_for_definition(self, definition_key):
        """
        Return a list of the static asset files related with a particular XBlock
        definition.

        If the bundle contains files like:
            problem/quiz1/definition.xml
            problem/quiz1/static/image1.png
        Then this will return
            [BundleFile(path="image1.png", size, url, hash_digest)]
        """
        path_prefix = self.get_static_prefix_for_definition(definition_key)
        path_prefix_len = len(path_prefix)
        return [
            blockstore_api.BundleFile(path=f.path[path_prefix_len:], size=f.size, url=f.url, hash_digest=f.hash_digest)
            for f in get_bundle_files_cached(self.bundle_uuid, draft_name=self.draft_name)
            if f.path.startswith(path_prefix)
        ]
Example #4
0
 def _get_changed_definitions(self):
     """
     Helper method to get a list of all paths with changes, where a path is
         problem/quiz1/
     Or similar (a type and an ID), excluding 'definition.xml'
     """
     cached_result = self.cache.get(('changed_definition_prefixes', ))
     if cached_result is not None:
         return cached_result
     changed = []
     bundle_files = get_bundle_files_cached(self.bundle_uuid, draft_name=self.draft_name)
     for file_ in bundle_files:
         if getattr(file_, 'modified', False) and file_.path.count('/') >= 2:
             (type_part, id_part, _rest) = file_.path.split('/', 2)
             prefix = type_part + '/' + id_part + '/'
             if prefix not in changed:
                 changed.append(prefix)
     self.cache.set(('changed_definition_prefixes', ), changed)
     return changed
Example #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