Esempio n. 1
0
def load_block(usage_key, user):
    """
    Load the specified XBlock for the given user.

    Returns an instantiated XBlock.

    Exceptions:
        NotFound - if the XBlock doesn't exist or if the user doesn't have the
                   necessary permissions
    """
    # Is this block part of a course, a library, or what?
    # Get the Learning Context Implementation based on the usage key
    context_impl = get_learning_context_impl(usage_key)
    # Now, check if the block exists in this context and if the user has
    # permission to render this XBlock view:
    if user is not None and not context_impl.can_view_block(user, usage_key):
        # We do not know if the block was not found or if the user doesn't have
        # permission, but we want to return the same result in either case:
        raise NotFound(
            "XBlock {} does not exist, or you don't have permission to view it."
            .format(usage_key))

    # TODO: load field overrides from the context
    # e.g. a course might specify that all 'problem' XBlocks have 'max_attempts'
    # set to 3.
    # field_overrides = context_impl.get_field_overrides(usage_key)

    runtime = get_runtime_system().get_runtime(user=user)

    return runtime.get_block(usage_key)
Esempio n. 2
0
    def save_block(self, block):
        """
        Save any pending field data values to Blockstore.

        This gets called by block.save() - do not call this directly.
        """
        if not self.system.authored_data_store.has_changes(block):
            return  # No changes, so no action needed.
        definition_key = block.scope_ids.def_id
        if definition_key.draft_name is None:
            raise RuntimeError(
                "The Blockstore runtime does not support saving changes to blockstore without a draft. "
                "Are you making changes to UserScope.NONE fields from the LMS rather than Studio?"
            )
        # Verify that the user has permission to write to authored data in this
        # learning context:
        if self.user is not None:
            learning_context = get_learning_context_impl(block.scope_ids.usage_id)
            if not learning_context.can_edit_block(self.user, block.scope_ids.usage_id):
                log.warning("User %s does not have permission to edit %s", self.user.username, block.scope_ids.usage_id)
                raise RuntimeError("You do not have permission to edit this XBlock")
        olx_str, static_files = serialize_xblock(block)
        # Write the OLX file to the bundle:
        draft_uuid = blockstore_api.get_or_create_bundle_draft(
            definition_key.bundle_uuid, definition_key.draft_name
        ).uuid
        olx_path = definition_key.olx_path
        blockstore_api.write_draft_file(draft_uuid, olx_path, olx_str)
        # And the other files, if any:
        olx_static_path = os.path.dirname(olx_path) + '/static/'
        for fh in static_files:
            new_path = olx_static_path + fh.name
            blockstore_api.write_draft_file(draft_uuid, new_path, fh.data)
        # Now invalidate the blockstore data cache for the bundle:
        BundleCache(definition_key.bundle_uuid, draft_name=definition_key.draft_name).clear()
Esempio n. 3
0
def create_library_block(library_key, block_type, definition_id):
    """
    Create a new XBlock in this library of the specified type (e.g. "html").

    The 'definition_id' value (which should be a string like "problem1") will be
    used as both the definition_id and the usage_id.
    """
    assert isinstance(library_key, LibraryLocatorV2)
    ref = ContentLibrary.objects.get_by_key(library_key)
    # Make sure the proposed ID will be valid:
    validate_unicode_slug(definition_id)
    # Ensure the XBlock type is valid and installed:
    XBlock.load_class(block_type)  # Will raise an exception if invalid
    # Make sure the new ID is not taken already:
    new_usage_id = definition_id  # Since this is a top level XBlock, usage_id == definition_id
    usage_key = LibraryUsageLocatorV2(
        library_org=library_key.org,
        library_slug=library_key.slug,
        block_type=block_type,
        usage_id=new_usage_id,
    )
    library_context = get_learning_context_impl(usage_key)
    if library_context.definition_for_usage(usage_key) is not None:
        raise LibraryBlockAlreadyExists("An XBlock with ID '{}' already exists".format(new_usage_id))

    new_definition_xml = '<{}/>'.format(block_type)  # xss-lint: disable=python-wrap-html
    path = "{}/{}/definition.xml".format(block_type, definition_id)
    # Write the new XML/OLX file into the library bundle's draft
    draft = get_or_create_bundle_draft(ref.bundle_uuid, DRAFT_NAME)
    write_draft_file(draft.uuid, path, new_definition_xml)
    # Clear the bundle cache so everyone sees the new block immediately:
    BundleCache(ref.bundle_uuid, draft_name=DRAFT_NAME).clear()
    # Now return the metadata about the new block:
    return get_library_block(usage_key)
