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
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
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