refactor: update to use Learning Core's new public API

This also bumps our openedx-learning dependency to 0.10.0 (the first
version with the new openedx_learning.api package).
This commit is contained in:
David Ormsbee
2024-05-16 13:08:02 -04:00
parent f820961de5
commit be03938377
13 changed files with 59 additions and 56 deletions

View File

@@ -1881,9 +1881,9 @@ INSTALLED_APPS = [
'openedx_events', 'openedx_events',
# Learning Core Apps, used by v2 content libraries (content_libraries app) # Learning Core Apps, used by v2 content libraries (content_libraries app)
"openedx_learning.core.components", "openedx_learning.apps.authoring.components",
"openedx_learning.core.contents", "openedx_learning.apps.authoring.contents",
"openedx_learning.core.publishing", "openedx_learning.apps.authoring.publishing",
] ]

View File

@@ -3385,9 +3385,9 @@ INSTALLED_APPS = [
'openedx_events', 'openedx_events',
# Learning Core Apps, used by v2 content libraries (content_libraries app) # Learning Core Apps, used by v2 content libraries (content_libraries app)
"openedx_learning.core.components", "openedx_learning.apps.authoring.components",
"openedx_learning.core.contents", "openedx_learning.apps.authoring.contents",
"openedx_learning.core.publishing", "openedx_learning.apps.authoring.publishing",
] ]

View File

