diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index 4ec1922fb9..dc13d9d431 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -304,6 +304,7 @@ def update_container( container_type = ContainerType(container_key.container_type) version: ContainerVersion + affected_containers: list[ContainerMetadata] = [] match container_type: case ContainerType.Unit: @@ -313,6 +314,7 @@ def update_container( created=created, created_by=user_id, ) + affected_containers = get_containers_contains_item(container_key) case ContainerType.Subsection: version = authoring_api.create_next_subsection_version( container.subsection, @@ -320,6 +322,7 @@ def update_container( created=created, created_by=user_id, ) + affected_containers = get_containers_contains_item(container_key) case ContainerType.Section: version = authoring_api.create_next_section_version( container.section, @@ -327,15 +330,28 @@ def update_container( created=created, created_by=user_id, ) + + # The `affected_containers` are not obtained, because the sections are + # not contained in any container. case _: raise NotImplementedError(f"Library does not support {container_type} yet") + # Send event related to the updated container LIBRARY_CONTAINER_UPDATED.send_event( library_container=LibraryContainerData( container_key=container_key, ) ) + # Send events related to the containers that contains the updated container. + # This is to update the children display names used in the section/subsection previews. + for affected_container in affected_containers: + LIBRARY_CONTAINER_UPDATED.send_event( + library_container=LibraryContainerData( + container_key=affected_container.container_key, + ) + ) + return ContainerMetadata.from_container(library_key, version.container) diff --git a/openedx/core/djangoapps/content_libraries/tests/test_api.py b/openedx/core/djangoapps/content_libraries/tests/test_api.py index 0715848c64..ee2b90410f 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_api.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_api.py @@ -335,7 +335,8 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): assert collection.created_by == self.user assert event_receiver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, { "signal": LIBRARY_COLLECTION_CREATED, "sender": None, @@ -346,7 +347,6 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): ), ), }, - event_receiver.call_args_list[0].kwargs, ) def test_create_library_collection_invalid_library(self) -> None: @@ -373,7 +373,8 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): assert self.col1.created_by == self.user assert event_receiver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, { "signal": LIBRARY_COLLECTION_UPDATED, "sender": None, @@ -384,7 +385,6 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): ), ), }, - event_receiver.call_args_list[0].kwargs, ) def test_update_library_collection_wrong_library(self) -> None: @@ -406,7 +406,8 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): ) assert event_receiver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, { "signal": LIBRARY_COLLECTION_DELETED, "sender": None, @@ -417,7 +418,6 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): ), ), }, - event_receiver.call_args_list[0].kwargs, ) def test_update_library_collection_items(self) -> None: @@ -463,7 +463,8 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): ) assert event_receiver.call_count == 4 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -472,9 +473,9 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): changes=["collections"], ), }, - event_receiver.call_args_list[0].kwargs, ) - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[1].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -483,9 +484,9 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): changes=["collections"], ), }, - event_receiver.call_args_list[1].kwargs, ) - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[2].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -494,9 +495,9 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): changes=["collections"], ), }, - event_receiver.call_args_list[2].kwargs, ) - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[3].kwargs, { "signal": LIBRARY_COLLECTION_UPDATED, "sender": None, @@ -507,7 +508,6 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): ), ), }, - event_receiver.call_args_list[3].kwargs, ) def test_update_collection_components_from_wrong_library(self) -> None: @@ -539,14 +539,18 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): assert self.lib2.learning_package_id is not None assert len(authoring_api.get_collection(self.lib2.learning_package_id, self.col2.key).entities.all()) == 1 assert len(authoring_api.get_collection(self.lib2.learning_package_id, self.col3.key).entities.all()) == 1 - assert { - "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, - "sender": None, - "content_object": ContentObjectChangedData( - object_id=self.lib2_problem_block["id"], - changes=["collections"], - ), - }.items() <= event_receiver.call_args_list[0].kwargs.items() + + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=self.lib2_problem_block["id"], + changes=["collections"], + ), + }, + ) assert len(collection_update_event_receiver.call_args_list) == 2 collection_update_events = [call.kwargs for call in collection_update_event_receiver.call_args_list] @@ -578,7 +582,8 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): api.delete_library_block(LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"])) assert event_receiver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, { "signal": LIBRARY_COLLECTION_UPDATED, "sender": None, @@ -590,7 +595,6 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): background=True, ), }, - event_receiver.call_args_list[0].kwargs, ) def test_delete_library_container(self) -> None: @@ -610,7 +614,8 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): api.delete_container(LibraryContainerLocator.from_string(self.unit1["id"])) assert event_receiver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, { "signal": LIBRARY_COLLECTION_UPDATED, "sender": None, @@ -622,7 +627,6 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): background=True, ), }, - event_receiver.call_args_list[0].kwargs, ) def test_restore_library_block(self) -> None: @@ -641,7 +645,8 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): api.restore_library_block(LibraryUsageLocatorV2.from_string(self.lib1_problem_block["id"])) assert event_receiver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_receiver.call_args_list[0].kwargs, { "signal": LIBRARY_COLLECTION_UPDATED, "sender": None, @@ -653,7 +658,6 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): background=True, ), }, - event_receiver.call_args_list[0].kwargs, ) def test_add_component_and_revert(self) -> None: @@ -681,16 +685,19 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): api.revert_changes(self.lib1.library_key) assert collection_update_event_receiver.call_count == 1 - assert { - "signal": LIBRARY_COLLECTION_UPDATED, - "sender": None, - "library_collection": LibraryCollectionData( - collection_key=api.library_collection_locator( - self.lib1.library_key, - collection_key=self.col1.key, + self.assertDictContainsEntries( + collection_update_event_receiver.call_args_list[0].kwargs, + { + "signal": LIBRARY_COLLECTION_UPDATED, + "sender": None, + "library_collection": LibraryCollectionData( + collection_key=api.library_collection_locator( + self.lib1.library_key, + collection_key=self.col1.key, + ), ), - ), - }.items() <= collection_update_event_receiver.call_args_list[0].kwargs.items() + }, + ) def test_delete_component_and_revert(self) -> None: """ @@ -717,16 +724,19 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest): api.revert_changes(self.lib1.library_key) assert collection_update_event_receiver.call_count == 1 - assert { - "signal": LIBRARY_COLLECTION_UPDATED, - "sender": None, - "library_collection": LibraryCollectionData( - collection_key=api.library_collection_locator( - self.lib1.library_key, - collection_key=self.col1.key, + self.assertDictContainsEntries( + collection_update_event_receiver.call_args_list[0].kwargs, + { + "signal": LIBRARY_COLLECTION_UPDATED, + "sender": None, + "library_collection": LibraryCollectionData( + collection_key=api.library_collection_locator( + self.lib1.library_key, + collection_key=self.col1.key, + ), ), - ), - }.items() <= collection_update_event_receiver.call_args_list[0].kwargs.items() + }, + ) class ContentLibraryContainersTest(ContentLibrariesRestApiTest): @@ -865,7 +875,8 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): using the `LIBRARY_CONTAINER_UPDATED` signal. """ assert event_mock.call_count == 2 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_mock.call_args_list[0].kwargs, { "signal": LIBRARY_CONTAINER_UPDATED, "sender": None, @@ -874,9 +885,9 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): background=True, ) }, - event_mock.call_args_list[0].kwargs, ) - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_mock.call_args_list[1].kwargs, { "signal": LIBRARY_CONTAINER_UPDATED, "sender": None, @@ -885,7 +896,6 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): background=True, ) }, - event_mock.call_args_list[1].kwargs, ) def test_call_container_update_signal_when_delete_component(self) -> None: @@ -920,6 +930,97 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): self._set_library_block_fields(self.html_block_usage_key, {"data": block_olx, "metadata": {}}) self._validate_calls_of_html_block(container_update_event_receiver) + def test_call_container_update_signal_when_update_unit(self) -> None: + container_update_event_receiver = mock.Mock() + LIBRARY_CONTAINER_UPDATED.connect(container_update_event_receiver) + self._update_container(self.unit1.container_key, 'New Unit Display Name') + + assert container_update_event_receiver.call_count == 3 + self.assertDictContainsEntries( + container_update_event_receiver.call_args_list[0].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.unit1.container_key, + ) + }, + ) + self.assertDictContainsEntries( + container_update_event_receiver.call_args_list[1].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.subsection1.container_key, + ) + }, + ) + self.assertDictContainsEntries( + container_update_event_receiver.call_args_list[2].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.subsection2.container_key, + ) + }, + ) + + def test_call_container_update_signal_when_update_subsection(self) -> None: + container_update_event_receiver = mock.Mock() + LIBRARY_CONTAINER_UPDATED.connect(container_update_event_receiver) + self._update_container(self.subsection1.container_key, 'New Subsection Display Name') + + assert container_update_event_receiver.call_count == 3 + self.assertDictContainsEntries( + container_update_event_receiver.call_args_list[0].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.subsection1.container_key, + ) + }, + ) + self.assertDictContainsEntries( + container_update_event_receiver.call_args_list[1].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.section1.container_key, + ) + }, + ) + self.assertDictContainsEntries( + container_update_event_receiver.call_args_list[2].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.section2.container_key, + ) + }, + ) + + def test_call_container_update_signal_when_update_section(self) -> None: + container_update_event_receiver = mock.Mock() + LIBRARY_CONTAINER_UPDATED.connect(container_update_event_receiver) + self._update_container(self.section1.container_key, 'New Section Display Name') + + assert container_update_event_receiver.call_count == 1 + self.assertDictContainsEntries( + container_update_event_receiver.call_args_list[0].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.section1.container_key, + ) + }, + ) + def test_call_object_changed_signal_when_remove_component(self) -> None: html_block_1 = self._add_block_to_library( self.lib1.library_key, "html", "html3", @@ -941,7 +1042,8 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): ) assert event_reciver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[0].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -950,7 +1052,6 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["units"], ), }, - event_reciver.call_args_list[0].kwargs, ) def test_call_object_changed_signal_when_remove_unit(self) -> None: @@ -973,7 +1074,8 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): ) assert event_reciver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[0].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -982,7 +1084,6 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["subsections"], ), }, - event_reciver.call_args_list[0].kwargs, ) def test_call_object_changed_signal_when_remove_subsection(self) -> None: @@ -1011,7 +1112,8 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): ) assert event_reciver.call_count == 1 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[0].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -1020,7 +1122,6 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["sections"], ), }, - event_reciver.call_args_list[0].kwargs, ) def test_call_object_changed_signal_when_add_component(self) -> None: @@ -1044,7 +1145,8 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): ) assert event_reciver.call_count == 2 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[0].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -1053,9 +1155,9 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["units"], ), }, - event_reciver.call_args_list[0].kwargs, ) - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[1].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -1064,7 +1166,6 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["units"], ), }, - event_reciver.call_args_list[1].kwargs, ) def test_call_object_changed_signal_when_add_unit(self) -> None: @@ -1081,7 +1182,8 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): entities_action=authoring_api.ChildrenEntitiesAction.APPEND, ) assert event_reciver.call_count == 2 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[0].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -1090,9 +1192,9 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["subsections"], ), }, - event_reciver.call_args_list[0].kwargs, ) - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[1].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -1101,7 +1203,6 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["subsections"], ), }, - event_reciver.call_args_list[1].kwargs, ) def test_call_object_changed_signal_when_add_subsection(self) -> None: @@ -1129,7 +1230,8 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): entities_action=authoring_api.ChildrenEntitiesAction.APPEND, ) assert event_reciver.call_count == 2 - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[0].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -1138,9 +1240,9 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["sections"], ), }, - event_reciver.call_args_list[0].kwargs, ) - self.assertDictContainsSubset( + self.assertDictContainsEntries( + event_reciver.call_args_list[1].kwargs, { "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, @@ -1149,7 +1251,6 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): changes=["sections"], ), }, - event_reciver.call_args_list[1].kwargs, ) def test_delete_component_and_revert(self) -> None: @@ -1172,8 +1273,13 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): api.revert_changes(self.lib1.library_key) assert container_event_receiver.call_count == 1 - assert { - "signal": LIBRARY_CONTAINER_UPDATED, - "sender": None, - "library_container": LibraryContainerData(container_key=self.unit3.container_key), - }.items() <= container_event_receiver.call_args_list[0].kwargs.items() + self.assertDictContainsEntries( + container_event_receiver.call_args_list[0].kwargs, + { + "signal": LIBRARY_CONTAINER_UPDATED, + "sender": None, + "library_container": LibraryContainerData( + container_key=self.unit3.container_key + ), + }, + )