diff --git a/cms/djangoapps/contentstore/views/tests/test_block.py b/cms/djangoapps/contentstore/views/tests/test_block.py
index 49e08b6a9c..26b3f91a0b 100644
--- a/cms/djangoapps/contentstore/views/tests/test_block.py
+++ b/cms/djangoapps/contentstore/views/tests/test_block.py
@@ -2675,10 +2675,7 @@ class TestComponentTemplates(CourseTestCase):
XBlockStudioConfiguration.objects.create(
name="openassessment", enabled=True, support_level="us"
)
- # Library Sourced Block and Library Content block has it's own category.
- XBlockStudioConfiguration.objects.create(
- name="library_sourced", enabled=True, support_level="fs"
- )
+ # Library Content block has its own category.
XBlockStudioConfiguration.objects.create(
name="library_content", enabled=True, support_level="fs"
)
diff --git a/docs/decisions/0013-library-reference-content-block.rst b/docs/decisions/0013-library-reference-content-block.rst
index cd842d8b0f..3d8dcb2153 100644
--- a/docs/decisions/0013-library-reference-content-block.rst
+++ b/docs/decisions/0013-library-reference-content-block.rst
@@ -3,7 +3,13 @@ Referencing Content Blocks in Library V2
Status
=======
-Pending
+
+**Deferred** as of September 2023.
+
+The goals described in the ADR are still relevant to future development,
+but for the intial launch of Blockstore-backed content libraries,
+the existing ``library_content`` block will be used instead,
+and it will continue to copy blocks from Blockstore into Modulestore as necessary.
Context
=======
diff --git a/openedx/core/lib/xblock_utils/__init__.py b/openedx/core/lib/xblock_utils/__init__.py
index 0910018f60..26127dbfb3 100644
--- a/openedx/core/lib/xblock_utils/__init__.py
+++ b/openedx/core/lib/xblock_utils/__init__.py
@@ -452,7 +452,7 @@ def xblock_resource_pkg(block):
ProblemBlock, and most other built-in blocks currently. Handling for these
assets does not interact with this function.
2. The (preferred) standard XBlock runtime resource loading system, used by
- LibrarySourcedBlock. Handling for these assets *does* interact with this
+ LibraryContentBlock. Handling for these assets *does* interact with this
function.
We hope to migrate to (2) eventually, tracked by:
diff --git a/setup.py b/setup.py
index 17d0f07b01..f405f92a95 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,6 @@ XBLOCKS = [
"image = xmodule.template_block:TranslateCustomTagBlock",
"library = xmodule.library_root_xblock:LibraryRoot",
"library_content = xmodule.library_content_block:LibraryContentBlock",
- "library_sourced = xmodule.library_sourced_block:LibrarySourcedBlock",
"lti = xmodule.lti_block:LTIBlock",
"poll_question = xmodule.poll_block:PollBlock",
"problem = xmodule.capa_block:ProblemBlock",
diff --git a/webpack.common.config.js b/webpack.common.config.js
index 4a7bb7366b..13d368c559 100644
--- a/webpack.common.config.js
+++ b/webpack.common.config.js
@@ -77,7 +77,6 @@ module.exports = Merge.smart({
// Studio
Import: './cms/static/js/features/import/factories/import.js',
CourseOrLibraryListing: './cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx',
- LibrarySourcedBlockPicker: './xmodule/assets/library_source_block/LibrarySourcedBlockPicker.jsx', // eslint-disable-line max-len
'js/factories/textbooks': './cms/static/js/factories/textbooks.js',
'js/factories/container': './cms/static/js/factories/container.js',
'js/factories/context_course': './cms/static/js/factories/context_course.js',
diff --git a/xmodule/assets/README.rst b/xmodule/assets/README.rst
index a39b389a35..a697915db8 100644
--- a/xmodule/assets/README.rst
+++ b/xmodule/assets/README.rst
@@ -46,11 +46,6 @@ It is collected into the static root, and then linked to from XBlock fragments b
.. _annotatable/_display.scss: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/annotatable/_display.scss
.. _simplify things: https://github.com/openedx/edx-platform/issues/32621
-Static CSS (.css)
-*****************
-
-Non-themable, ready-to-seve CSS may also be added to the any block type's
-subdirectory. For example, see `library_source_block/style.css`_.
JavaScript (.js)
****************
@@ -72,7 +67,7 @@ Currently, edx-platform XBlock JS is defined both here in `xmodule/assets`_ and
* For other "purer" blocks, the JS is used as a standard XBlock fragment. Example blocks:
* `VerticalBlock`_
- * `LibrarySourcedBlock`_
+ * `LibraryContentBlock`_
As part of an `active build refactoring`_, we will soon consolidate all edx-platform XBlock JS here in `xmodule/assets`_.
@@ -82,7 +77,7 @@ As part of an `active build refactoring`_, we will soon consolidate all edx-plat
.. _HtmlBlock: https://github.com/openedx/edx-platform/blob/master/xmodule/html_block.py
.. _AnnotatableBlock: https://github.com/openedx/edx-platform/blob/master/xmodule/annotatable_block.py
.. _VerticalBlock: https://github.com/openedx/edx-platform/blob/master/xmodule/vertical_block.py
-.. _LibrarySourcedBlock: https://github.com/openedx/edx-platform/blob/master/xmodule/library_sourced_block.py
+.. _LibraryContentBlock: https://github.com/openedx/edx-platform/blob/master/xmodule/library_content_block.py
.. _active build refactoring: https://github.com/openedx/edx-platform/issues/31624
.. _builtin_assets.py: https://github.com/openedx/edx-platform/tree/master/xmodule/util/builtin_assets.py
.. _static_content.py: https://github.com/openedx/edx-platform/blob/master/xmodule/static_content.py
diff --git a/xmodule/assets/library_source_block/LibrarySourcedBlockPicker.jsx b/xmodule/assets/library_source_block/LibrarySourcedBlockPicker.jsx
deleted file mode 100644
index 0b6363893a..0000000000
--- a/xmodule/assets/library_source_block/LibrarySourcedBlockPicker.jsx
+++ /dev/null
@@ -1,236 +0,0 @@
-/* globals gettext */
-
-import 'whatwg-fetch';
-import PropTypes from 'prop-types';
-import React from 'react';
-import _ from 'underscore';
-import styles from './style.css';
-
-class LibrarySourcedBlockPicker extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- libraries: [],
- xblocks: [],
- // eslint-disable-next-line react/no-unused-state
- searchedLibrary: '',
- libraryLoading: false,
- xblocksLoading: false,
- selectedLibrary: undefined,
- selectedXblocks: new Set(this.props.selectedXblocks),
- };
- this.onLibrarySearchInput = this.onLibrarySearchInput.bind(this);
- this.onXBlockSearchInput = this.onXBlockSearchInput.bind(this);
- this.onLibrarySelected = this.onLibrarySelected.bind(this);
- this.onXblockSelected = this.onXblockSelected.bind(this);
- this.onDeleteClick = this.onDeleteClick.bind(this);
- }
-
- componentDidMount() {
- this.fetchLibraries();
- }
-
- // eslint-disable-next-line react/sort-comp
- fetchLibraries(textSearch = '', page = 1, append = false) {
- this.setState({
- // eslint-disable-next-line react/no-access-state-in-setstate
- libraries: append ? this.state.libraries : [],
- libraryLoading: true,
- }, async function() {
- try {
- let res = await fetch(`/api/libraries/v2/?pagination=true&page=${page}&text_search=${textSearch}`);
- res = await res.json();
- this.setState({
- // eslint-disable-next-line react/no-access-state-in-setstate
- libraries: this.state.libraries.concat(res.results),
- libraryLoading: false,
- }, () => {
- if (res.next) {
- this.fetchLibraries(textSearch, page + 1, true);
- }
- });
- } catch (error) {
- $('#library-sourced-block-picker').trigger('error', {
- title: 'Could not fetch library',
- message: error,
- });
- this.setState({
- libraries: [],
- libraryLoading: false,
- });
- }
- });
- }
-
- fetchXblocks(library, textSearch = '', page = 1, append = false) {
- this.setState({
- // eslint-disable-next-line react/no-access-state-in-setstate
- xblocks: append ? this.state.xblocks : [],
- xblocksLoading: true,
- }, async function() {
- try {
- let res = await fetch(`/api/libraries/v2/${library}/blocks/?pagination=true&page=${page}&text_search=${textSearch}`);
- res = await res.json();
- this.setState({
- // eslint-disable-next-line react/no-access-state-in-setstate
- xblocks: this.state.xblocks.concat(res.results),
- xblocksLoading: false,
- }, () => {
- if (res.next) {
- this.fetchXblocks(library, textSearch, page + 1, true);
- }
- });
- } catch (error) {
- $('#library-sourced-block-picker').trigger('error', {
- title: 'Could not fetch xblocks',
- message: error,
- });
- this.setState({
- xblocks: [],
- xblocksLoading: false,
- });
- }
- });
- }
-
- onLibrarySearchInput(event) {
- event.persist();
- this.setState({
- // eslint-disable-next-line react/no-unused-state
- searchedLibrary: event.target.value,
- });
- if (!this.debouncedFetchLibraries) {
- this.debouncedFetchLibraries = _.debounce(value => {
- this.fetchLibraries(value);
- }, 300);
- }
- this.debouncedFetchLibraries(event.target.value);
- }
-
- onXBlockSearchInput(event) {
- event.persist();
- if (!this.debouncedFetchXblocks) {
- this.debouncedFetchXblocks = _.debounce(value => {
- this.fetchXblocks(this.state.selectedLibrary, value);
- }, 300);
- }
- this.debouncedFetchXblocks(event.target.value);
- }
-
- onLibrarySelected(event) {
- this.setState({
- selectedLibrary: event.target.value,
- });
- this.fetchXblocks(event.target.value);
- }
-
- onXblockSelected(event) {
- // eslint-disable-next-line prefer-const, react/no-access-state-in-setstate
- let state = new Set(this.state.selectedXblocks);
- if (event.target.checked) {
- state.add(event.target.value);
- } else {
- state.delete(event.target.value);
- }
- this.setState({
- selectedXblocks: state,
- }, this.updateList);
- }
-
- onDeleteClick(event) {
- let value;
- if (event.target.tagName == 'SPAN') {
- value = event.target.parentElement.dataset.value;
- } else {
- value = event.target.dataset.value;
- }
- // eslint-disable-next-line prefer-const, react/no-access-state-in-setstate
- let state = new Set(this.state.selectedXblocks);
- state.delete(value);
- this.setState({
- selectedXblocks: state,
- }, this.updateList);
- }
-
- updateList(list) {
- $('#library-sourced-block-picker').trigger('selected-xblocks', {
- sourceBlockIds: Array.from(this.state.selectedXblocks),
- });
- }
-
- render() {
- return (
-
-
-
-
-
- {/* eslint-disable-next-line react/no-unescaped-entities */}
- Hitting 'Save and Import' will import the latest versions of the selected blocks, overwriting any changes done to this block post-import.
-
-
- );
- }
-}
-
-LibrarySourcedBlockPicker.propTypes = {
- // eslint-disable-next-line react/forbid-prop-types
- selectedXblocks: PropTypes.array,
-};
-
-LibrarySourcedBlockPicker.defaultProps = {
- selectedXblocks: [],
-};
-
-export {LibrarySourcedBlockPicker}; // eslint-disable-line import/prefer-default-export
diff --git a/xmodule/assets/library_source_block/public/js/library_source_block.js b/xmodule/assets/library_source_block/public/js/library_source_block.js
deleted file mode 100644
index 9674080d59..0000000000
--- a/xmodule/assets/library_source_block/public/js/library_source_block.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/* JavaScript for allowing editing options on LibrarySourceBlock's studio view */
-window.LibrarySourceBlockStudioView = function(runtime, element) {
- 'use strict';
- var self = this;
-
- $('#library-sourced-block-picker', element).on('selected-xblocks', function(e, params) {
- self.sourceBlockIds = params.sourceBlockIds;
- });
-
- $('#library-sourced-block-picker', element).on('error', function(e, params) {
- runtime.notify('error', {title: gettext(params.title), message: params.message});
- });
-
- $('.save-button', element).on('click', function(e) {
- e.preventDefault();
- var url = $(e.target).data('submit-url');
- var data = {
- values: {
- source_block_ids: self.sourceBlockIds
- },
- defaults: ['display_name']
- };
-
- runtime.notify('save', {
- state: 'start',
- message: gettext('Saving'),
- element: element
- });
- $.ajax({
- type: 'POST',
- url: url,
- data: JSON.stringify(data),
- global: false // Disable error handling that conflicts with studio's notify('save') and notify('cancel')
- }).done(function() {
- runtime.notify('save', {
- state: 'end',
- element: element
- });
- }).fail(function(jqXHR) {
- var message = gettext('This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.'); // eslint-disable-line max-len
- if (jqXHR.responseText) { // Is there a more specific error message we can show?
- try {
- message = JSON.parse(jqXHR.responseText).error;
- if (typeof message === 'object' && message.messages) {
- // e.g. {"error": {"messages": [{"text": "Unknown user 'bob'!", "type": "error"}, ...]}} etc.
- message = $.map(message.messages, function(msg) { return msg.text; }).join(', ');
- }
- } catch (error) { message = jqXHR.responseText.substr(0, 300); }
- }
- runtime.notify('error', {title: gettext('Unable to update settings'), message: message});
- });
- });
-
- $('.cancel-button', element).on('click', function(e) {
- e.preventDefault();
- runtime.notify('cancel', {});
- });
-};
diff --git a/xmodule/assets/library_source_block/style.css b/xmodule/assets/library_source_block/style.css
deleted file mode 100644
index 4892f20405..0000000000
--- a/xmodule/assets/library_source_block/style.css
+++ /dev/null
@@ -1,58 +0,0 @@
-.column {
- display: flex;
- flex-direction: column;
- margin: 10px;
- max-width: 300px;
- flex-grow: 1;
-}
-.elementList {
- margin-top: 10px;
-}
-input.search {
- width: 100% !important;
- height: auto !important;
-}
-.element > input[type='checkbox'],
-.element > input[type='radio'] {
- position: absolute;
- width: 0 !important;
- height: 0 !important;
- top: -9999px;
-}
-.element > .elementItem {
- display: flex;
- flex-grow: 1;
- padding: 0.625rem 1.25rem;
- border: 1px solid rgba(0, 0, 0, 0.25);
-}
-.element + .element > label {
- border-top: 0;
-}
-.element > input[type='checkbox']:focus + label,
-.element > input[type='radio']:focus + label,
-.element > input:hover + label {
- background: #f6f6f7;
- cursor: pointer;
-}
-.element > input:checked + label {
- background: #23419f;
- color: #fff;
-}
-.element > input[type='checkbox']:checked:focus + label,
-.element > input[type='radio']:checked:focus + label,
-.element > input:checked:hover + label {
- background: #193787;
- cursor: pointer;
-}
-.selectedBlocks {
- padding: 12px 8px 20px;
-}
-button.remove {
- background: #e00;
- color: #fff;
- border: solid rgba(0,0,0,0.25) 1px;
-}
-button.remove:focus,
-button.remove:hover {
- background: #d00;
-}
diff --git a/xmodule/library_sourced_block.py b/xmodule/library_sourced_block.py
deleted file mode 100644
index d96741d6e4..0000000000
--- a/xmodule/library_sourced_block.py
+++ /dev/null
@@ -1,159 +0,0 @@
-"""
-Library Sourced Content XBlock
-"""
-import logging
-
-from copy import copy
-from mako.template import Template as MakoTemplate
-from xblock.core import XBlock
-from xblock.fields import Scope, String, List
-from xblock.validation import ValidationMessage
-from xblockutils.resources import ResourceLoader
-from xblockutils.studio_editable import StudioEditableXBlockMixin
-from webob import Response
-from web_fragments.fragment import Fragment
-
-from xmodule.studio_editable import StudioEditableBlock as EditableChildrenMixin
-from xmodule.validation import StudioValidation, StudioValidationMessage
-
-log = logging.getLogger(__name__)
-loader = ResourceLoader(__name__)
-
-# Make '_' a no-op so we can scrape strings. Using lambda instead of
-# `django.utils.translation.ugettext_noop` because Django cannot be imported in this file
-_ = lambda text: text
-
-
-@XBlock.wants('library_tools') # Only needed in studio
-class LibrarySourcedBlock(StudioEditableXBlockMixin, EditableChildrenMixin, XBlock):
- """
- Library Sourced Content XBlock
-
- Allows copying specific XBlocks from a Blockstore-based content library into
- a modulestore-based course. The selected blocks are copied and become
- children of this block.
-
- When we implement support for Blockstore-based courses, it's expected we'll
- use a different mechanism for importing library content into a course.
- """
- display_name = String(
- help=_("The display name for this component."),
- default="Library Sourced Content",
- display_name=_("Display Name"),
- scope=Scope.content,
- )
- source_block_ids = List(
- display_name=_("Library Blocks List"),
- help=_("Enter the IDs of the library XBlocks that you wish to use."),
- scope=Scope.content,
- )
- editable_fields = ("display_name", "source_block_ids")
- has_children = True
- has_author_view = True
- resources_dir = 'assets/library_source_block'
- MAX_BLOCKS_ALLOWED = 10
-
- def __str__(self):
- return f"LibrarySourcedBlock: {self.display_name}"
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- if not self.source_block_ids:
- self.has_children = False
-
- def studio_view(self, context):
- """
- Render a form for editing this XBlock
- """
- fragment = Fragment()
- static_content = ResourceLoader('common.djangoapps.pipeline_mako').load_unicode('templates/static_content.html')
- render_react = MakoTemplate(static_content, default_filters=[]).get_def('renderReact')
- react_content = render_react.render(
- component="LibrarySourcedBlockPicker",
- id="library-sourced-block-picker",
- props={
- 'selectedXblocks': self.source_block_ids,
- }
- )
- fragment.content = loader.render_django_template('templates/library-sourced-block-studio-view.html', {
- 'react_content': react_content,
- 'save_url': self.runtime.handler_url(self, 'submit_studio_edits'),
- })
-
- fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/library_source_block.js'))
- fragment.initialize_js('LibrarySourceBlockStudioView')
-
- return fragment
-
- def author_view(self, context):
- """
- Renders the Studio preview view.
- """
- fragment = Fragment()
- context = {} if not context else copy(context) # Isolate context - without this there are weird bugs in Studio
- # EditableChildrenMixin.render_children will render HTML that allows instructors to make edits to the children
- context['can_move'] = False
- self.render_children(context, fragment, can_reorder=False, can_add=False)
- return fragment
-
- def student_view(self, context):
- """
- Renders the view that learners see.
- """
- result = Fragment()
- child_frags = self.runtime.render_children(self, context=context)
- result.add_resources(child_frags)
- result.add_content('
')
- for frag in child_frags:
- result.add_content(frag.content)
- result.add_content('
')
- return result
-
- def validate_field_data(self, validation, data):
- """
- Validate this block's field data. Instead of checking fields like self.name, check the
- fields set on data, e.g. data.name. This allows the same validation method to be re-used
- for the studio editor.
- """
- if len(data.source_block_ids) > self.MAX_BLOCKS_ALLOWED:
- # Because importing library blocks is an expensive operation
- validation.add(
- ValidationMessage(
- ValidationMessage.ERROR,
- _("A maximum of {0} components may be added.").format(self.MAX_BLOCKS_ALLOWED)
- )
- )
-
- def validate(self):
- """
- Validates the state of this library_sourced_xblock Instance. This is the override of the general XBlock method,
- and it will also ask its superclass to validate.
- """
- validation = super().validate()
- validation = StudioValidation.copy(validation)
-
- if not self.source_block_ids:
- validation.set_summary(
- StudioValidationMessage(
- StudioValidationMessage.NOT_CONFIGURED,
- _("No XBlock has been configured for this component. Use the editor to select the target blocks."),
- action_class='edit-button',
- action_label=_("Open Editor")
- )
- )
- return validation
-
- @XBlock.handler
- def submit_studio_edits(self, data, suffix=''):
- """
- Save changes to this block, applying edits made in Studio.
- """
- response = super().submit_studio_edits(data, suffix)
- # Replace our current children with the latest ones from the libraries.
- lib_tools = self.runtime.service(self, 'library_tools')
- try:
- lib_tools.import_from_blockstore(self, self.source_block_ids)
- except Exception as err: # pylint: disable=broad-except
- log.exception(err)
- return Response(_("Importing Library Block failed - are the IDs valid and readable?"), status=400)
- return response
diff --git a/xmodule/library_tools.py b/xmodule/library_tools.py
index 58cd821241..748f1b58ac 100644
--- a/xmodule/library_tools.py
+++ b/xmodule/library_tools.py
@@ -196,7 +196,7 @@ class LibraryToolsService:
content library) into modulestore, as a new child of dest_block.
Any existing children of dest_block are replaced.
- This is only used by LibrarySourcedBlock. It should verify first that
+ This is only used by LibraryContentBlock. It should verify first that
the number of block IDs is reasonable.
"""
dest_key = dest_block.scope_ids.usage_id
@@ -216,7 +216,7 @@ class LibraryToolsService:
raise PermissionDenied()
# Read the source block; this will also confirm that user has permission to read it.
- # (This could be slow and use lots of memory, except for the fact that LibrarySourcedBlock which calls this
+ # (This could be slow and use lots of memory, except for the fact that LibraryContentBlock which calls this
# should be limiting the number of blocks to a reasonable limit. We load them all now instead of one at a
# time in order to raise any errors before we start actually copying blocks over.)
orig_blocks = [load_block(UsageKey.from_string(key), user) for key in blockstore_block_ids]
diff --git a/xmodule/templates/library-sourced-block-studio-view.html b/xmodule/templates/library-sourced-block-studio-view.html
deleted file mode 100644
index 08a2882b51..0000000000
--- a/xmodule/templates/library-sourced-block-studio-view.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% load i18n %}
-