Пример #1
0
    def _lookup_asset_url(self, block, asset_path):
        """
        Return an absolute URL for the specified static asset file that may
        belong to this XBlock.

        e.g. if the XBlock settings have a field value like "/static/foo.png"
        then this method will be called with asset_path="foo.png" and should
        return a URL like https://cdn.none/xblock/f843u89789/static/foo.png

        If the asset file is not recognized, return None
        """
        if '..' in asset_path:
            return None  # Illegal path
        definition_key = block.scope_ids.def_id
        # Compute the full path to the static file in the bundle,
        # e.g. "problem/prob1/static/illustration.svg"
        expanded_path = os.path.dirname(definition_key.olx_path) + '/static/' + asset_path
        try:
            metadata = get_bundle_file_metadata_with_cache(
                bundle_uuid=definition_key.bundle_uuid,
                path=expanded_path,
                bundle_version=definition_key.bundle_version,
                draft_name=definition_key.draft_name,
            )
        except blockstore_api.BundleFileNotFound:
            log.warning("XBlock static file not found: %s for %s", asset_path, block.scope_ids.usage_id)
            return None
        # Make sure the URL is one that will work from the user's browser,
        # not one that only works from within a docker container:
        url = blockstore_api.force_browser_url(metadata.url)
        return url
Пример #2
0
 def to_representation(self, instance):
     """
     Generate the serialized representation of this static asset file.
     """
     result = super(LibraryXBlockStaticFileSerializer, self).to_representation(instance)
     # Make sure the URL is one that will work from the user's browser,
     # not one that only works from within a docker container:
     result['url'] = blockstore_api.force_browser_url(result['url'])
     return result
Пример #3
0
    def _import_block(self, source_block, dest_parent_key):
        """
        Recursively import a blockstore block and its children. See import_from_blockstore above.
        """
        def generate_block_key(source_key, dest_parent_key):
            """
            Deterministically generate an ID for the new block and return the key
            """
            block_id = (
                dest_parent_key.block_id[:10] +
                '-' +
                hashlib.sha1(str(source_key).encode('utf-8')).hexdigest()[:10]
            )
            return dest_parent_key.context_key.make_usage_key(source_key.block_type, block_id)

        source_key = source_block.scope_ids.usage_id
        new_block_key = generate_block_key(source_key, dest_parent_key)
        try:
            new_block = self.store.get_item(new_block_key)
            if new_block.parent != dest_parent_key:
                raise ValueError(
                    "Expected existing block {} to be a child of {} but instead it's a child of {}".format(
                        new_block_key, dest_parent_key, new_block.parent,
                    )
                )
        except ItemNotFoundError:
            new_block = self.store.create_child(
                user_id=self.user_id,
                parent_usage_key=dest_parent_key,
                block_type=source_key.block_type,
                block_id=new_block_key.block_id,
            )

        # Prepare a list of this block's static assets; any assets that are referenced as /static/{path} (the
        # recommended way for referencing them) will stop working, and so we rewrite the url when importing.
        # Copying assets not advised because modulestore doesn't namespace assets to each block like blockstore, which
        # might cause conflicts when the same filename is used across imported blocks.
        if isinstance(source_key, LibraryUsageLocatorV2):
            all_assets = library_api.get_library_block_static_asset_files(source_key)
        else:
            all_assets = []

        for field_name, field in source_block.fields.items():
            if field.scope not in (Scope.settings, Scope.content):
                continue  # Only copy authored field data
            if field.is_set_on(source_block) or field.is_set_on(new_block):
                field_value = getattr(source_block, field_name)
                if isinstance(field_value, str):
                    # If string field (which may also be JSON/XML data), rewrite /static/... URLs to point to blockstore
                    for asset in all_assets:
                        field_value = field_value.replace('/static/{}'.format(asset.path), asset.url)
                        # Make sure the URL is one that will work from the user's browser when using the docker devstack
                        field_value = blockstore_api.force_browser_url(field_value)
                setattr(new_block, field_name, field_value)
        new_block.save()
        self.store.update_item(new_block, self.user_id)

        if new_block.has_children:
            # Delete existing children in the new block, which can be reimported again if they still exist in the
            # source library
            for existing_child_key in new_block.children:
                self.store.delete_item(existing_child_key, self.user_id)
            # Now import the children
            for child in source_block.get_children():
                self._import_block(child, new_block_key)

        return new_block_key