Skip to content

Commit

Permalink
feat: Sync tags when sync upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Oct 18, 2024
1 parent b6bac86 commit 7a1797a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 5 deletions.
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ def copy_tags():
)

# Copy content tags to the new xblock
if (new_xblock.upstream):
if new_xblock.upstream:
# Verify if the upstream is a library component
# Copy the tags from library component upstream as ready only
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ def setUp(self):
tagging_api.add_tag_to_taxonomy(taxonomy_all_org, tag_value)

self.lib_block_tags = ['tag_1', 'tag_5']
tagging_api.tag_object(str(self.lib_block_key), taxonomy_all_org, ['tag_1', 'tag_5'])
tagging_api.tag_object(str(self.lib_block_key), taxonomy_all_org, self.lib_block_tags)

def test_paste_from_library_creates_link(self):
"""
Expand Down Expand Up @@ -495,9 +495,7 @@ def test_paste_from_library_read_only_tags(self):

object_tags = tagging_api.get_object_tags(new_block_key)
assert len(object_tags) == len(self.lib_block_tags)
print(object_tags)
for object_tag in object_tags:
print(object_tag)
assert object_tag.value in self.lib_block_tags
assert object_tag.is_copied

Expand Down
64 changes: 64 additions & 0 deletions cms/lib/xblock/test/test_upstream_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from common.djangoapps.student.tests.factories import UserFactory
from openedx.core.djangoapps.content_libraries import api as libs
from openedx.core.djangoapps.content_tagging import api as tagging_api
from openedx.core.djangoapps.xblock import api as xblock
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory
Expand Down Expand Up @@ -48,6 +49,18 @@ def setUp(self):
upstream.data = "<html><body>Upstream content V2</body></html>"
upstream.save()

self.taxonomy_all_org = tagging_api.create_taxonomy(
"test_taxonomy",
"Test Taxonomy",
export_id="ALL_ORGS",
)
tagging_api.set_taxonomy_orgs(self.taxonomy_all_org, all_orgs=True)
for tag_value in ('tag_1', 'tag_2', 'tag_3', 'tag_4', 'tag_5', 'tag_6', 'tag_7'):
tagging_api.add_tag_to_taxonomy(self.taxonomy_all_org, tag_value)

self.upstream_tags = ['tag_1', 'tag_5']
tagging_api.tag_object(str(self.upstream_key), self.taxonomy_all_org, self.upstream_tags)

def test_sync_bad_downstream(self):
"""
Syncing into an unsupported downstream (such as a another Content Library block) raises BadDownstream, but
Expand Down Expand Up @@ -127,11 +140,19 @@ def test_sync_updates_happy_path(self):
assert downstream.display_name == "Upstream Title V2"
assert downstream.data == "<html><body>Upstream content V2</body></html>"

# Verify tags
object_tags = tagging_api.get_object_tags(str(downstream.location))
assert len(object_tags) == len(self.upstream_tags)
for object_tag in object_tags:
assert object_tag.value in self.upstream_tags

# Upstream updates
upstream = xblock.load_block(self.upstream_key, self.user)
upstream.display_name = "Upstream Title V3"
upstream.data = "<html><body>Upstream content V3</body></html>"
upstream.save()
new_upstream_tags = self.upstream_tags + ['tag_2', 'tag_3']
tagging_api.tag_object(str(self.upstream_key), self.taxonomy_all_org, new_upstream_tags)

# Follow-up sync. Assert that updates are pulled into downstream.
sync_from_upstream(downstream, self.user)
Expand All @@ -140,6 +161,12 @@ def test_sync_updates_happy_path(self):
assert downstream.display_name == "Upstream Title V3"
assert downstream.data == "<html><body>Upstream content V3</body></html>"

# Verify tags
object_tags = tagging_api.get_object_tags(str(downstream.location))
assert len(object_tags) == len(new_upstream_tags)
for object_tag in object_tags:
assert object_tag.value in new_upstream_tags

def test_sync_updates_to_modified_content(self):
"""
If we sync to modified content, will it preserve customizable fields, but overwrite the rest?
Expand Down Expand Up @@ -335,3 +362,40 @@ def test_sever_upstream_link(self):

# AND, we have recorded the old upstream as our copied_from_block.
assert downstream.copied_from_block == str(self.upstream_key)

def test_sync_library_block_tags(self):
upstream_lib_block_key = libs.create_library_block(self.library.key, "html", "upstream").usage_key
upstream_lib_block = xblock.load_block(upstream_lib_block_key, self.user)
upstream_lib_block.display_name = "Another lib block"
upstream_lib_block.data = "<html>another lib block</html>"
upstream_lib_block.save()

expected_tags = self.upstream_tags
tagging_api.tag_object(str(upstream_lib_block_key), self.taxonomy_all_org, expected_tags)

downstream = BlockFactory.create(category='html', parent=self.unit, upstream=str(upstream_lib_block_key))

# Initial sync
sync_from_upstream(downstream, self.user)

# Verify tags
object_tags = tagging_api.get_object_tags(str(downstream.location))
assert len(object_tags) == len(expected_tags)
for object_tag in object_tags:
assert object_tag.value in expected_tags

# Upstream updates
upstream_lib_block.display_name = "Upstream Title V3"
upstream_lib_block.data = "<html><body>Upstream content V3</body></html>"
upstream_lib_block.save()
new_upstream_tags = self.upstream_tags + ['tag_2', 'tag_3']
tagging_api.tag_object(str(upstream_lib_block_key), self.taxonomy_all_org, new_upstream_tags)

# Follow-up sync.
sync_from_upstream(downstream, self.user)

#Verify tags
object_tags = tagging_api.get_object_tags(str(downstream.location))
assert len(object_tags) == len(new_upstream_tags)
for object_tag in object_tags:
assert object_tag.value in new_upstream_tags
18 changes: 17 additions & 1 deletion cms/lib/xblock/upstream_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
if t.TYPE_CHECKING:
from django.contrib.auth.models import User # pylint: disable=imported-auth-user


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -187,6 +186,7 @@ def sync_from_upstream(downstream: XBlock, user: User) -> None:
link, upstream = _load_upstream_link_and_block(downstream, user)
_update_customizable_fields(upstream=upstream, downstream=downstream, only_fetch=False)
_update_non_customizable_fields(upstream=upstream, downstream=downstream)
_update_tags(upstream=upstream, downstream=downstream)
downstream.upstream_version = link.version_available


Expand Down Expand Up @@ -284,6 +284,22 @@ def _update_non_customizable_fields(*, upstream: XBlock, downstream: XBlock) ->
setattr(downstream, field_name, new_upstream_value)


def _update_tags(*, upstream: XBlock, downstream: XBlock) -> None:
"""
Update tags from `upstream` to `downstream`
"""
from openedx.core.djangoapps.content_tagging.api import copy_object_tags, copy_tags_as_read_only
if isinstance(upstream.location, LibraryUsageLocatorV2):
# If is a library component, then update the tags as read_only
# This keeps tags added locally.
copy_tags_as_read_only(
str(upstream.location),
str(downstream.location),
)
else:
copy_object_tags(upstream.location, downstream.location)


def _get_synchronizable_fields(upstream: XBlock, downstream: XBlock) -> set[str]:
"""
The syncable fields are the ones which are content- or settings-scoped AND are defined on both (up,down)stream.
Expand Down

0 comments on commit 7a1797a

Please sign in to comment.