@@ -85,10 +85,8 @@ from openedx_events.content_authoring.signals import (
LIBRARY_BLOCK_DELETED, LIBRARY_BLOCK_DELETED,
LIBRARY_BLOCK_UPDATED, LIBRARY_BLOCK_UPDATED,
) )
from openedx_learning.core.publishing import api as publishing_api from openedx_learning.api import authoring as authoring_api
from openedx_learning.core.contents import api as contents_api from openedx_learning.api.authoring_models import Component, MediaType
from openedx_learning.core.components import api as components_api
from openedx_learning.core.components.models import Component
from organizations.models import Organization from organizations.models import Organization
from xblock.core import XBlock from xblock.core import XBlock
from xblock.exceptions import XBlockNotFoundError from xblock.exceptions import XBlockNotFoundError
@@ -327,18 +325,18 @@ def get_library(library_key):
""" """
ref = ContentLibrary.objects.get_by_key(library_key) ref = ContentLibrary.objects.get_by_key(library_key)
learning_package = ref.learning_package learning_package = ref.learning_package
num_blocks = publishing_api.get_all_drafts(learning_package.id).count() num_blocks = authoring_api.get_all_drafts(learning_package.id).count()
last_publish_log = publishing_api.get_last_publish(learning_package.id) last_publish_log = authoring_api.get_last_publish(learning_package.id)
has_unpublished_changes = publishing_api.get_entities_with_unpublished_changes(learning_package.id) \ has_unpublished_changes = authoring_api.get_entities_with_unpublished_changes(learning_package.id) \
.exists() .exists()
# TODO: I'm doing this one to match already-existing behavior, but this is # TODO: I'm doing this one to match already-existing behavior, but this is
# something that we should remove. It exists to accomodate some complexities # something that we should remove. It exists to accomodate some complexities
# with how Blockstore staged changes, but Learning Core works differently, # with how Blockstore staged changes, but Learning Core works differently,
# and has_unpublished_changes should be sufficient. # and has_unpublished_changes should be sufficient.
# Ref: https://github.com/openedx/edx-platform/issues/34283 # Ref: https://github.com/openedx/edx-platform/issues/34283
has_unpublished_deletes = publishing_api.get_entities_with_unpublished_deletes(learning_package.id) \ has_unpublished_deletes = authoring_api.get_entities_with_unpublished_deletes(learning_package.id) \
.exists() .exists()
# Learning Core doesn't really have a notion of a global version number,but # Learning Core doesn't really have a notion of a global version number,but
# we can sort of approximate it by using the primary key of the last publish # we can sort of approximate it by using the primary key of the last publish
@@ -415,7 +413,7 @@ def create_library(
allow_public_read=allow_public_read, allow_public_read=allow_public_read,
license=library_license, license=library_license,
) )
learning_package = publishing_api.create_learning_package( learning_package = authoring_api.create_learning_package(
key=str(ref.library_key), key=str(ref.library_key),
title=title, title=title,
description=description, description=description,
@@ -556,7 +554,7 @@ def update_library(
content_lib.save() content_lib.save()
if learning_pkg_changed: if learning_pkg_changed:
publishing_api.update_learning_package( authoring_api.update_learning_package(
content_lib.learning_package_id, content_lib.learning_package_id,
title=title, title=title,
description=description, description=description,
@@ -614,7 +612,7 @@ def get_library_components(library_key, text_search=None, block_types=None) -> Q
""" """
lib = ContentLibrary.objects.get_by_key(library_key) # type: ignore[attr-defined] lib = ContentLibrary.objects.get_by_key(library_key) # type: ignore[attr-defined]
learning_package = lib.learning_package learning_package = lib.learning_package
components = components_api.get_components( components = authoring_api.get_components(
learning_package.id, learning_package.id,
draft=True, draft=True,
namespace='xblock.v1', namespace='xblock.v1',
@@ -693,13 +691,13 @@ def set_library_block_olx(usage_key, new_olx_str):
now = datetime.now(tz=timezone.utc) now = datetime.now(tz=timezone.utc)
with transaction.atomic(): with transaction.atomic():
new_content = contents_api.get_or_create_text_content( new_content = authoring_api.get_or_create_text_content(
component.learning_package_id, component.learning_package_id,
get_or_create_olx_media_type(usage_key.block_type).id, get_or_create_olx_media_type(usage_key.block_type).id,
text=new_olx_str, text=new_olx_str,
created=now, created=now,
) )
components_api.create_next_version( authoring_api.create_next_version(
component.pk, component.pk,
title=new_title, title=new_title,
content_to_replace={ content_to_replace={
@@ -736,7 +734,7 @@ def create_library_block(library_key, block_type, definition_id):
) )
# If adding a component would take us over our max, return an error. # If adding a component would take us over our max, return an error.
component_count = publishing_api.get_all_drafts(ref.learning_package.id).count() component_count = authoring_api.get_all_drafts(ref.learning_package.id).count()
if component_count + 1 > settings.MAX_BLOCKS_PER_CONTENT_LIBRARY: if component_count + 1 > settings.MAX_BLOCKS_PER_CONTENT_LIBRARY:
raise BlockLimitReachedError( raise BlockLimitReachedError(
_("Library cannot have more than {} Components").format( _("Library cannot have more than {} Components").format(
@@ -785,14 +783,14 @@ def _component_exists(usage_key: UsageKeyV2) -> bool:
return True return True
def get_or_create_olx_media_type(block_type: str) -> contents_api.MediaType: def get_or_create_olx_media_type(block_type: str) -> MediaType:
""" """
Get or create a MediaType for the block type. Get or create a MediaType for the block type.
Learning Core stores all Content with a Media Type (a.k.a. MIME type). For Learning Core stores all Content with a Media Type (a.k.a. MIME type). For
OLX, we use the "application/vnd.*" convention, per RFC 6838. OLX, we use the "application/vnd.*" convention, per RFC 6838.
""" """
return contents_api.get_or_create_media_type( return authoring_api.get_or_create_media_type(
f"application/vnd.openedx.xblock.v1.{block_type}+xml" f"application/vnd.openedx.xblock.v1.{block_type}+xml"
) )
@@ -819,10 +817,10 @@ def _create_component_for_block(content_lib, usage_key):
learning_package = content_lib.learning_package learning_package = content_lib.learning_package
with transaction.atomic(): with transaction.atomic():
component_type = components_api.get_or_create_component_type( component_type = authoring_api.get_or_create_component_type(
"xblock.v1", usage_key.block_type "xblock.v1", usage_key.block_type
) )
component, component_version = components_api.create_component_and_version( component, component_version = authoring_api.create_component_and_version(
learning_package.id, learning_package.id,
component_type=component_type, component_type=component_type,
local_key=usage_key.block_id, local_key=usage_key.block_id,
@@ -830,13 +828,13 @@ def _create_component_for_block(content_lib, usage_key):
created=now, created=now,
created_by=None, created_by=None,
) )
content = contents_api.get_or_create_text_content( content = authoring_api.get_or_create_text_content(
learning_package.id, learning_package.id,
get_or_create_olx_media_type(usage_key.block_type).id, get_or_create_olx_media_type(usage_key.block_type).id,
text=xml_text, text=xml_text,
created=now, created=now,
) )
components_api.create_component_version_content( authoring_api.create_component_version_content(
component_version.pk, component_version.pk,
content.id, content.id,
key="block.xml", key="block.xml",
@@ -849,7 +847,7 @@ def delete_library_block(usage_key, remove_from_parent=True):
Delete the specified block from this library (soft delete). Delete the specified block from this library (soft delete).
""" """
component = get_component_from_usage_key(usage_key) component = get_component_from_usage_key(usage_key)
publishing_api.soft_delete_draft(component.pk) authoring_api.soft_delete_draft(component.pk)
LIBRARY_BLOCK_DELETED.send_event( LIBRARY_BLOCK_DELETED.send_event(
library_block=LibraryBlockData( library_block=LibraryBlockData(
@@ -938,7 +936,7 @@ def publish_changes(library_key):
""" """
learning_package = ContentLibrary.objects.get_by_key(library_key).learning_package learning_package = ContentLibrary.objects.get_by_key(library_key).learning_package
publishing_api.publish_all_drafts(learning_package.id) authoring_api.publish_all_drafts(learning_package.id)
CONTENT_LIBRARY_UPDATED.send_event( CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData( content_library=ContentLibraryData(
@@ -954,7 +952,7 @@ def revert_changes(library_key):
last published version. last published version.
""" """
learning_package = ContentLibrary.objects.get_by_key(library_key).learning_package learning_package = ContentLibrary.objects.get_by_key(library_key).learning_package
publishing_api.reset_drafts_to_published(learning_package.id) authoring_api.reset_drafts_to_published(learning_package.id)
CONTENT_LIBRARY_UPDATED.send_event( CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData( content_library=ContentLibraryData(

View File

@@ -10,7 +10,7 @@ from openedx.core.djangoapps.content_libraries import api, permissions
from openedx.core.djangoapps.content_libraries.models import ContentLibrary from openedx.core.djangoapps.content_libraries.models import ContentLibrary
from openedx.core.djangoapps.xblock.api import LearningContext from openedx.core.djangoapps.xblock.api import LearningContext
from openedx_learning.core.components import api as components_api from openedx_learning.api import authoring as authoring_api
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -87,7 +87,7 @@ class LibraryContextImpl(LearningContext):
if learning_package is None: if learning_package is None:
return False return False
return components_api.component_exists_by_key( return authoring_api.component_exists_by_key(
learning_package.id, learning_package.id,
namespace='xblock.v1', namespace='xblock.v1',
type_name=usage_key.block_type, type_name=usage_key.block_type,

View File

@@ -57,7 +57,7 @@ from openedx.core.djangoapps.content_libraries.constants import (
LIBRARY_TYPES, COMPLEX, LICENSE_OPTIONS, LIBRARY_TYPES, COMPLEX, LICENSE_OPTIONS,
ALL_RIGHTS_RESERVED, ALL_RIGHTS_RESERVED,
) )
from openedx_learning.core.publishing.models import LearningPackage from openedx_learning.api.authoring_models import LearningPackage
from organizations.models import Organization # lint-amnesty, pylint: disable=wrong-import-order from organizations.models import Organization # lint-amnesty, pylint: disable=wrong-import-order
from .apps import ContentLibrariesConfig from .apps import ContentLibrariesConfig

View File

@@ -15,12 +15,10 @@ import threading
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from openedx_learning.core.components import api as components_api from openedx_learning.api import authoring as authoring_api
from openedx_learning.core.components.models import Component from openedx_learning.api.authoring_models import Component
from openedx_learning.core.publishing import api as publishing_api
from opaque_keys.edx.keys import UsageKeyV2 from opaque_keys.edx.keys import UsageKeyV2
from opaque_keys.edx.locator import BundleDefinitionLocator, LibraryUsageLocatorV2 from opaque_keys.edx.locator import BundleDefinitionLocator, LibraryUsageLocatorV2
from rest_framework.exceptions import NotFound from rest_framework.exceptions import NotFound
from xblock.core import XBlock from xblock.core import XBlock
from xblock.exceptions import NoSuchViewError from xblock.exceptions import NoSuchViewError
@@ -28,13 +26,10 @@ from xblock.plugin import PluginMissingError
from openedx.core.djangoapps.xblock.apps import get_xblock_app_config from openedx.core.djangoapps.xblock.apps import get_xblock_app_config
from openedx.core.djangoapps.xblock.learning_context.manager import get_learning_context_impl from openedx.core.djangoapps.xblock.learning_context.manager import get_learning_context_impl
from openedx.core.djangoapps.xblock.runtime.learning_core_runtime import ( from openedx.core.djangoapps.xblock.runtime.learning_core_runtime import (
LearningCoreFieldData, LearningCoreFieldData,
LearningCoreXBlockRuntime, LearningCoreXBlockRuntime,
) )
from openedx.core.djangoapps.xblock.runtime.runtime import XBlockRuntimeSystem as _XBlockRuntimeSystem from openedx.core.djangoapps.xblock.runtime.runtime import XBlockRuntimeSystem as _XBlockRuntimeSystem
from .utils import get_secure_token_for_xblock_handler, get_xblock_id_for_anonymous_user from .utils import get_secure_token_for_xblock_handler, get_xblock_id_for_anonymous_user
@@ -192,10 +187,10 @@ def get_component_from_usage_key(usage_key: UsageKeyV2) -> Component:
This is a lower-level function that will return a Component even if there is This is a lower-level function that will return a Component even if there is
no current draft version of that Component (because it's been soft-deleted). no current draft version of that Component (because it's been soft-deleted).
""" """
learning_package = publishing_api.get_learning_package_by_key( learning_package = authoring_api.get_learning_package_by_key(
str(usage_key.context_key) str(usage_key.context_key)
) )
return components_api.get_component_by_key( return authoring_api.get_component_by_key(
learning_package.id, learning_package.id,
namespace='xblock.v1', namespace='xblock.v1',
type_name=usage_key.block_type, type_name=usage_key.block_type,

View File

@@ -10,9 +10,7 @@ from datetime import datetime, timezone
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.transaction import atomic from django.db.transaction import atomic
from openedx_learning.core.components import api as components_api from openedx_learning.api import authoring as authoring_api
from openedx_learning.core.contents import api as contents_api
from openedx_learning.core.publishing import api as publishing_api
from lxml import etree from lxml import etree
@@ -239,16 +237,16 @@ class LearningCoreXBlockRuntime(XBlockRuntime):
usage_key = block.scope_ids.usage_id usage_key = block.scope_ids.usage_id
with atomic(): with atomic():
component = self._get_component_from_usage_key(usage_key) component = self._get_component_from_usage_key(usage_key)
block_media_type = contents_api.get_or_create_media_type( block_media_type = authoring_api.get_or_create_media_type(
f"application/vnd.openedx.xblock.v1.{usage_key.block_type}+xml" f"application/vnd.openedx.xblock.v1.{usage_key.block_type}+xml"
) )
content = contents_api.get_or_create_text_content( content = authoring_api.get_or_create_text_content(
component.learning_package_id, component.learning_package_id,
block_media_type.id, block_media_type.id,
text=serialized.olx_str, text=serialized.olx_str,
created=now, created=now,
) )
components_api.create_next_version( authoring_api.create_next_version(
component.pk, component.pk,
title=block.display_name, title=block.display_name,
content_to_replace={ content_to_replace={
@@ -267,9 +265,9 @@ class LearningCoreXBlockRuntime(XBlockRuntime):
TODO: This is the third place where we're implementing this. Figure out TODO: This is the third place where we're implementing this. Figure out
where the definitive place should be and have everything else call that. where the definitive place should be and have everything else call that.
""" """
learning_package = publishing_api.get_learning_package_by_key(str(usage_key.lib_key)) learning_package = authoring_api.get_learning_package_by_key(str(usage_key.lib_key))
try: try:
component = components_api.get_component_by_key( component = authoring_api.get_component_by_key(
learning_package.id, learning_package.id,
namespace='xblock.v1', namespace='xblock.v1',
type_name=usage_key.block_type, type_name=usage_key.block_type,

View File

@@ -97,7 +97,7 @@ libsass==0.10.0
click==8.1.6 click==8.1.6
# pinning this version to avoid updates while the library is being developed # pinning this version to avoid updates while the library is being developed
openedx-learning==0.9.4 openedx-learning==0.10.0
# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. # Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise.
openai<=0.28.1 openai<=0.28.1

View File

@@ -775,7 +775,7 @@ openedx-filters==1.8.1
# -r requirements/edx/kernel.in # -r requirements/edx/kernel.in
# lti-consumer-xblock # lti-consumer-xblock
# ora2 # ora2
openedx-learning==0.9.4 openedx-learning==0.10.0
# via # via
# -c requirements/edx/../constraints.txt # -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in # -r requirements/edx/kernel.in

View File

@@ -1331,7 +1331,7 @@ openedx-filters==1.8.1
# -r requirements/edx/testing.txt # -r requirements/edx/testing.txt
# lti-consumer-xblock # lti-consumer-xblock
# ora2 # ora2
openedx-learning==0.9.4 openedx-learning==0.10.0
# via # via
# -c requirements/edx/../constraints.txt # -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt # -r requirements/edx/doc.txt

View File

@@ -906,7 +906,7 @@ openedx-filters==1.8.1
# -r requirements/edx/base.txt # -r requirements/edx/base.txt
# lti-consumer-xblock # lti-consumer-xblock
# ora2 # ora2
openedx-learning==0.9.4 openedx-learning==0.10.0
# via # via
# -c requirements/edx/../constraints.txt # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt # -r requirements/edx/base.txt

View File

@@ -991,7 +991,7 @@ openedx-filters==1.8.1
# -r requirements/edx/base.txt # -r requirements/edx/base.txt
# lti-consumer-xblock # lti-consumer-xblock
# ora2 # ora2
openedx-learning==0.9.4 openedx-learning==0.10.0
# via # via
# -c requirements/edx/../constraints.txt # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt # -r requirements/edx/base.txt

View File

@@ -62,6 +62,7 @@ root_packages =
lms lms
cms cms
openedx openedx
openedx_learning
include_external_packages = True include_external_packages = True
contract_types = contract_types =
# Our custom contract which checks that we're only importing from 'api.py' # Our custom contract which checks that we're only importing from 'api.py'
@@ -185,3 +186,14 @@ allowed_modules =
# Only imports from api.py are allowed elsewhere in the code # Only imports from api.py are allowed elsewhere in the code
# See https://open-edx-proposals.readthedocs.io/en/latest/best-practices/oep-0049-django-app-patterns.html#api-py # See https://open-edx-proposals.readthedocs.io/en/latest/best-practices/oep-0049-django-app-patterns.html#api-py
api api
[importlinter:contract:3]
name = Do not import apps from openedx-learning (only import from openedx_learning.api.* and openedx_learning.lib.*).
type = forbidden
source_modules =
cms
lms
openedx
forbidden_modules =
openedx_learning.apps
allow_indirect_imports = True