Make copied tags editable again after breaking the upstream link to library content [FC-0076] (#36228)
When deleting an upstream library block, ensure that any tags that may have been copied to downstream blocks are made editable again. This is achieved by un-setting the `is_copied` flag on the downstream tags.
This commit is contained in:
@@ -12,10 +12,17 @@ from django.db import transaction
|
||||
from django.dispatch import receiver
|
||||
from edx_toggles.toggles import SettingToggle
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx_events.content_authoring.data import CourseCatalogData, CourseData, CourseScheduleData, XBlockData
|
||||
from openedx_events.content_authoring.data import (
|
||||
CourseCatalogData,
|
||||
CourseData,
|
||||
CourseScheduleData,
|
||||
LibraryBlockData,
|
||||
XBlockData,
|
||||
)
|
||||
from openedx_events.content_authoring.signals import (
|
||||
COURSE_CATALOG_INFO_CHANGED,
|
||||
COURSE_IMPORT_COMPLETED,
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
XBLOCK_CREATED,
|
||||
XBLOCK_DELETED,
|
||||
XBLOCK_UPDATED,
|
||||
@@ -38,7 +45,11 @@ from xmodule.modulestore.django import SignalHandler, modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
from ..models import PublishableEntityLink
|
||||
from ..tasks import create_or_update_upstream_links, handle_create_or_update_xblock_upstream_link
|
||||
from ..tasks import (
|
||||
create_or_update_upstream_links,
|
||||
handle_create_or_update_xblock_upstream_link,
|
||||
handle_unlink_upstream_block,
|
||||
)
|
||||
from .signals import GRADING_POLICY_CHANGED
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -287,3 +298,16 @@ def handle_new_course_import(**kwargs):
|
||||
force=True,
|
||||
replace=True
|
||||
)
|
||||
|
||||
|
||||
@receiver(LIBRARY_BLOCK_DELETED)
|
||||
def unlink_upstream_block_handler(**kwargs):
|
||||
"""
|
||||
Handle unlinking the upstream (library) block from any downstream (course) blocks.
|
||||
"""
|
||||
library_block = kwargs.get("library_block", None)
|
||||
if not library_block or not isinstance(library_block, LibraryBlockData):
|
||||
log.error("Received null or incorrect data for event")
|
||||
return
|
||||
|
||||
handle_unlink_upstream_block.delay(str(library_block.usage_key))
|
||||
|
||||
@@ -67,6 +67,7 @@ from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRol
|
||||
from common.djangoapps.util.monitoring import monitor_import_failure
|
||||
from openedx.core.djangoapps.content.learning_sequences.api import key_supports_outlines
|
||||
from openedx.core.djangoapps.content_libraries import api as v2contentlib_api
|
||||
from openedx.core.djangoapps.content_tagging.api import make_copied_tags_editable
|
||||
from openedx.core.djangoapps.course_apps.toggles import exams_ida_enabled
|
||||
from openedx.core.djangoapps.discussions.config.waffle import ENABLE_NEW_STRUCTURE_DISCUSSIONS
|
||||
from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, Provider
|
||||
@@ -1466,3 +1467,23 @@ def create_or_update_upstream_links(
|
||||
for xblock in xblocks:
|
||||
create_or_update_xblock_upstream_link(xblock, course_key_str, created)
|
||||
course_status.update_status(LearningContextLinksStatusChoices.COMPLETED)
|
||||
|
||||
|
||||
@shared_task
|
||||
@set_code_owner_attribute
|
||||
def handle_unlink_upstream_block(upstream_usage_key_string: str) -> None:
|
||||
"""
|
||||
Handle updates needed to downstream blocks when the upstream link is severed.
|
||||
"""
|
||||
ensure_cms("handle_unlink_upstream_block may only be executed in a CMS context")
|
||||
|
||||
try:
|
||||
upstream_usage_key = UsageKey.from_string(upstream_usage_key_string)
|
||||
except (InvalidKeyError):
|
||||
LOGGER.exception(f'Invalid upstream usage_key: {upstream_usage_key_string}')
|
||||
return
|
||||
|
||||
for link in PublishableEntityLink.objects.filter(
|
||||
upstream_usage_key=upstream_usage_key,
|
||||
):
|
||||
make_copied_tags_editable(str(link.downstream_usage_key))
|
||||
|
||||
@@ -6,6 +6,13 @@ APIs.
|
||||
import ddt
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from rest_framework.test import APIClient
|
||||
from openedx_events.content_authoring.signals import (
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
XBLOCK_CREATED,
|
||||
XBLOCK_DELETED,
|
||||
XBLOCK_UPDATED,
|
||||
)
|
||||
from openedx_events.tests.utils import OpenEdxEventsTestMixin
|
||||
from openedx_tagging.core.tagging.models import Tag
|
||||
from organizations.models import Organization
|
||||
from xmodule.modulestore.django import contentstore, modulestore
|
||||
@@ -393,10 +400,16 @@ class ClipboardPasteTestCase(ModuleStoreTestCase):
|
||||
assert source_pic2_hash != dest_pic2_hash # Because there was a conflict, this file was unchanged.
|
||||
|
||||
|
||||
class ClipboardPasteFromV2LibraryTestCase(ModuleStoreTestCase):
|
||||
class ClipboardPasteFromV2LibraryTestCase(OpenEdxEventsTestMixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Test Clipboard Paste functionality with a "new" (as of Sumac) library
|
||||
"""
|
||||
ENABLED_OPENEDX_EVENTS = [
|
||||
LIBRARY_BLOCK_DELETED.event_type,
|
||||
XBLOCK_CREATED.event_type,
|
||||
XBLOCK_DELETED.event_type,
|
||||
XBLOCK_UPDATED.event_type,
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
@@ -477,6 +490,16 @@ class ClipboardPasteFromV2LibraryTestCase(ModuleStoreTestCase):
|
||||
assert object_tag.value in self.lib_block_tags
|
||||
assert object_tag.is_copied
|
||||
|
||||
# If we delete the upstream library block...
|
||||
library_api.delete_library_block(self.lib_block_key)
|
||||
|
||||
# ...the copied tags remain, but should no longer be marked as "copied"
|
||||
object_tags = tagging_api.get_object_tags(new_block_key)
|
||||
assert len(object_tags) == len(self.lib_block_tags)
|
||||
for object_tag in object_tags:
|
||||
assert object_tag.value in self.lib_block_tags
|
||||
assert not object_tag.is_copied
|
||||
|
||||
def test_paste_from_library_copies_asset(self):
|
||||
"""
|
||||
Assets from a library component copied into a subdir of Files & Uploads.
|
||||
|
||||
@@ -441,3 +441,4 @@ resync_object_tags = oel_tagging.resync_object_tags
|
||||
get_object_tags = oel_tagging.get_object_tags
|
||||
add_tag_to_taxonomy = oel_tagging.add_tag_to_taxonomy
|
||||
copy_tags_as_read_only = oel_tagging.copy_tags
|
||||
make_copied_tags_editable = oel_tagging.unmark_copied_tags
|
||||
|
||||
@@ -131,7 +131,7 @@ optimizely-sdk<5.0
|
||||
# Date: 2023-09-18
|
||||
# pinning this version to avoid updates while the library is being developed
|
||||
# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269
|
||||
openedx-learning==0.18.2
|
||||
openedx-learning==0.18.3
|
||||
|
||||
# Date: 2023-11-29
|
||||
# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise.
|
||||
|
||||
@@ -827,7 +827,7 @@ openedx-filters==1.12.0
|
||||
# ora2
|
||||
openedx-forum==0.1.6
|
||||
# via -r requirements/edx/kernel.in
|
||||
openedx-learning==0.18.2
|
||||
openedx-learning==0.18.3
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/kernel.in
|
||||
|
||||
@@ -1381,7 +1381,7 @@ openedx-forum==0.1.6
|
||||
# via
|
||||
# -r requirements/edx/doc.txt
|
||||
# -r requirements/edx/testing.txt
|
||||
openedx-learning==0.18.2
|
||||
openedx-learning==0.18.3
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/doc.txt
|
||||
|
||||
@@ -1000,7 +1000,7 @@ openedx-filters==1.12.0
|
||||
# ora2
|
||||
openedx-forum==0.1.6
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-learning==0.18.2
|
||||
openedx-learning==0.18.3
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
|
||||
@@ -1048,7 +1048,7 @@ openedx-filters==1.12.0
|
||||
# ora2
|
||||
openedx-forum==0.1.6
|
||||
# via -r requirements/edx/base.txt
|
||||
openedx-learning==0.18.2
|
||||
openedx-learning==0.18.3
|
||||
# via
|
||||
# -c requirements/edx/../constraints.txt
|
||||
# -r requirements/edx/base.txt
|
||||
|
||||
Reference in New Issue
Block a user