diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 9ed3f1ce4b3..e0369698864 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -7,9 +7,8 @@ import re from collections import defaultdict from contextlib import contextmanager -from datetime import datetime, timezone +from datetime import datetime from urllib.parse import quote_plus -from uuid import uuid4 from django.conf import settings from django.core.exceptions import ValidationError @@ -22,8 +21,6 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.locator import LibraryLocator from openedx.core.lib.teams_config import CONTENT_GROUPS_FOR_TEAMS, TEAM_SCHEME -from openedx_events.content_authoring.data import DuplicatedXBlockData -from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED from openedx_events.learning.data import CourseNotificationData from openedx_events.learning.signals import COURSE_NOTIFICATION_REQUESTED @@ -1102,77 +1099,22 @@ def duplicate_block( Duplicate an existing xblock as a child of the supplied parent_usage_key. You can optionally specify what usage key the new duplicate block will use via dest_usage_key. - If shallow is True, does not copy children. Otherwise, this function calls itself - recursively, and will set the is_child flag to True when dealing with recursed child - blocks. + If shallow is True, does not copy children. """ store = modulestore() with store.bulk_operations(duplicate_source_usage_key.course_key): source_item = store.get_item(duplicate_source_usage_key) - if not dest_usage_key: - # Change the blockID to be unique. - dest_usage_key = source_item.location.replace(name=uuid4().hex) - - category = dest_usage_key.block_type - - duplicate_metadata, asides_to_create = gather_block_attributes( - source_item, display_name=display_name, is_child=is_child, + return source_item.studio_duplicate( + parent_usage_key=parent_usage_key, + duplicate_source_usage_key=duplicate_source_usage_key, + user=user, + store=store, + dest_usage_key=dest_usage_key, + display_name=display_name, + shallow=shallow, + is_child=is_child, ) - dest_block = store.create_item( - user.id, - dest_usage_key.course_key, - dest_usage_key.block_type, - block_id=dest_usage_key.block_id, - definition_data=source_item.get_explicitly_set_fields_by_scope(Scope.content), - metadata=duplicate_metadata, - runtime=source_item.runtime, - asides=asides_to_create - ) - - children_handled = False - - if hasattr(dest_block, 'studio_post_duplicate'): - # 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. - load_services_for_studio(dest_block.runtime, user) - children_handled = dest_block.studio_post_duplicate(store, source_item) - - # Children are not automatically copied over (and not all xblocks have a 'children' attribute). - # Because DAGs are not fully supported, we need to actually duplicate each child as well. - if source_item.has_children and not shallow and not children_handled: - dest_block.children = dest_block.children or [] - for child in source_item.children: - dupe = duplicate_block(dest_block.location, child, user=user, is_child=True) - if dupe not in dest_block.children: # _duplicate_block may add the child for us. - dest_block.children.append(dupe) - store.update_item(dest_block, user.id) - - # pylint: disable=protected-access - if 'detached' not in source_item.runtime.load_block_type(category)._class_tags: - parent = store.get_item(parent_usage_key) - # If source was already a child of the parent, add duplicate immediately afterward. - # Otherwise, add child to end. - if source_item.location in parent.children: - source_index = parent.children.index(source_item.location) - parent.children.insert(source_index + 1, dest_block.location) - else: - parent.children.append(dest_block.location) - store.update_item(parent, user.id) - - # .. event_implemented_name: XBLOCK_DUPLICATED - XBLOCK_DUPLICATED.send_event( - time=datetime.now(timezone.utc), - xblock_info=DuplicatedXBlockData( - usage_key=dest_block.location, - block_type=dest_block.location.block_type, - source_usage_key=duplicate_source_usage_key, - ) - ) - - return dest_block.location - def update_from_source(*, source_block, destination_block, user_id): """ diff --git a/cms/lib/xblock/authoring_mixin.py b/cms/lib/xblock/authoring_mixin.py index acc11d48a26..b9057391b18 100644 --- a/cms/lib/xblock/authoring_mixin.py +++ b/cms/lib/xblock/authoring_mixin.py @@ -4,15 +4,11 @@ import logging -from datetime import datetime, timezone -from uuid import uuid4 from django.conf import settings from web_fragments.fragment import Fragment from xblock.core import XBlock, XBlockMixin from xblock.fields import String, Scope -from openedx_events.content_authoring.data import DuplicatedXBlockData -from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED log = logging.getLogger(__name__) diff --git a/xmodule/library_content_block.py b/xmodule/library_content_block.py index 09a5d1dee13..ac0d3fa95b4 100644 --- a/xmodule/library_content_block.py +++ b/xmodule/library_content_block.py @@ -591,7 +591,12 @@ def children_are_syncing(self, request, suffix=''): # pylint: disable=unused-ar is_updating = False return Response(json.dumps(is_updating)) - def studio_post_duplicate(self, store, source_block): + def studio_post_duplicate( + self, + source_block, + *_args, + **__kwargs, + ) -> None: """ Used by the studio after basic duplication of a source block. We handle the children ourselves, because we have to properly reference the library upstream and set the overrides. @@ -603,7 +608,6 @@ def studio_post_duplicate(self, store, source_block): self._validate_sync_permissions() self.get_tools(to_read_library_content=True).trigger_duplication(source_block=source_block, dest_block=self) - return True # Children have been handled. def studio_post_paste(self, store, source_node) -> bool: """