Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions openedx/core/djangoapps/content_libraries/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content_libraries import api as library_api
from openedx.core.djangoapps.xblock.api import load_block
from openedx.core.djangoapps.content_tagging import api as tagging_api
from openedx.core.lib import ensure_cms
from xmodule.capa_block import ProblemBlock
from xmodule.library_content_block import ANY_CAPA_TYPE_VALUE, LibraryContentBlock
Expand Down Expand Up @@ -143,6 +144,9 @@ def generate_block_key(source_key, dest_parent_key):
new_block.save()
store.update_item(new_block, user_id)

# Copy/Sync tags of this block
tagging_api.copy_tags(source_key, new_block_key)

if new_block.has_children:
# Delete existing children in the new block, which can be reimported again if they still exist in the
# source library
Expand Down
1 change: 1 addition & 0 deletions openedx/core/djangoapps/content_tagging/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,4 @@ def tag_object(
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 = oel_tagging.copy_tags
22 changes: 22 additions & 0 deletions openedx/core/djangoapps/content_tagging/rest_api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from rest_framework import serializers, fields

from openedx_tagging.core.tagging.rest_api.v1.serializers import (
ObjectTagMinimalSerializer,
TaxonomyListQueryParamsSerializer,
TaxonomySerializer,
)
Expand Down Expand Up @@ -94,3 +95,24 @@ class Meta:
model = TaxonomySerializer.Meta.model
fields = TaxonomySerializer.Meta.fields + ["orgs", "all_orgs"]
read_only_fields = ["orgs", "all_orgs"]


class ObjectTagCopiedMinimalSerializer(ObjectTagMinimalSerializer):
"""
Serializer for Object Tags.

This override `get_can_delete_objecttag` to avoid delete
object tags if is copied.
"""

def get_can_delete_objecttag(self, instance):
"""
Verify if the user can delete the object tag.

Override to return `False` if the object tag is copied.
"""
if instance.is_copied:
# The user can't delete copied tags.
return False

return super().get_can_delete_objecttag(instance)
Original file line number Diff line number Diff line change
Expand Up @@ -1850,6 +1850,31 @@ def test_get_tags(self):
assert status.is_success(response3.status_code)
assert response3.data[str(self.courseA)]["taxonomies"] == expected_tags

def test_get_copied_tags(self):
self.client.force_authenticate(user=self.staffB)

object_id_1 = str(self.courseA)
object_id_2 = str(self.courseB)
tagging_api.tag_object(object_id=object_id_1, taxonomy=self.t1, tags=["android"])
tagging_api.tag_object(object_id=object_id_2, taxonomy=self.t1, tags=["anvil"])
tagging_api.copy_tags(object_id_1, object_id_2)

expected_tags = [{
'name': self.t1.name,
'taxonomy_id': self.t1.pk,
'can_tag_object': True,
'export_id': self.t1.export_id,
'tags': [
{'value': 'android', 'lineage': ['ALPHABET', 'android'], 'can_delete_objecttag': False},
{'value': 'anvil', 'lineage': ['ALPHABET', 'anvil'], 'can_delete_objecttag': True}
]
}]

get_url = OBJECT_TAGS_URL.format(object_id=self.courseB)
response = self.client.get(get_url, format="json")
assert status.is_success(response.status_code)
assert response.data[str(object_id_2)]["taxonomies"] == expected_tags

@ddt.data(
('staff', 'courseA', 8),
('staff', 'libraryA', 8),
Expand Down
8 changes: 7 additions & 1 deletion openedx/core/djangoapps/content_tagging/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@
)
from ...rules import get_admin_orgs
from .filters import ObjectTagTaxonomyOrgFilterBackend, UserOrgFilterBackend
from .serializers import TaxonomyOrgListQueryParamsSerializer, TaxonomyOrgSerializer, TaxonomyUpdateOrgBodySerializer
from .serializers import (
ObjectTagCopiedMinimalSerializer,
TaxonomyOrgListQueryParamsSerializer,
TaxonomyOrgSerializer,
TaxonomyUpdateOrgBodySerializer,
)


class TaxonomyOrgView(TaxonomyView):
Expand Down Expand Up @@ -148,6 +153,7 @@ class ObjectTagOrgView(ObjectTagView):

Refer to ObjectTagView docstring for usage details.
"""
minimal_serializer_class = ObjectTagCopiedMinimalSerializer
filter_backends = [ObjectTagTaxonomyOrgFilterBackend]

def update(self, request, *args, **kwargs) -> Response:
Expand Down
2 changes: 1 addition & 1 deletion requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ libsass==0.10.0
click==8.1.6

# pinning this version to avoid updates while the library is being developed
openedx-learning==0.13.1
openedx-learning==0.16.0

# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise.
openai<=0.28.1
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ openedx-filters==1.10.0
# -r requirements/edx/kernel.in
# lti-consumer-xblock
# ora2
openedx-learning==0.13.1
openedx-learning @ git+https://github.com/open-craft/openedx-learning.git@chris/FAL-3616-copy-tags
Comment thread
ChrisChV marked this conversation as resolved.
Outdated
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1373,7 +1373,7 @@ openedx-filters==1.10.0
# -r requirements/edx/testing.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.13.1
openedx-learning @ git+https://github.com/open-craft/openedx-learning.git@chris/FAL-3616-copy-tags
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,7 @@ openedx-filters==1.10.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.13.1
openedx-learning @ git+https://github.com/open-craft/openedx-learning.git@chris/FAL-3616-copy-tags
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/kernel.in
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ openedx-calc # Library supporting mathematical calculatio
openedx-django-require
openedx-events # Open edX Events from Hooks Extension Framework (OEP-50)
openedx-filters # Open edX Filters from Hooks Extension Framework (OEP-50)
openedx-learning # Open edX Learning core (experimental)
git+https://github.com/open-craft/openedx-learning.git@chris/FAL-3616-copy-tags#egg=openedx-learning # Open edX Learning core (experimental)
openedx-mongodbproxy
openedx-django-wiki
path
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,7 @@ openedx-filters==1.10.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.13.1
openedx-learning @ git+https://github.com/open-craft/openedx-learning.git@chris/FAL-3616-copy-tags
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
Expand Down
16 changes: 14 additions & 2 deletions xmodule/tests/test_library_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@
from common.djangoapps.student.tests.factories import UserFactory
from openedx.core.djangolib.testing.utils import skip_unless_cms
from openedx.core.djangoapps.content_libraries.tests.base import ContentLibrariesRestApiTest
from openedx.core.djangoapps.content_tagging import api as tagging_api
from openedx.core.djangoapps.content_tagging.tests.test_api import TestTaxonomyMixin
from xmodule.library_tools import LibraryToolsService
from xmodule.modulestore.tests.factories import CourseFactory, LibraryFactory
from xmodule.modulestore.tests.utils import MixedSplitTestCase


@skip_unless_cms
@ddt.ddt
class ContentLibraryToolsTest(MixedSplitTestCase, ContentLibrariesRestApiTest):
class ContentLibraryToolsTest(TestTaxonomyMixin, MixedSplitTestCase, ContentLibrariesRestApiTest):
"""
Tests for LibraryToolsService.

Expand Down Expand Up @@ -104,7 +106,8 @@ def test_update_children_for_v2_lib(self):
library = self._create_library(
slug="cool-v2-lib", title="The best Library", description="Spectacular description"
)
self._add_block_to_library(library["id"], "unit", "unit1_id")
block = self._add_block_to_library(library["id"], "unit", "unit1_id")
tagging_api.tag_object(block["id"], self.taxonomy_all_orgs, [self.tag_all_orgs.value])

course = CourseFactory.create(modulestore=self.store, user_id=self.user.id)
CourseInstructorRole(course.id).add_users(self.user)
Expand All @@ -126,6 +129,15 @@ def test_update_children_for_v2_lib(self):

assert len(content_block.children) == 1

# Verify that tags are copied to children
child_key = str(content_block.children[0])
tags = tagging_api.get_object_tags(child_key)
assert len(tags) == 1
object_tag = tags[0]
assert object_tag.value == self.tag_all_orgs.value
assert object_tag.taxonomy == self.taxonomy_all_orgs
assert object_tag.object_id == child_key

def test_update_children_for_v1_lib(self):
"""
Test update_children with V1 library as a source.
Expand Down