feat: paste tags when pasting xblocks with tag data (#34270)

This commit is contained in:
Rômulo Penido
2024-03-08 17:03:43 -03:00
committed by GitHub
parent 4a2e0f7df5
commit cb6801dbfd
17 changed files with 320 additions and 49 deletions

View File

@@ -13,7 +13,8 @@ from openedx_tagging.core.tagging.models import ObjectTag, Taxonomy
from organizations.models import Organization
from .models import TaxonomyOrg
from .types import ObjectTagByObjectIdDict, TaxonomyDict
from .types import ContentKey, ObjectTagByObjectIdDict, TagValuesByTaxonomyExportIdDict, TaxonomyDict
from .utils import check_taxonomy_context_key_org, get_context_key_from_key
def create_taxonomy(
@@ -161,16 +162,42 @@ def get_all_object_tags(
for object_id, block_tags in groupby(all_object_tags, lambda x: x.object_id):
grouped_object_tags[object_id] = {}
for taxonomy_id, taxonomy_tags in groupby(block_tags, lambda x: x.tag.taxonomy_id):
for taxonomy_id, taxonomy_tags in groupby(block_tags, lambda x: x.tag.taxonomy_id if x.tag else 0):
object_tags_list = list(taxonomy_tags)
grouped_object_tags[object_id][taxonomy_id] = object_tags_list
if taxonomy_id not in taxonomies:
assert object_tags_list[0].tag
assert object_tags_list[0].tag.taxonomy
taxonomies[taxonomy_id] = object_tags_list[0].tag.taxonomy
return grouped_object_tags, taxonomies
def set_object_tags(
content_key: ContentKey,
object_tags: TagValuesByTaxonomyExportIdDict,
) -> None:
"""
Sets the tags for the given content object.
"""
context_key = get_context_key_from_key(content_key)
for taxonomy_export_id, tags_values in object_tags.items():
taxonomy = oel_tagging.get_taxonomy_by_export_id(taxonomy_export_id)
if not taxonomy:
continue
if not check_taxonomy_context_key_org(taxonomy, context_key):
continue
oel_tagging.tag_object(
object_id=str(content_key),
taxonomy=taxonomy,
tags=tags_values,
)
# Expose the oel_tagging APIs
get_taxonomy = oel_tagging.get_taxonomy
@@ -181,3 +208,4 @@ delete_object_tags = oel_tagging.delete_object_tags
resync_object_tags = oel_tagging.resync_object_tags
get_object_tags = oel_tagging.get_object_tags
tag_object = oel_tagging.tag_object
add_tag_to_taxonomy = oel_tagging.add_tag_to_taxonomy

View File

@@ -20,10 +20,9 @@ from common.djangoapps.student.roles import (
)
from .models import TaxonomyOrg
from .utils import get_context_key_from_key_string, TaggingRulesCache
from .utils import check_taxonomy_context_key_org, get_context_key_from_key_string, rules_cache
rules_cache = TaggingRulesCache()
UserType = Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]
@@ -288,19 +287,12 @@ def can_change_object_tag(
"""
if oel_tagging.can_change_object_tag(user, perm_obj):
if perm_obj and perm_obj.taxonomy and perm_obj.object_id:
# can_change_object_tag_objectid already checked that object_id is valid and has an org,
# so these statements will not fail. But we need to assert to keep the type checker happy.
try:
context_key = get_context_key_from_key_string(perm_obj.object_id)
assert context_key.org
except (ValueError, AssertionError):
except ValueError:
return False # pragma: no cover
is_all_org, taxonomy_orgs = TaxonomyOrg.get_organizations(perm_obj.taxonomy)
if not is_all_org:
# Ensure the object_id's org is among the allowed taxonomy orgs
object_org = rules_cache.get_orgs([context_key.org])
return bool(object_org) and object_org[0] in taxonomy_orgs
return check_taxonomy_context_key_org(perm_obj.taxonomy, context_key)
return True
return False

View File

@@ -10,7 +10,9 @@ from opaque_keys.edx.locator import LibraryLocatorV2
from openedx_tagging.core.tagging.models import ObjectTag, Taxonomy
ContentKey = Union[LibraryLocatorV2, CourseKey, UsageKey]
ContextKey = Union[LibraryLocatorV2, CourseKey]
ObjectTagByTaxonomyIdDict = Dict[int, List[ObjectTag]]
ObjectTagByObjectIdDict = Dict[str, ObjectTagByTaxonomyIdDict]
TaxonomyDict = Dict[int, Taxonomy]
TagValuesByTaxonomyExportIdDict = Dict[str, List[str]]

View File

@@ -7,11 +7,13 @@ from edx_django_utils.cache import RequestCache
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys.edx.locator import LibraryLocatorV2
from openedx_tagging.core.tagging.models import Taxonomy
from organizations.models import Organization
from openedx.core.djangoapps.content_libraries.api import get_libraries_for_user
from .types import ContentKey
from .types import ContentKey, ContextKey
from .models import TaxonomyOrg
def get_content_key_from_string(key_str: str) -> ContentKey:
@@ -30,11 +32,10 @@ def get_content_key_from_string(key_str: str) -> ContentKey:
raise ValueError("object_id must be a CourseKey, LibraryLocatorV2 or a UsageKey") from usage_key_error
def get_context_key_from_key_string(key_str: str) -> CourseKey | LibraryLocatorV2:
def get_context_key_from_key(content_key: ContentKey) -> ContextKey:
"""
Get context key from an key string
Returns the context key from a given content key.
"""
content_key = get_content_key_from_string(key_str)
# If the content key is a CourseKey or a LibraryLocatorV2, return it
if isinstance(content_key, (CourseKey, LibraryLocatorV2)):
return content_key
@@ -48,6 +49,31 @@ def get_context_key_from_key_string(key_str: str) -> CourseKey | LibraryLocatorV
raise ValueError("context must be a CourseKey or a LibraryLocatorV2")
def get_context_key_from_key_string(key_str: str) -> ContextKey:
"""
Get context key from an key string
"""
content_key = get_content_key_from_string(key_str)
return get_context_key_from_key(content_key)
def check_taxonomy_context_key_org(taxonomy: Taxonomy, context_key: ContextKey) -> bool:
"""
Returns True if the given taxonomy can tag a object with the given context_key.
"""
if not context_key.org:
return False
is_all_org, taxonomy_orgs = TaxonomyOrg.get_organizations(taxonomy)
if is_all_org:
return True
# Ensure the object_id's org is among the allowed taxonomy orgs
object_org = rules_cache.get_orgs([context_key.org])
return bool(object_org) and object_org[0] in taxonomy_orgs
class TaggingRulesCache:
"""
Caches data required for computing rules for the duration of the request.
@@ -57,7 +83,7 @@ class TaggingRulesCache:
"""
Initializes the request cache.
"""
self.request_cache = RequestCache('openedx.core.djangoapps.content_tagging.rules')
self.request_cache = RequestCache('openedx.core.djangoapps.content_tagging.utils')
def get_orgs(self, org_names: list[str] | None = None) -> list[Organization]:
"""
@@ -102,3 +128,6 @@ class TaggingRulesCache:
return [
library_orgs[org_name] for org_name in org_names if org_name in library_orgs
]
rules_cache = TaggingRulesCache()