diff --git a/openedx/core/djangoapps/content_tagging/handlers.py b/openedx/core/djangoapps/content_tagging/handlers.py index 211c8f4fbf5c..c1fad65cdda1 100644 --- a/openedx/core/djangoapps/content_tagging/handlers.py +++ b/openedx/core/djangoapps/content_tagging/handlers.py @@ -2,17 +2,32 @@ Automatic tagging of content """ +import crum import logging from django.dispatch import receiver -from openedx_events.content_authoring.data import CourseData, XBlockData -from openedx_events.content_authoring.signals import COURSE_CREATED, XBLOCK_CREATED, XBLOCK_DELETED, XBLOCK_UPDATED +from openedx_events.content_authoring.data import ( + CourseData, + XBlockData, + LibraryBlockData, +) +from openedx_events.content_authoring.signals import ( + COURSE_CREATED, + XBLOCK_CREATED, + XBLOCK_DELETED, + XBLOCK_UPDATED, + LIBRARY_BLOCK_CREATED, + LIBRARY_BLOCK_UPDATED, + LIBRARY_BLOCK_DELETED, +) from .tasks import delete_course_tags from .tasks import ( delete_xblock_tags, update_course_tags, - update_xblock_tags + update_xblock_tags, + update_library_block_tags, + delete_library_block_tags, ) from .toggles import CONTENT_TAGGING_AUTO @@ -74,3 +89,39 @@ def delete_tag_xblock(**kwargs): delete_course_tags.delay(str(xblock_info.usage_key.course_key)) delete_xblock_tags.delay(str(xblock_info.usage_key)) + + +@receiver(LIBRARY_BLOCK_CREATED) +@receiver(LIBRARY_BLOCK_UPDATED) +def auto_tag_library_block(**kwargs): + """ + Automatically tag Library Blocks based on metadata + """ + if not CONTENT_TAGGING_AUTO.is_enabled(): + return + + library_block_data = kwargs.get("library_block", None) + if not library_block_data or not isinstance(library_block_data, LibraryBlockData): + log.error("Received null or incorrect data for event") + return + + current_request = crum.get_current_request() + update_library_block_tags.delay( + str(library_block_data.usage_key), current_request.LANGUAGE_CODE + ) + + +@receiver(LIBRARY_BLOCK_DELETED) +def delete_tag_library_block(**kwargs): + """ + Delete tags associated with a Library XBlock whenever the block is deleted. + """ + library_block_data = kwargs.get("library_block", None) + if not library_block_data or not isinstance(library_block_data, LibraryBlockData): + log.error("Received null or incorrect data for event") + return + + try: + delete_library_block_tags(str(library_block_data.usage_key)) + except Exception as err: # pylint: disable=broad-except + log.error(f"Failed to delete library block tags: {err}") diff --git a/openedx/core/djangoapps/content_tagging/tasks.py b/openedx/core/djangoapps/content_tagging/tasks.py index 063a24072e1f..c566a020cbcb 100644 --- a/openedx/core/djangoapps/content_tagging/tasks.py +++ b/openedx/core/djangoapps/content_tagging/tasks.py @@ -11,6 +11,7 @@ from django.contrib.auth import get_user_model from edx_django_utils.monitoring import set_code_owner_attribute from opaque_keys.edx.keys import LearningContextKey, UsageKey +from opaque_keys.edx.locator import LibraryUsageLocatorV2 from openedx_tagging.core.tagging.models import Taxonomy from xmodule.modulestore.django import modulestore @@ -152,3 +153,48 @@ def delete_xblock_tags(usage_key_str: str) -> bool: except Exception as e: # pylint: disable=broad-except log.error("Error deleting tags for XBlock with id: %s. %s", usage_key, e) return False + + +@shared_task(base=LoggedTask) +@set_code_owner_attribute +def update_library_block_tags(usage_key_str: str, language_code: str) -> bool: + """ + Updates the automatically-managed tags for a content library block + whenever it is created/updated + + Params: + usage_key_str (str): identifier of the Library Block + langauge_code (str): the preferred language code of the user + """ + try: + usage_key = LibraryUsageLocatorV2.from_string(usage_key_str) + + log.info("Updating tags for Library Block with id: %s", usage_key) + + _set_initial_language_tag(usage_key, language_code) + return True + except Exception as e: # pylint: disable=broad-except + log.error("Error updating tags for XBlock with id: %s. %s", usage_key, e) + return False + + +@shared_task(base=LoggedTask) +@set_code_owner_attribute +def delete_library_block_tags(usage_key_str: str) -> bool: + """ + Delete the tags for a Library Block (when the Library Block itself is deleted). + + Params: + usage_key_str (str): identifier of the Library Block + """ + try: + usage_key = LibraryUsageLocatorV2.from_string(usage_key_str) + + log.info("Deleting tags for Library Block with id: %s", usage_key) + + _delete_tags(usage_key) + + return True + except Exception as e: # pylint: disable=broad-except + log.error("Error deleting tags for Library Block with id: %s. %s", usage_key, e) + return False diff --git a/openedx/core/djangoapps/content_tagging/tests/test_tasks.py b/openedx/core/djangoapps/content_tagging/tests/test_tasks.py index fccac3e30774..7c8527e3b5e8 100644 --- a/openedx/core/djangoapps/content_tagging/tests/test_tasks.py +++ b/openedx/core/djangoapps/content_tagging/tests/test_tasks.py @@ -5,7 +5,8 @@ from unittest.mock import patch -from django.test import override_settings +from django.test import override_settings, LiveServerTestCase +from django.http import HttpRequest from edx_toggles.toggles.testutils import override_waffle_flag from openedx_tagging.core.tagging.models import Tag, Taxonomy from organizations.models import Organization @@ -13,6 +14,9 @@ from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangolib.testing.utils import skip_unless_cms from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase +from openedx.core.lib.blockstore_api import create_collection +from openedx.core.djangoapps.content_libraries.api import create_library, create_library_block, delete_library_block +from openedx.core.lib.blockstore_api.tests.base import BlockstoreAppTestMixin from .. import api from ..models.base import TaxonomyOrg @@ -51,7 +55,12 @@ def setUp(self): @skip_unless_cms # Auto-tagging is only available in the CMS @override_waffle_flag(CONTENT_TAGGING_AUTO, active=True) -class TestAutoTagging(LanguageTaxonomyTestMixin, ModuleStoreTestCase): # type: ignore[misc] +class TestAutoTagging( # type: ignore[misc] + LanguageTaxonomyTestMixin, + ModuleStoreTestCase, + BlockstoreAppTestMixin, + LiveServerTestCase +): """ Test if the Course and XBlock tags are automatically created """ @@ -237,3 +246,60 @@ def test_waffle_disabled_create_delete_xblock(self): # Still no tags assert self._check_tag(usage_key_str, LANGUAGE_TAXONOMY_ID, None) + + def test_create_delete_library_block(self): + # Create collection and library + collection = create_collection("Test library collection") + library = create_library( + collection_uuid=collection.uuid, + org=self.orgA, + slug="lib_a", + title="Library Org A", + description="This is a library from Org A", + ) + + fake_request = HttpRequest() + fake_request.LANGUAGE_CODE = "pt-br" + with patch('crum.get_current_request', return_value=fake_request): + # Create Library Block + library_block = create_library_block(library.key, "problem", "Problem1") + + usage_key_str = str(library_block.usage_key) + + # Check if the tags are created in the Library Block with the user's preferred language + assert self._check_tag(usage_key_str, LANGUAGE_TAXONOMY_ID, 'Português (Brasil)') + + # Delete the XBlock + delete_library_block(library_block.usage_key) + + # Check if the tags are deleted + assert self._check_tag(usage_key_str, LANGUAGE_TAXONOMY_ID, None) + + @override_waffle_flag(CONTENT_TAGGING_AUTO, active=False) + def test_waffle_disabled_create_delete_library_block(self): + # Create collection and library + collection = create_collection("Test library collection 2") + library = create_library( + collection_uuid=collection.uuid, + org=self.orgA, + slug="lib_a2", + title="Library Org A 2", + description="This is a library from Org A 2", + ) + + fake_request = HttpRequest() + fake_request.LANGUAGE_CODE = "pt-br" + with patch('crum.get_current_request', return_value=fake_request): + # Create Library Block + library_block = create_library_block(library.key, "problem", "Problem2") + + usage_key_str = str(library_block.usage_key) + + # No tags created + assert self._check_tag(usage_key_str, LANGUAGE_TAXONOMY_ID, None) + + # Delete the XBlock + delete_library_block(library_block.usage_key) + + # Still no tags + assert self._check_tag(usage_key_str, LANGUAGE_TAXONOMY_ID, None)