Esempio n. 4
0
def delete_library_block(usage_key, remove_from_parent=True):
    """
    Delete the specified block from this library (and any children it has).

    If the block's definition (OLX file) is within this same library as the
    usage key, both the definition and the usage will be deleted.

    If the usage points to a definition in a linked bundle, the usage will be
    deleted but the link and the linked bundle will be unaffected.

    If the block is in use by some other bundle that links to this one, that
    will not prevent deletion of the definition.

    remove_from_parent: modify the parent to remove the reference to this
        delete block. This should always be true except when this function
        calls itself recursively.
    """
    assert isinstance(usage_key, LibraryUsageLocatorV2)
    library_context = get_learning_context_impl(usage_key)
    library_ref = ContentLibrary.objects.get_by_key(usage_key.context_key)
    def_key = library_context.definition_for_usage(usage_key)
    if def_key is None:
        raise ContentLibraryBlockNotFound(usage_key)
    lib_bundle = LibraryBundle(usage_key.context_key,
                               library_ref.bundle_uuid,
                               draft_name=DRAFT_NAME)
    # Create a draft:
    draft_uuid = get_or_create_bundle_draft(def_key.bundle_uuid,
                                            DRAFT_NAME).uuid
    # Does this block have a parent?
    if usage_key not in lib_bundle.get_top_level_usages(
    ) and remove_from_parent:
        # Yes: this is not a top-level block.
        # First need to modify the parent to remove this block as a child.
        raise NotImplementedError
    # Does this block have children?
    block = load_block(usage_key, user=None)
    if block.has_children:
        # Next, recursively call delete_library_block(...) on each child usage
        for child_usage in block.children:
            # Specify remove_from_parent=False to avoid unnecessary work to
            # modify this block's children list when deleting each child, since
            # we're going to delete this block anyways.
            delete_library_block(child_usage, remove_from_parent=False)
    # Delete the definition:
    if def_key.bundle_uuid == library_ref.bundle_uuid:
        # This definition is in the library, so delete it:
        path_prefix = lib_bundle.olx_prefix(def_key)
        for bundle_file in get_bundle_files(def_key.bundle_uuid,
                                            use_draft=DRAFT_NAME):
            if bundle_file.path.startswith(path_prefix):
                # Delete this file, within this definition's "folder"
                write_draft_file(draft_uuid, bundle_file.path, contents=None)
    else:
        # The definition must be in a linked bundle, so we don't want to delete
        # it; just the <xblock-include /> in the parent, which was already
        # deleted above.
        pass
    # Clear the bundle cache so everyone sees the deleted block immediately:
    lib_bundle.cache.clear()
