From 1962ae8fbfb0e59e7f16631e72f19ef10556d2ab Mon Sep 17 00:00:00 2001 From: Asad Ali Date: Wed, 8 Jul 2026 14:46:51 +0500 Subject: [PATCH] feat: allow block publish if was published before Library v2 migration --- cms/djangoapps/contentstore/tasks.py | 12 ++++- xmodule/library_content_block.py | 8 +++- xmodule/tests/test_library_content.py | 66 +++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/tasks.py b/cms/djangoapps/contentstore/tasks.py index 4d8e02dfd6be..946ee363888d 100644 --- a/cms/djangoapps/contentstore/tasks.py +++ b/cms/djangoapps/contentstore/tasks.py @@ -2340,12 +2340,20 @@ def _cancel_old_tasks(course_key: str, user: User, ignore_task_ids: list[str]): @shared_task(base=LegacyLibraryContentToItemBank, bind=True) -def migrate_course_legacy_library_blocks_to_item_bank(self, user_id: int, course_key: str): +def migrate_course_legacy_library_blocks_to_item_bank( + self, user_id: int, course_key: str, persist_publish_state: bool = False, +): """ Migrate legacy course library blocks to Item Bank. Depending on the number of blocks and its children blocks this operation can take a significant amount of time and this is why it is run as a celery task. + + Arguments: + user_id: id of the user performing the migration. + course_key: the course whose legacy library content blocks should be migrated. + persist_publish_state: if True, blocks that were published before the migration are + re-published afterwards. Defaults to False, leaving migrated blocks as drafts. """ ensure_cms("Legacy library content references may only be executed in CMS") set_code_owner_attribute_from_module(__name__) @@ -2363,7 +2371,7 @@ def migrate_course_legacy_library_blocks_to_item_bank(self, user_id: int, course with store.bulk_operations(key): for block in blocks: self.status.set_state(f'Migrating block: {block.usage_key}') - block.v2_update_children_upstream_version(user_id) + block.v2_update_children_upstream_version(user_id, persist_publish_state) except Exception as exc: # pylint: disable=broad-except LOGGER.exception(f'Error while migrating blocks: {exc}') self.status.fail(str(exc)) diff --git a/xmodule/library_content_block.py b/xmodule/library_content_block.py index 995eafad19d5..d000be1af589 100644 --- a/xmodule/library_content_block.py +++ b/xmodule/library_content_block.py @@ -329,14 +329,18 @@ def studio_post_paste(self, store, source_node) -> bool: self.sync_from_library(upgrade_to_latest=False) return True # Children have been handled - def v2_update_children_upstream_version(self, user_id=None): + def v2_update_children_upstream_version(self, user_id=None, persist_publish_state=False): """ Update the upstream and upstream version fields of all children to point to library v2 version of the legacy library blocks. This essentially converts this legacy block to new ItemBankBlock. + + If `persist_publish_state` is True, and this block was published prior to the migration, it is + re-published afterwards so that the upstream/upstream_version changes reach LMS. """ from cms.djangoapps.modulestore_migrator import api as migrator_api store = modulestore() with store.bulk_operations(self.course_id): + was_published = persist_publish_state and store.has_published_version(self) children = self.get_children() # These are the v1 library item upstream UsageKeys child_old_upstream_keys = [ @@ -358,6 +362,8 @@ def v2_update_children_upstream_version(self, user_id=None): self.is_migrated_to_v2 = True self.save() store.update_item(self, user_id) + if was_published: + store.publish(self.location, user_id) def _validate_library_version(self, validation, lib_tools, version, library_key): """ diff --git a/xmodule/tests/test_library_content.py b/xmodule/tests/test_library_content.py index d5a7fcbc1cc5..793a30dda591 100644 --- a/xmodule/tests/test_library_content.py +++ b/xmodule/tests/test_library_content.py @@ -826,3 +826,69 @@ def test_author_view(self): assert '
  • html 2
  • ' in rendered.content assert '
  • html 3
  • ' in rendered.content assert '
  • html 4
  • ' in rendered.content + + +@ddt.ddt +class TestLegacyLibraryContentBlockMigrationPublishing(LegacyLibraryContentTest): + """ + Unit tests for the `persist_publish_state` flag of + LegacyLibraryContentBlock.v2_update_children_upstream_version. + """ + + def setUp(self): + from cms.djangoapps.modulestore_migrator import api + from cms.djangoapps.modulestore_migrator.data import CompositionLevel, RepeatHandlingStrategy + super().setUp() + user = UserFactory() + self._sync_lc_block_from_library() + self.organization = OrganizationFactory(short_name="myorg") + lib_api.create_library( + org=self.organization, + slug="mylib", + title="My Test V2 Library", + ) + self.library_v2 = lib_api.ContentLibrary.objects.get(slug="mylib") + api.start_migration_to_library( + user=user, + source_key=self.library.location.library_key, + target_library_key=self.library_v2.library_key, + target_collection_slug=None, + composition_level=CompositionLevel.Component, + repeat_handling_strategy=RepeatHandlingStrategy.Skip, + preserve_url_slugs=True, + forward_source_to_target=True, + ) + + @ddt.data( + # Published before migration, flag True: re-published with the migration reflected. + (True, True), + # Published before migration, flag False (default): published branch is left untouched. + (True, False), + # Never published before migration, flag True: stays unpublished. + (False, True), + ) + @ddt.unpack + def test_persist_publish_state(self, was_published_before, persist_publish_state): + """ + Tests the `persist_publish_state` flag of `v2_update_children_upstream_version` under + the various combinations of prior publish state and flag value. + """ + if was_published_before: + self.store.publish(self.course.location, self.user_id) + + self.lc_block.v2_update_children_upstream_version( + self.user_id, persist_publish_state=persist_publish_state, + ) + + if was_published_before and persist_publish_state: + with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): + published_block = self.store.get_item(self.lc_block.location) + assert published_block.is_migrated_to_v2 is True + assert published_block.get_children()[0].upstream == "lb:myorg:mylib:html:html_1" + elif was_published_before and not persist_publish_state: + with self.store.branch_setting(ModuleStoreEnum.Branch.published_only): + published_block = self.store.get_item(self.lc_block.location) + # The published version still reflects the pre-migration state. + assert published_block.is_migrated_to_v2 is False + else: + assert not self.store.has_published_version(self.store.get_item(self.lc_block.location))