Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provisionally support V2 libraries in LibraryContentBlock (randomized only) #33263

Merged
merged 78 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
d81d774
feat!: remove LibrarySourcedBlock
kdmccormick Sep 14, 2023
9eee194
feat: support V2 libraries in LibraryContentBlock (randomized only)
kdmccormick Sep 14, 2023
258216b
Merge branch 'master' into kdmccormick/library-content-v2
kdmccormick Sep 15, 2023
ef54bd6
fix: user tasks out of lms, move tasks.py
connorhaugh Sep 18, 2023
9b0b722
Merge commit 'refs/pull/33263/head' of github.com:openedx/edx-platfor…
connorhaugh Sep 18, 2023
1daf629
fix: lint
connorhaugh Sep 19, 2023
982abc7
fix: lint
connorhaugh Sep 19, 2023
0dbe410
fix: lint fix
connorhaugh Sep 19, 2023
3299cef
fix: move codeowner attribute to layer
connorhaugh Sep 19, 2023
109a33c
fix: remove codeowner attribute from update_children_task
kdmccormick Oct 5, 2023
4b4a34e
build: put back set_code_owner_attribute but as a regular function call
kdmccormick Oct 5, 2023
9b2f0f0
feat: add cms-only assertions to content_libraries tasks
kdmccormick Oct 5, 2023
0f730f8
Merge branch 'master' into kdmccormick/library-content-v2
kdmccormick Oct 17, 2023
fccbaa4
style: fix indentation
kdmccormick Oct 17, 2023
6dcb047
fix: add missing imports for _assert_cms
kdmccormick Oct 17, 2023
f2230db
docs: remove outdated is_v2_lib from function docstring
kdmccormick Oct 17, 2023
e25e2a3
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 18, 2023
f1370d0
test: run ./xmodule/ LibraryContentTests only in CMS
kdmccormick Oct 18, 2023
8fe86a1
test: run ./xmodule/ tests with CMS settings
kdmccormick Oct 18, 2023
71cde02
Revert "test: run ./xmodule/ LibraryContentTests only in CMS"
kdmccormick Oct 18, 2023
9f9c047
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 18, 2023
42e82ba
Merge branch 'kdmccormick/test-xmodule-with-cms' into kdmccormick/lib…
kdmccormick Oct 18, 2023
3a25800
test: run ./xmodule/ LibraryContentTests only in CMS
kdmccormick Oct 18, 2023
8ac429a
style: remove duplicate imports fomr test_lib_tools
kdmccormick Oct 18, 2023
3cf5fc3
test: fix patching in test_list_available_libraries
kdmccormick Oct 18, 2023
caecac1
test: attempt at stricter enforcement of library tools in cms only
kdmccormick Oct 18, 2023
ae73343
Merge branch 'master' into kdmccormick/library-content-v2
kdmccormick Oct 19, 2023
2241ca5
temp: fix: put library_tools back in LMS, at least for now
kdmccormick Oct 19, 2023
4379f0f
test: fix skip_unless_[lms|cms]
kdmccormick Oct 19, 2023
e911056
test: fix library_content completion test w/ settings hack
kdmccormick Oct 19, 2023
1fadf7a
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 19, 2023
0809ad4
test: update test patch for new libraries tasks location
kdmccormick Oct 19, 2023
22cb7d5
test: use string mock to satisfy importlinter
kdmccormick Oct 19, 2023
2609a43
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 19, 2023
9c23889
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 23, 2023
0cf735f
fix: getting status of library_content's update_children task
kdmccormick Oct 24, 2023
3dfa080
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 24, 2023
535fb52
fix: squash: _are_children_updating utiltity func
kdmccormick Oct 24, 2023
2c69e81
Revert "fix: squash: _are_children_updating utiltity func"
kdmccormick Oct 24, 2023
781cdf5
Revert "fix: getting status of library_content's update_children task"
kdmccormick Oct 24, 2023
b9fc30d
fix: getting status of library_content's update_children task
kdmccormick Oct 24, 2023
c609b0b
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 26, 2023
35db46c
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Oct 27, 2023
455424e
Merge branch 'master' into kdmccormick/library-content-v2
kdmccormick Oct 30, 2023
6666243
refactor: delete unused library_tools code; rename update_children
kdmccormick Oct 31, 2023
65e9599
test: update unit tests for previous refactor commit
kdmccormick Oct 31, 2023
3d9b305
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 2, 2023
e96f8c8
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 3, 2023
d8baa20
fix: duplicate library_content children asynchronously (#33652)
kdmccormick Nov 6, 2023
c44d361
fix: bad user_id kwarg in call to trigger_duplicate_children
kdmccormick Nov 6, 2023
74f711f
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 6, 2023
9dfa9ef
test: reorder library_content test_duplicate_version assertions for b…
kdmccormick Nov 6, 2023
0e3cd00
fix: various fixes relating to library version awareness (WIP)
kdmccormick Nov 6, 2023
2298803
fix: fail better when library_tools is missing
kdmccormick Nov 8, 2023
3ab2d2a
refactor: rename to be clearer when we're upgrading lib versions vs j…
kdmccormick Nov 8, 2023
2f0bac1
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 8, 2023
8432225
test: change 'refresh' to 'reselect' in library_content test name
kdmccormick Nov 8, 2023
7a40ee4
fix: differentiate upgrade_and_sync http handler from its internal me…
kdmccormick Nov 8, 2023
eff235a
fix: make get_latest_library_version always return str
kdmccormick Nov 8, 2023
82a8b42
fix: use get_tools() consistently. also, turn upgrade_to_latest into …
kdmccormick Nov 8, 2023
54e12ad
test(libraries): only sync when necessary, and only upgrade when nece…
kdmccormick Nov 8, 2023
776e2bd
fix: don't stringify lib version when it's None
kdmccormick Nov 8, 2023
2a5c85e
fix: lib version type annoations; also remove extraneous update_items
kdmccormick Nov 8, 2023
f7d2970
test: fix setup for import/export tests
kdmccormick Nov 13, 2023
e0c5d27
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 13, 2023
2fe32eb
fix: post_editor_saved should only apply when source lib or capa type…
kdmccormick Nov 15, 2023
6398c35
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 15, 2023
0d01595
fix: correct post_editor saved (wip - test still failing
kdmccormick Nov 15, 2023
195bd83
fix: return 400 (not 500) when upgrade_and_sync called with bad lib key
kdmccormick Nov 16, 2023
aeb8d74
test: consolidate & fix test cases around nonexistent source libraries
kdmccormick Nov 16, 2023
5b603b6
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 16, 2023
c0c5456
fix: typos in last two commits
kdmccormick Nov 16, 2023
1dba7c6
test: test_duplicate_library_content_block
kdmccormick Nov 17, 2023
c9459ef
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 17, 2023
671dd83
fix: import shouldn't fail if lib doesn't exist
kdmccormick Nov 17, 2023
03fc0dc
style: ignore pylint violation
kdmccormick Nov 20, 2023
f4ea5f2
refactor: undo accidental test case rename
kdmccormick Nov 20, 2023
10da278
Merge remote-tracking branch 'upstream/master' into kdmccormick/libra…
kdmccormick Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 84 additions & 63 deletions cms/djangoapps/contentstore/tests/test_libraries.py

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions cms/djangoapps/contentstore/views/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ def container_handler(request, usage_key_string):

# Get the status of the user's clipboard so they can paste components if they have something to paste
user_clipboard = content_staging_api.get_user_clipboard_json(request.user.id, request)
library_block_types = [problem_type['component'] for problem_type in LIBRARY_BLOCK_TYPES]
is_library_xblock = xblock.location.block_type in library_block_types

return render_to_response('container.html', {
'language_code': request.LANGUAGE_CODE,
'context_course': course, # Needed only for display of menus at top of page.
Expand All @@ -203,6 +206,7 @@ def container_handler(request, usage_key_string):
'xblock_locator': xblock.location,
'unit': unit,
'is_unit_page': is_unit_page,
'is_collapsible': is_library_xblock,
'subsection': subsection,
'section': section,
'position': index,
Expand All @@ -218,6 +222,7 @@ def container_handler(request, usage_key_string):
'templates': CONTAINER_TEMPLATES,
# Status of the user's clipboard, exactly as would be returned from the "GET clipboard" REST API.
'user_clipboard': user_clipboard,
'is_fullwidth_content': is_library_xblock,
})
else:
return HttpResponseBadRequest("Only supports HTML requests")
Expand Down
6 changes: 5 additions & 1 deletion cms/djangoapps/contentstore/views/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
wrap_xblock_aside
)

from ..utils import get_visibility_partition_info
from ..utils import get_visibility_partition_info, StudioPermissionsService
from .access import get_user_role
from .session_kv_store import SessionKeyValueStore

Expand Down Expand Up @@ -198,6 +198,7 @@ def _prepare_runtime_for_preview(request, block):
deprecated_anonymous_user_id = anonymous_id_for_user(request.user, None)

services = {
"studio_user_permissions": StudioPermissionsService(request.user),
"i18n": XBlockI18nService,
'mako': mako_service,
"settings": SettingsService(),
Expand Down Expand Up @@ -310,6 +311,9 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'is_reorderable': is_reorderable,
'can_edit': can_edit,
'can_edit_visibility': context.get('can_edit_visibility', is_course),
'is_loading': context.get('is_loading', False),
'is_selected': context.get('is_selected', False),
'selectable': context.get('selectable', False),
'selected_groups_label': selected_groups_label,
'can_add': context.get('can_add', True),
'can_move': context.get('can_move', is_course),
Expand Down
133 changes: 131 additions & 2 deletions cms/djangoapps/contentstore/views/tests/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def test_get_empty_container_fragment(self):
self.assertNotRegex(html, r"wrapper-xblock[^-]+")

# Verify that the header and article tags are still added
self.assertIn('<header class="xblock-header xblock-header-vertical">', html)
self.assertIn('<header class="xblock-header xblock-header-vertical ">', html)
self.assertIn('<article class="xblock-render">', html)

def test_get_container_fragment(self):
Expand All @@ -233,7 +233,7 @@ def test_get_container_fragment(self):

# Verify that the Studio nesting wrapper has been added
self.assertIn("level-nesting", html)
self.assertIn('<header class="xblock-header xblock-header-vertical">', html)
self.assertIn('<header class="xblock-header xblock-header-vertical ">', html)
self.assertIn('<article class="xblock-render">', html)

# Verify that the Studio element wrapper has been added
Expand Down Expand Up @@ -950,6 +950,135 @@ def test_shallow_duplicate(self):
self.assertEqual(len(destination_chapter.get_children()), 0)
self.assertEqual(destination_chapter.display_name, "Source Chapter")

def test_duplicate_library_content_block(self): # pylint: disable=too-many-statements
"""
Test the LibraryContentBlock's special duplication process.
"""
store = modulestore()

# Create a library with two blocks (HTML 1 and HTML 2).
# These are the "Original" version of the blocks.
lib = LibraryFactory()
BlockFactory(
parent=lib,
category="html",
display_name="HTML 1 Title (Original)",
data="HTML 1 Content (Original)",
publish_item=False,
)
BlockFactory(
parent=lib,
category="html",
display_name="HTML 2 Title (Original)",
data="HTML 2 Content (Original)",
publish_item=False,
)
original_lib_version = store.get_library(
lib.location.library_key, remove_version=False, remove_branch=False,
).location.library_key.version_guid
assert original_lib_version is not None

# Create a library content block (lc), point it out our library, and sync it.
unit = BlockFactory(
parent_location=self.seq_usage_key,
category="vertical",
display_name="Parent Unit of LC and its Dupe",
publish_item=False,
)
lc = BlockFactory(
parent=unit,
category="library_content",
source_library_id=str(lib.location.library_key),
display_name="LC Block",
max_count=1,
publish_item=False,
)
lc.sync_from_library()
lc = store.get_item(lc.location) # we must reload because sync_from_library happens out-of-thread
assert lc.source_library_version == str(original_lib_version)
lc_html_1 = store.get_item(lc.children[0])
lc_html_2 = store.get_item(lc.children[1])
assert lc_html_1.display_name == "HTML 1 Title (Original)"
assert lc_html_2.display_name == "HTML 2 Title (Original)"
assert lc_html_1.data == "HTML 1 Content (Original)"
assert lc_html_2.data == "HTML 2 Content (Original)"

# Override the title and data of HTML 1 under lc ("Course Override").
# Note that title is settings-scoped and data is content-scoped.
lc_html_1.display_name = "HTML 1 Title (Course Override)"
lc_html_1.data = "HTML 1 Content (Course Override)"
store.update_item(lc_html_1, self.user.id)

# Now, update the titles and contents of both HTML 1 and HTML 2 ("Lib Update").
# This will yield a new version of the library (updated_lib_version).
lib_html_1 = store.get_item(lib.children[0])
lib_html_2 = store.get_item(lib.children[1])
assert lib_html_1.display_name == "HTML 1 Title (Original)"
assert lib_html_2.display_name == "HTML 2 Title (Original)"
lib_html_1.display_name = "HTML 1 Title (Lib Update)"
lib_html_2.display_name = "HTML 2 Title (Lib Update)"
lib_html_1.data = "HTML 1 Content (Lib Update)"
lib_html_2.data = "HTML 2 Content (Lib Update)"
store.update_item(lib_html_1, self.user.id)
store.update_item(lib_html_2, self.user.id)
updated_lib_version = store.get_library(
lib.location.library_key, remove_version=False, remove_branch=False,
).location.library_key.version_guid
assert updated_lib_version is not None
assert updated_lib_version != original_lib_version

# DUPLICATE lc.
# Unit should now contain both lc and dupe.
# All settings should match between lc and dupe.
dupe = store.get_item(
self._duplicate_item(
parent_usage_key=unit.location,
source_usage_key=lc.location,
display_name="Dupe LC Block",
)
)
lc = store.get_item(lc.location)
unit = store.get_item(unit.location)
assert unit.children == [lc.location, dupe.location]
assert len(lc.children) == len(dupe.children) == 2
assert lc.max_count == dupe.max_count == 1
assert lc.source_library_id == dupe.source_library_id == str(lib.location.library_key)
assert lc.source_library_version == dupe.source_library_version == str(original_lib_version)

# The lc block's children should remain unchanged.
# That is: HTML 1 has overrides, HTML 2 has originals.
lc_html_1 = store.get_item(lc.children[0])
assert lc_html_1.display_name == "HTML 1 Title (Course Override)"
assert lc_html_1.data == "HTML 1 Content (Course Override)"
lc_html_2 = store.get_item(lc.children[1])
assert lc_html_2.display_name == "HTML 2 Title (Original)"
assert lc_html_2.data == "HTML 2 Content (Original)"

# Now, the dupe's children should copy *settings* overrides over from the lc block,
# but we don't actually expect it to copy *content* overrides over from the lc block.
# (Yes, this is weird. One would expect it to copy all fields from the lc block, whether settings or content.
# But that's the existing behavior, so we're going to test for it, for now at least.
# We may change this in the future: https://github.com/openedx/edx-platform/issues/33739)
dupe_html_1 = store.get_item(dupe.children[0])
assert dupe_html_1.display_name == "HTML 1 Title (Course Override)" # <- as you'd expect
assert dupe_html_1.data == "HTML 1 Content (Original)" # <- weird!
dupe_html_2 = store.get_item(dupe.children[1])
assert dupe_html_2.display_name == "HTML 2 Title (Original)" # <- as you'd expect
assert dupe_html_2.data == "HTML 2 Content (Original)" # <- as you'd expect

# Finally, upgrade the dupe's library version, and make sure it pulls in updated library block *content*,
# whilst preserving *settings overrides* (specifically, HTML 1's title override).
dupe.sync_from_library(upgrade_to_latest=True)
dupe = store.get_item(dupe.location)
assert dupe.source_library_version == str(updated_lib_version)
assert len(dupe.children) == 2
dupe_html_1 = store.get_item(dupe.children[0])
dupe_html_2 = store.get_item(dupe.children[1])
assert dupe_html_1.display_name == "HTML 1 Title (Course Override)"
assert dupe_html_1.data == "HTML 1 Content (Lib Update)"
assert dupe_html_2.display_name == "HTML 2 Title (Lib Update)"
assert dupe_html_2.data == "HTML 2 Content (Lib Update)"


@ddt.ddt
class TestMoveItem(ItemTest):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def test_library_content_preview_html(self):
self.assertEqual(len(lc_block.children), 0)

# Refresh children to be reflected in lc_block
lc_block = self._refresh_children(lc_block)
lc_block = self._upgrade_and_sync(lc_block)
self.assertEqual(len(lc_block.children), 1)

self.validate_preview_html(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1112,7 +1112,7 @@ def _setup_source_course_with_library_content(self, publish=False, version=None)
lc_block = self._add_library_content_block(
vertical, self.lib_key, publish_item=publish, other_settings=dict(source_library_version=version)
)
self._refresh_children(lc_block)
lc_block.sync_from_library(upgrade_to_latest=True)

def get_lib_content_block_children(self, block_location):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
contentstore/views/block.py to this file, because the logic is reused in another view now.
Along with it, we moved the business logic of the other views in that file, since that is related.
"""

import logging
from datetime import datetime
from uuid import uuid4

from attrs import asdict
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import (User) # lint-amnesty, pylint: disable=imported-auth-user
from django.contrib.auth.models import User # pylint: disable=imported-auth-user
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, HttpResponseBadRequest
from django.utils.timezone import timezone
Expand Down Expand Up @@ -57,37 +56,15 @@
from openedx.core.lib.gating import api as gating_api
from openedx.core.lib.cache_utils import request_cached
from openedx.core.toggles import ENTRANCE_EXAMS
from xmodule.course_block import (
DEFAULT_START_DATE,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.library_tools import (
LibraryToolsService,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore import (
EdxJSONEncoder,
ModuleStoreEnum,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import (
modulestore,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.draft_and_published import (
DIRECT_ONLY_CATEGORIES,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.exceptions import (
InvalidLocationError,
ItemNotFoundError,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.inheritance import (
own_metadata,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.services import (
ConfigurationService,
SettingsService,
TeamsConfigurationService,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.tabs import (
CourseTabList,
) # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.course_block import DEFAULT_START_DATE
from xmodule.library_tools import LibraryToolsService
from xmodule.modulestore import EdxJSONEncoder, ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError
from xmodule.modulestore.inheritance import own_metadata
from xmodule.services import ConfigurationService, SettingsService, TeamsConfigurationService
from xmodule.tabs import CourseTabList

from ..utils import (
ancestor_has_staff_lock,
Expand Down Expand Up @@ -180,6 +157,7 @@ def handle_xblock(request, usage_key_string=None):
the public CMS API.
"""
if usage_key_string:

usage_key = usage_key_with_run(usage_key_string)

access_check = (
Expand Down Expand Up @@ -220,15 +198,15 @@ def handle_xblock(request, usage_key_string=None):
_delete_item(usage_key, request.user)
return JsonResponse()
else: # Since we have a usage_key, we are updating an existing xblock.
return modify_xblock(usage_key, request)
modified_xblock = modify_xblock(usage_key, request)
return modified_xblock

elif request.method in ("PUT", "POST"):
if "duplicate_source_locator" in request.json:
parent_usage_key = usage_key_with_run(request.json["parent_locator"])
duplicate_source_usage_key = usage_key_with_run(
request.json["duplicate_source_locator"]
)

source_course = duplicate_source_usage_key.course_key
dest_course = parent_usage_key.course_key
if not has_studio_write_access(
Expand All @@ -255,6 +233,7 @@ def handle_xblock(request, usage_key_string=None):
request.user,
request.json.get("display_name"),
)

return JsonResponse(
{
"locator": str(dest_usage_key),
Expand Down Expand Up @@ -298,7 +277,6 @@ def handle_xblock(request, usage_key_string=None):

def modify_xblock(usage_key, request):
request_data = request.json
print(f'In modify_xblock with data = {request_data.get("data")}, fields = {request_data.get("fields")}')
return _save_xblock(
request.user,
get_xblock(usage_key, request.user),
Expand Down Expand Up @@ -360,21 +338,27 @@ def load_services_for_studio(runtime, user):
def _update_with_callback(xblock, user, old_metadata=None, old_content=None):
"""
Updates the xblock in the modulestore.
But before doing so, it calls the xblock's editor_saved callback function.
But before doing so, it calls the xblock's editor_saved callback function,
and after doing so, it calls the xblock's post_editor_saved callback function.

TODO: Remove getattrs from this function.
See https://github.com/openedx/edx-platform/issues/33715
"""
if callable(getattr(xblock, "editor_saved", None)):
if old_metadata is None:
old_metadata = own_metadata(xblock)
if old_content is None:
old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content)
if old_metadata is None:
old_metadata = own_metadata(xblock)
if old_content is None:
old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content)
if hasattr(xblock, "editor_saved"):
load_services_for_studio(xblock.runtime, user)
xblock.editor_saved(user, old_metadata, old_content)

# Update after the callback so any changes made in the callback will get persisted.
return modulestore().update_item(xblock, user.id)
xblock_updated = modulestore().update_item(xblock, user.id)
if hasattr(xblock_updated, "post_editor_saved"):
load_services_for_studio(xblock_updated.runtime, user)
xblock_updated.post_editor_saved(user, old_metadata, old_content)
return xblock_updated


def _save_xblock( # lint-amnesty, pylint: disable=too-many-statements
def _save_xblock(
user,
xblock,
data=None,
Expand All @@ -389,12 +373,11 @@ def _save_xblock( # lint-amnesty, pylint: disable=too-many-statements
publish=None,
fields=None,
summary_configuration_enabled=None,
):
): # lint-amnesty, pylint: disable=too-many-statements
kdmccormick marked this conversation as resolved.
Show resolved Hide resolved
"""
Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert
to default).

"""
store = modulestore()
# Perform all xblock changes within a (single-versioned) transaction
Expand Down Expand Up @@ -881,6 +864,8 @@ def _duplicate_block(
# Allow an XBlock to do anything fancy it may need to when duplicated from another block.
# These blocks may handle their own children or parenting if needed. Let them return booleans to
# let us know if we need to handle these or not.
# TODO: Make this a proper method in the base class so we don't need getattr.
# See https://github.com/openedx/edx-platform/issues/33715
load_services_for_studio(dest_block.runtime, user)
children_handled = dest_block.studio_post_duplicate(store, source_item)

Expand Down
Loading
Loading