Esempio n. 5
0
def create_library_block(library_key, block_type, definition_id):
    """
    Create a new XBlock in this library of the specified type (e.g. "html").

    The 'definition_id' value (which should be a string like "problem1") will be
    used as both the definition_id and the usage_id.
    """
    assert isinstance(library_key, LibraryLocatorV2)
    ref = ContentLibrary.objects.get_by_key(library_key)
    if ref.type != COMPLEX:
        if block_type != ref.type:
            raise IncompatibleTypesError(
                _('Block type "{block_type}" is not compatible with library type "{library_type}".'
                  ).format(
                      block_type=block_type,
                      library_type=ref.type,
                  ))
    lib_bundle = LibraryBundle(library_key,
                               ref.bundle_uuid,
                               draft_name=DRAFT_NAME)
    # Total number of blocks should not exceed the maximum allowed
    total_blocks = len(lib_bundle.get_top_level_usages())
    if total_blocks + 1 > settings.MAX_BLOCKS_PER_CONTENT_LIBRARY:
        raise BlockLimitReachedError(
            _(u"Library cannot have more than {} XBlocks").format(
                settings.MAX_BLOCKS_PER_CONTENT_LIBRARY))
    # Make sure the proposed ID will be valid:
    validate_unicode_slug(definition_id)
    # Ensure the XBlock type is valid and installed:
    XBlock.load_class(block_type)  # Will raise an exception if invalid
    # Make sure the new ID is not taken already:
    new_usage_id = definition_id  # Since this is a top level XBlock, usage_id == definition_id
    usage_key = LibraryUsageLocatorV2(
        lib_key=library_key,
        block_type=block_type,
        usage_id=new_usage_id,
    )
    library_context = get_learning_context_impl(usage_key)
    if library_context.definition_for_usage(usage_key) is not None:
        raise LibraryBlockAlreadyExists(
            "An XBlock with ID '{}' already exists".format(new_usage_id))

    new_definition_xml = '<{}/>'.format(
        block_type)  # xss-lint: disable=python-wrap-html
    path = "{}/{}/definition.xml".format(block_type, definition_id)
    # Write the new XML/OLX file into the library bundle's draft
    draft = get_or_create_bundle_draft(ref.bundle_uuid, DRAFT_NAME)
    write_draft_file(draft.uuid, path, new_definition_xml.encode('utf-8'))
    # Clear the bundle cache so everyone sees the new block immediately:
    BundleCache(ref.bundle_uuid, draft_name=DRAFT_NAME).clear()
    # Now return the metadata about the new block:
    LIBRARY_BLOCK_CREATED.send(sender=None,
                               library_key=ref.library_key,
                               usage_key=usage_key)
    return get_library_block(usage_key)
Esempio n. 6
0
    def get_definition_id(self, usage_id):
        """Retrieve the definition that a usage is derived from.

        Args:
            usage_id: The id of the usage to query

        Returns:
            The `definition_id` the usage is derived from
        """
        if isinstance(usage_id, BlockUsageKeyV2):
            return get_learning_context_impl(usage_id).definition_for_usage(usage_id)
        raise TypeError("This version of get_definition_id doesn't support the given key type.")
Esempio n. 7
0
def resolve_definition(block_or_key):
    """
    Given an XBlock, definition key, or usage key, return the definition key.
    """
    if isinstance(block_or_key, BundleDefinitionLocator):
        return block_or_key
    elif isinstance(block_or_key, UsageKeyV2):
        context_impl = get_learning_context_impl(block_or_key)
        return context_impl.definition_for_usage(block_or_key)
    elif isinstance(block_or_key, XBlock):
        return block_or_key.scope_ids.def_id
    else:
        raise TypeError(block_or_key)
