From 215e89961d2a9d7171ebe699ebadacd77a19d469 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 13 Mar 2025 16:33:24 -0500 Subject: [PATCH 1/3] temp: Use opencraft branch of opaquekeys --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/kernel.in | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 17f3e804ee9c..ac4b3a010589 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -489,7 +489,7 @@ edx-milestones==0.6.0 # via -r requirements/edx/kernel.in edx-name-affirmation==3.0.1 # via -r requirements/edx/kernel.in -edx-opaque-keys[django]==2.11.0 +edx-opaque-keys[django] @ git+https://github.com/open-craft/opaque-keys.git@chris/FAL-4108-create-container-keys # via # -r requirements/edx/kernel.in # edx-bulk-grades diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 11efa011521e..61000a05117c 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -780,7 +780,7 @@ edx-name-affirmation==3.0.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-opaque-keys[django]==2.11.0 +edx-opaque-keys[django] @ git+https://github.com/open-craft/opaque-keys.git@chris/FAL-4108-create-container-keys # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 4c0808043772..4211358ab8a4 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -573,7 +573,7 @@ edx-milestones==0.6.0 # via -r requirements/edx/base.txt edx-name-affirmation==3.0.1 # via -r requirements/edx/base.txt -edx-opaque-keys[django]==2.11.0 +edx-opaque-keys[django] @ git+https://github.com/open-craft/opaque-keys.git@chris/FAL-4108-create-container-keys # via # -r requirements/edx/base.txt # edx-bulk-grades diff --git a/requirements/edx/kernel.in b/requirements/edx/kernel.in index a17b9db4c868..4b8df657decd 100644 --- a/requirements/edx/kernel.in +++ b/requirements/edx/kernel.in @@ -75,7 +75,7 @@ edx-event-bus-kafka>=5.6.0 # Kafka implementation of event bus edx-event-bus-redis edx-milestones edx-name-affirmation -edx-opaque-keys +git+https://github.com/open-craft/opaque-keys.git@chris/FAL-4108-create-container-keys#egg=edx-opaque-keys edx-organizations edx-proctoring>=2.0.1 # using hash to support django42 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 44e2e5c6fbf4..73167524d2bf 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -600,7 +600,7 @@ edx-milestones==0.6.0 # via -r requirements/edx/base.txt edx-name-affirmation==3.0.1 # via -r requirements/edx/base.txt -edx-opaque-keys[django]==2.11.0 +edx-opaque-keys[django] @ git+https://github.com/open-craft/opaque-keys.git@chris/FAL-4108-create-container-keys # via # -r requirements/edx/base.txt # edx-bulk-grades From a34944846b5ed20134152851fcabbe0000e431d6 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 13 Mar 2025 16:34:04 -0500 Subject: [PATCH 2/3] refactor: Use LibraryElementKey instead of LibraryCollectionKey --- openedx/core/djangoapps/content_tagging/api.py | 4 ++-- .../core/djangoapps/content_tagging/tests/test_api.py | 6 +++--- openedx/core/djangoapps/content_tagging/types.py | 4 ++-- openedx/core/djangoapps/content_tagging/utils.py | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openedx/core/djangoapps/content_tagging/api.py b/openedx/core/djangoapps/content_tagging/api.py index 96f6886b622b..8a11d8d1cb6b 100644 --- a/openedx/core/djangoapps/content_tagging/api.py +++ b/openedx/core/djangoapps/content_tagging/api.py @@ -12,7 +12,7 @@ import openedx_tagging.core.tagging.api as oel_tagging from django.db.models import Exists, OuterRef, Q, QuerySet from django.utils.timezone import now -from opaque_keys.edx.keys import CourseKey, LibraryCollectionKey +from opaque_keys.edx.keys import CourseKey, LibraryElementKey from opaque_keys.edx.locator import LibraryLocatorV2 from openedx_tagging.core.tagging.models import ObjectTag, Taxonomy from openedx_tagging.core.tagging.models.utils import TAGS_CSV_SEPARATOR @@ -230,7 +230,7 @@ def generate_csv_rows(object_id, buffer) -> Iterator[str]: """ content_key = get_content_key_from_string(object_id) - if isinstance(content_key, (UsageKey, LibraryCollectionKey)): + if isinstance(content_key, (UsageKey, LibraryElementKey)): raise ValueError("The object_id must be a CourseKey or a LibraryLocatorV2.") all_object_tags, taxonomies = get_all_object_tags(content_key) diff --git a/openedx/core/djangoapps/content_tagging/tests/test_api.py b/openedx/core/djangoapps/content_tagging/tests/test_api.py index b693f7ee0f56..d85c87d62b17 100644 --- a/openedx/core/djangoapps/content_tagging/tests/test_api.py +++ b/openedx/core/djangoapps/content_tagging/tests/test_api.py @@ -5,8 +5,8 @@ import ddt from django.test.testcases import TestCase from fs.osfs import OSFS -from opaque_keys.edx.keys import CourseKey, UsageKey, LibraryCollectionKey -from opaque_keys.edx.locator import LibraryLocatorV2 +from opaque_keys.edx.keys import CourseKey, UsageKey +from opaque_keys.edx.locator import LibraryLocatorV2, LibraryCollectionLocator from openedx_tagging.core.tagging.models import ObjectTag from organizations.models import Organization from .test_objecttag_export_helpers import TestGetAllObjectTagsMixin, TaggedCourseMixin @@ -381,7 +381,7 @@ def test_copy_cross_org_tags(self): self._test_copy_object_tags(src_key, dst_key, expected_tags) def test_tag_collection(self): - collection_key = LibraryCollectionKey.from_string("lib-collection:orgA:libX:1") + collection_key = LibraryCollectionLocator.from_string("lib-collection:orgA:libX:1") api.tag_object( object_id=str(collection_key), diff --git a/openedx/core/djangoapps/content_tagging/types.py b/openedx/core/djangoapps/content_tagging/types.py index 9ffb090d61e3..c630a8bff107 100644 --- a/openedx/core/djangoapps/content_tagging/types.py +++ b/openedx/core/djangoapps/content_tagging/types.py @@ -5,11 +5,11 @@ from typing import Dict, List, Union -from opaque_keys.edx.keys import CourseKey, UsageKey, LibraryCollectionKey +from opaque_keys.edx.keys import CourseKey, UsageKey, LibraryElementKey from opaque_keys.edx.locator import LibraryLocatorV2 from openedx_tagging.core.tagging.models import Taxonomy -ContentKey = Union[LibraryLocatorV2, CourseKey, UsageKey, LibraryCollectionKey] +ContentKey = Union[LibraryLocatorV2, CourseKey, UsageKey, LibraryElementKey] ContextKey = Union[LibraryLocatorV2, CourseKey] TagValuesByTaxonomyIdDict = Dict[int, List[str]] diff --git a/openedx/core/djangoapps/content_tagging/utils.py b/openedx/core/djangoapps/content_tagging/utils.py index 39dd925c1acd..7373e5190ae1 100644 --- a/openedx/core/djangoapps/content_tagging/utils.py +++ b/openedx/core/djangoapps/content_tagging/utils.py @@ -5,7 +5,7 @@ from edx_django_utils.cache import RequestCache from opaque_keys import InvalidKeyError -from opaque_keys.edx.keys import CourseKey, UsageKey, LibraryCollectionKey +from opaque_keys.edx.keys import CourseKey, UsageKey, LibraryElementKey from opaque_keys.edx.locator import LibraryLocatorV2 from openedx_tagging.core.tagging.models import Taxonomy from organizations.models import Organization @@ -28,11 +28,11 @@ def get_content_key_from_string(key_str: str) -> ContentKey: return UsageKey.from_string(key_str) except InvalidKeyError: try: - return LibraryCollectionKey.from_string(key_str) + return LibraryElementKey.from_string(key_str) except InvalidKeyError as usage_key_error: raise ValueError( "object_id must be one of the following " - "keys: CourseKey, LibraryLocatorV2, UsageKey or LibCollectionKey" + "keys: CourseKey, LibraryLocatorV2, UsageKey or LibraryElementKey" ) from usage_key_error @@ -44,8 +44,8 @@ def get_context_key_from_key(content_key: ContentKey) -> ContextKey: if isinstance(content_key, (CourseKey, LibraryLocatorV2)): return content_key - # If the content key is a LibraryCollectionKey, return the LibraryLocatorV2 - if isinstance(content_key, LibraryCollectionKey): + # If the content key is a LibraryElementKey, return the LibraryLocatorV2 + if isinstance(content_key, LibraryElementKey): return content_key.library_key # If the content key is a UsageKey, return the context key From 8f3c29e1e921d46b0045ee648e8531e21fe39252 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 13 Mar 2025 17:26:24 -0500 Subject: [PATCH 3/3] feat: Library Units API with get_library_unit_usage_key --- .../core/djangoapps/content_libraries/api.py | 3 ++ .../djangoapps/content_libraries/constants.py | 3 ++ .../content_libraries/units/__init__.py | 0 .../djangoapps/content_libraries/units/api.py | 30 +++++++++++++++++++ .../content_libraries/units/tests/__init__.py | 0 .../content_libraries/units/tests/test_api.py | 26 ++++++++++++++++ 6 files changed, 62 insertions(+) create mode 100644 openedx/core/djangoapps/content_libraries/units/__init__.py create mode 100644 openedx/core/djangoapps/content_libraries/units/api.py create mode 100644 openedx/core/djangoapps/content_libraries/units/tests/__init__.py create mode 100644 openedx/core/djangoapps/content_libraries/units/tests/test_api.py diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 15d628a74aed..4457a14ca450 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -120,6 +120,9 @@ from .constants import ALL_RIGHTS_RESERVED from .models import ContentLibrary, ContentLibraryPermission, ContentLibraryBlockImportTask +# Import Units API Module for use when importing the content_libraries API module +from .units.api import * + log = logging.getLogger(__name__) diff --git a/openedx/core/djangoapps/content_libraries/constants.py b/openedx/core/djangoapps/content_libraries/constants.py index a01e0fbdda91..12217023a1a4 100644 --- a/openedx/core/djangoapps/content_libraries/constants.py +++ b/openedx/core/djangoapps/content_libraries/constants.py @@ -11,6 +11,9 @@ CC_4_BY_ND = 'CC:4.0:BY:ND' CC_4_BY_SA = 'CC:4.0:BY:SA' +# Container types +CONTAINER_UNIT_TYPE = 'unit' + LICENSE_OPTIONS = ( (ALL_RIGHTS_RESERVED, _('All Rights Reserved.')), (CC_4_BY, _('Creative Commons Attribution 4.0')), diff --git a/openedx/core/djangoapps/content_libraries/units/__init__.py b/openedx/core/djangoapps/content_libraries/units/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/core/djangoapps/content_libraries/units/api.py b/openedx/core/djangoapps/content_libraries/units/api.py new file mode 100644 index 000000000000..21a165e91bd6 --- /dev/null +++ b/openedx/core/djangoapps/content_libraries/units/api.py @@ -0,0 +1,30 @@ +""" +Python API for units in content libraries +================================ + +Via ``views.py``, most of these API methods are also exposed as a REST API. + +The API methods in this file are focused on authoring and specific to units +in content libraries. + +To import this API methods you can use: + + from openedx.core.djangoapps.content_libraries import api as library_api + +""" +from __future__ import annotations + +from opaque_keys.edx.locator import LibraryLocatorV2, LibraryContainerLocator + +from ..constants import CONTAINER_UNIT_TYPE + + +def get_library_unit_usage_key( + library_key: LibraryLocatorV2, + unit_key: str, +) -> LibraryContainerLocator: + """ + Returns the LibraryContainerLocator associated to a Unit. + """ + + return LibraryContainerLocator(library_key, CONTAINER_UNIT_TYPE, unit_key) diff --git a/openedx/core/djangoapps/content_libraries/units/tests/__init__.py b/openedx/core/djangoapps/content_libraries/units/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/core/djangoapps/content_libraries/units/tests/test_api.py b/openedx/core/djangoapps/content_libraries/units/tests/test_api.py new file mode 100644 index 000000000000..db1298637b61 --- /dev/null +++ b/openedx/core/djangoapps/content_libraries/units/tests/test_api.py @@ -0,0 +1,26 @@ +""" +Tests for Units internal api. +""" +from django.test import TestCase + +from opaque_keys.edx.locator import LibraryLocatorV2, LibraryContainerLocator + +from .. import api + +class UnitLibraryTest(TestCase): + + library_key_str = 'lib:foobar_content:foobar_library' + + def test_get_library_unit_usage_key(self): + """ + Test build the unit usage key + """ + library_key = LibraryLocatorV2.from_string(self.library_key_str) + unit_id = 'test-unit' + expected_key = f'lct:{library_key.org}:{library_key.slug}:unit:{unit_id}' + + unit_key = api.get_library_unit_usage_key(library_key, unit_id) + + assert unit_key.library_key == library_key + assert unit_key.container_id == unit_id + assert str(unit_key) == expected_key