Skip to content

Commit 69b4720

Browse files
fix: use a bulk context when pasting containers into a content library
1 parent f0bad41 commit 69b4720

2 files changed

Lines changed: 47 additions & 19 deletions

File tree

openedx/core/djangoapps/content_libraries/api/blocks.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ def _import_staged_block(
591591
olx_str: str,
592592
library_key: LibraryLocatorV2,
593593
source_context_key: LearningContextKey,
594-
user,
594+
user: UserType,
595595
staged_content_id: StagedContentID,
596596
staged_content_files: list[StagedContentFileData],
597597
now: datetime,
@@ -697,7 +697,7 @@ def _import_staged_block(
697697
# This will create the first component version and set the OLX/title
698698
# appropriately. It will not publish. Once we get the newly created
699699
# ComponentVersion back from this, we can attach all our files to it.
700-
set_library_block_olx(usage_key, olx_str, paths_to_media)
700+
set_library_block_olx(usage_key, olx_str, paths_to_media, created_by=user.id)
701701

702702
# Now return the metadata about the new block
703703
return get_library_block(usage_key)
@@ -713,7 +713,7 @@ def _is_container(block_type: str) -> bool:
713713
def _import_staged_block_as_container(
714714
library_key: LibraryLocatorV2,
715715
source_context_key: LearningContextKey,
716-
user,
716+
user: UserType,
717717
staged_content_id: StagedContentID,
718718
staged_content_files: list[StagedContentFileData],
719719
now: datetime,
@@ -829,7 +829,7 @@ def _import_staged_block_as_container(
829829
return container
830830

831831

832-
def import_staged_content_from_user_clipboard(library_key: LibraryLocatorV2, user) -> PublishableItem:
832+
def import_staged_content_from_user_clipboard(library_key: LibraryLocatorV2, user: UserType) -> PublishableItem:
833833
"""
834834
Create a new library item from the staged content from clipboard.
835835
Can create containers (e.g. units) or XBlocks.
@@ -852,11 +852,11 @@ def import_staged_content_from_user_clipboard(library_key: LibraryLocatorV2, use
852852
raise RuntimeError("olx_str missing") # Shouldn't happen - mostly here for type checker
853853

854854
now = datetime.now(tz=timezone.utc) # noqa: UP017
855+
lp_id = ContentLibrary.objects.get_by_key(library_key).learning_package_id
855856

856-
if _is_container(user_clipboard.content.block_type):
857-
# This is a container and we can import it as such.
858-
# Start an atomic section so the whole paste succeeds or fails together:
859-
with transaction.atomic():
857+
# Start an atomic section so the whole paste succeeds or fails together and creates a single change log entry:
858+
with transaction.atomic(), content_api.bulk_draft_changes_for(lp_id, changed_by=user.id, changed_at=now):
859+
if _is_container(user_clipboard.content.block_type):
860860
return _import_staged_block_as_container(
861861
library_key,
862862
source_context_key,
@@ -866,17 +866,17 @@ def import_staged_content_from_user_clipboard(library_key: LibraryLocatorV2, use
866866
now,
867867
olx_str=olx_str,
868868
)
869-
else:
870-
return _import_staged_block(
871-
user_clipboard.content.block_type,
872-
olx_str,
873-
library_key,
874-
source_context_key,
875-
user,
876-
staged_content_id,
877-
staged_content_files,
878-
now,
879-
)
869+
else:
870+
return _import_staged_block(
871+
user_clipboard.content.block_type,
872+
olx_str,
873+
library_key,
874+
source_context_key,
875+
user,
876+
staged_content_id,
877+
staged_content_files,
878+
now,
879+
)
880880

881881
def get_or_create_olx_media_type(block_type: str) -> MediaType:
882882
"""

openedx/core/djangoapps/content_libraries/tests/test_api.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,11 @@ def test_get_containers_contains_item(self):
749749

750750

751751
def test_copy_and_paste_container_same_library(self) -> None:
752+
"""
753+
Test copying and then pasting a container (within the same library)
754+
"""
755+
# Publish the library so we can track what happens from this point forward more easily
756+
api.publish_changes(self.lib1.library_key, self.user.id)
752757
# Copy a section with children
753758
api.copy_container(self.section1.container_key, self.user.id)
754759
# Paste the container
@@ -768,7 +773,17 @@ def test_copy_and_paste_container_same_library(self) -> None:
768773
assert isinstance(subsections[1], api.ContainerMetadata)
769774
assert subsections[1].container_key == self.subsection2.container_key
770775

776+
# Verify that everything was pasted in a single draft change:
777+
container_history = content_api.get_entity_draft_history(new_container.container_id)
778+
assert len(container_history) == 1
779+
# The subsections are re-used on paste into the same library, so they aren't modified at all since the publish:
780+
child_history = content_api.get_entity_draft_history(subsections[0].container_id)
781+
assert len(child_history) == 0
782+
771783
def test_copy_and_paste_container_another_library(self) -> None:
784+
"""
785+
Test copying and then pasting a container (into a different library)
786+
"""
772787
# Copy a section with children
773788
api.copy_container(self.section1.container_key, self.user.id)
774789

@@ -824,6 +839,19 @@ def test_copy_and_paste_container_another_library(self) -> None:
824839
# This is the same unit, so it should not be duplicated
825840
assert units_subsection1[0].container_key == units_subsection2[0].container_key
826841

842+
# Verify that everything was pasted in a single draft change:
843+
container_history = content_api.get_entity_draft_history(new_container.container_id)
844+
assert len(container_history) == 1
845+
subsection_history = content_api.get_entity_draft_history(subsections[0].container_id)
846+
assert len(subsection_history) == 1
847+
component_id = content_api.get_entities_in_container(
848+
content_api.get_container(subsections[0].container_id), published=False
849+
)[0].entity.id
850+
component_history = content_api.get_entity_draft_history(component_id)
851+
assert len(component_history) == 1
852+
assert container_history[0].draft_change_log.id == subsection_history[0].draft_change_log.id
853+
assert container_history[0].draft_change_log.id == component_history[0].draft_change_log.id
854+
827855
def test_set_library_block_olx_no_signal_on_rollback(self) -> None:
828856
"""
829857
LIBRARY_BLOCK_UPDATED is NOT emitted when set_library_block_olx is called

0 commit comments

Comments
 (0)