Esempio n. 8
0
def _lookup_usage_key(usage_key):
    """
    Given a LibraryUsageLocatorV2 (usage key for an XBlock in a content library)
    return the definition key and LibraryBundle
    or raise ContentLibraryBlockNotFound
    """
    assert isinstance(usage_key, LibraryUsageLocatorV2)
    lib_context = get_learning_context_impl(usage_key)
    def_key = lib_context.definition_for_usage(usage_key, force_draft=DRAFT_NAME)
    if def_key is None:
        raise ContentLibraryBlockNotFound(usage_key)
    lib_bundle = LibraryBundle(usage_key.lib_key, def_key.bundle_uuid, draft_name=DRAFT_NAME)
    return def_key, lib_bundle
 def add_child_include(self, block, parsed_include):
     """
     Given an XBlockInclude tuple that represents a new <xblock-include />
     node, add it as a child of the specified XBlock. This is the only
     supported API for adding a new child to an XBlock - one cannot just
     modify block.children to append a usage ID, since that doesn't provide
     enough information to serialize the block's <xblock-include /> elements.
     """
     learning_context = get_learning_context_impl(block.scope_ids.usage_id)
     child_usage_key = learning_context.usage_for_child_include(
         block.scope_ids.usage_id, block.scope_ids.def_id, parsed_include,
     )
     block.children.append(child_usage_key)
     self.child_includes_of(block).append(parsed_include)
 def add_node_as_child(self, block, node, id_generator=None):
     """
     This runtime API should normally be used via
     runtime.get_block() -> block.parse_xml() -> runtime.add_node_as_child
     """
     parent_usage = block.scope_ids.usage_id
     parent_definition = block.scope_ids.def_id
     learning_context = get_learning_context_impl(parent_usage)
     parsed_include = parse_xblock_include(node)
     usage_key = learning_context.usage_for_child_include(parent_usage, parent_definition, parsed_include)
     block.children.append(usage_key)
     if parent_definition.draft_name:
         # Store the <xblock-include /> data which we'll need later if saving changes to this block
         self.child_includes_of(block).append(parsed_include)
Esempio n. 11
0
    def definition_for_usage(self, usage_key, **kwargs):
        """
        Given a usage key for an XBlock in this context, 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.
        """
        try:
            original_usage_id = self._original_usage_id(usage_key, **kwargs)
        except Pathway.DoesNotExist:
            return None
        if not original_usage_id:
            return None
        original_learning_context = get_learning_context_impl(original_usage_id)
        return original_learning_context.definition_for_usage(original_usage_id)
Esempio n. 12
0
    def usage_for_child_include(self, parent_usage, parent_definition, parsed_include):
        """
        Method that the runtime uses when loading a block's child, to get the
        ID of the child.

        The child is always from an <xblock-include /> element.
        """
        assert isinstance(parent_usage, PathwayUsageLocator)
        # We need some kind of 'child_usage_id' part that can go into this child's
        orig_parent = self._original_usage_id(parent_usage)
        orig_learning_context = get_learning_context_impl(orig_parent)
        orig_usage_id = orig_learning_context.usage_for_child_include(orig_parent, parent_definition, parsed_include)
        return PathwayUsageLocator(
            pathway_key=parent_usage.pathway_key,
            block_type=parsed_include.block_type,
            usage_id=parent_usage.usage_id,  # This part of our pathway key is the same for all descendant blocks.
            child_usage_id=orig_usage_id.usage_id,
        )
Esempio n. 13
0
def get_library_block(usage_key):
    """
    Get metadata (LibraryXBlockMetadata) about one specific XBlock in a library

    To load the actual XBlock instance, use
        openedx.core.djangoapps.xblock.api.load_block()
    instead.
    """
    assert isinstance(usage_key, LibraryUsageLocatorV2)
    lib_context = get_learning_context_impl(usage_key)
    def_key = lib_context.definition_for_usage(usage_key)
    if def_key is None:
        raise ContentLibraryBlockNotFound(usage_key)
    lib_bundle = LibraryBundle(usage_key.library_slug, def_key.bundle_uuid, draft_name=DRAFT_NAME)
    return LibraryXBlockMetadata(
        usage_key=usage_key,
        def_key=def_key,
        display_name=get_block_display_name(def_key),
        has_unpublished_changes=lib_bundle.does_definition_have_unpublished_changes(def_key),
    )
    def get(self, block, name):
        """
        Get the "children' field value.

        We do this by reading the parsed <xblock-include /> values from
        the regular authored data store and then transforming them to usage IDs.
        """
        self._check_field(block, name)
        children_includes = self.get_includes(block)
        if not children_includes:
            return []
        # Now the .children field is required to be a list of usage IDs:
        learning_context = get_learning_context_impl(block.scope_ids.usage_id)
        child_usages = []
        for parsed_include in children_includes:
            child_usages.append(
                learning_context.usage_for_child_include(
                    block.scope_ids.usage_id, block.scope_ids.def_id, parsed_include,
                )
            )
        return child_usages