diff --git a/openedx/core/djangoapps/content_libraries/views.py b/openedx/core/djangoapps/content_libraries/views.py index 12a262a934b8..df8d10190c64 100644 --- a/openedx/core/djangoapps/content_libraries/views.py +++ b/openedx/core/djangoapps/content_libraries/views.py @@ -710,6 +710,9 @@ class LibraryBlockOlxView(APIView): @convert_exceptions def get(self, request, usage_key_str): """ + DEPRECATED. Use get_block_olx_view() in xblock REST-API. + Can be removed post-Teak. + Get the block's OLX """ key = LibraryUsageLocatorV2.from_string(usage_key_str) diff --git a/openedx/core/djangoapps/xblock/api.py b/openedx/core/djangoapps/xblock/api.py index c0bda6ca5c63..a6f37dbd432e 100644 --- a/openedx/core/djangoapps/xblock/api.py +++ b/openedx/core/djangoapps/xblock/api.py @@ -34,7 +34,11 @@ ) from .data import CheckPerm, LatestVersion from .rest_api.url_converters import VersionConverter -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, + get_auto_latest_version, +) from .runtime.learning_core_runtime import LearningCoreXBlockRuntime @@ -211,7 +215,6 @@ def get_component_from_usage_key(usage_key: UsageKeyV2) -> Component: def get_block_olx( usage_key: UsageKeyV2, - user: UserType, *, version: int | LatestVersion = LatestVersion.AUTO ) -> str: @@ -219,8 +222,7 @@ def get_block_olx( Get the OLX source of the of the given Learning-Core-backed XBlock and a version. """ component = get_component_from_usage_key(usage_key) - runtime = get_runtime(user=user) - version = runtime.get_auto_latest_version(version) + version = get_auto_latest_version(version) if version == LatestVersion.DRAFT: component_version = component.versioning.draft @@ -240,6 +242,11 @@ def get_block_olx( return content.text +def get_block_draft_olx(usage_key: UsageKeyV2) -> str: + """ DEPRECATED. Use get_block_olx(). Can be removed post-Teak. """ + return get_block_olx(usage_key, version=LatestVersion.DRAFT) + + def render_block_view(block, view_name, user): # pylint: disable=unused-argument """ Get the HTML, JS, and CSS needed to render the given XBlock view. diff --git a/openedx/core/djangoapps/xblock/rest_api/serializers.py b/openedx/core/djangoapps/xblock/rest_api/serializers.py new file mode 100644 index 000000000000..c5971f0895d7 --- /dev/null +++ b/openedx/core/djangoapps/xblock/rest_api/serializers.py @@ -0,0 +1,10 @@ +""" +Serializers for the xblock REST API +""" +from rest_framework import serializers + +class XBlockOlxSerializer(serializers.Serializer): + """ + Serializer for representing an XBlock's OLX + """ + olx = serializers.CharField() diff --git a/openedx/core/djangoapps/xblock/rest_api/urls.py b/openedx/core/djangoapps/xblock/rest_api/urls.py index ee41b43f5b39..1ed169b4a525 100644 --- a/openedx/core/djangoapps/xblock/rest_api/urls.py +++ b/openedx/core/djangoapps/xblock/rest_api/urls.py @@ -19,6 +19,8 @@ path('', views.block_metadata), # get/post full json fields of an XBlock: path('fields/', views.BlockFieldsView.as_view()), + # Get the OLX source code of the specified block + path('fields/olx/', views.get_block_olx_view), # render one of this XBlock's views (e.g. student_view) path('view//', views.render_block_view), # get the URL needed to call this XBlock's handlers diff --git a/openedx/core/djangoapps/xblock/rest_api/views.py b/openedx/core/djangoapps/xblock/rest_api/views.py index d69edcbfd51c..6e709b9cef19 100644 --- a/openedx/core/djangoapps/xblock/rest_api/views.py +++ b/openedx/core/djangoapps/xblock/rest_api/views.py @@ -33,9 +33,11 @@ get_handler_url as _get_handler_url, load_block, render_block_view as _render_block_view, + get_block_olx, ) from ..utils import validate_secure_token_for_xblock_handler from .url_converters import VersionConverter +from .serializers import XBlockOlxSerializer User = get_user_model() @@ -213,6 +215,20 @@ def xblock_handler( return response +@api_view(['GET']) +@view_auth_classes(is_authenticated=False) +def get_block_olx_view( + request, + usage_key: UsageKeyV2, + version: LatestVersion | int = LatestVersion.AUTO, +): + context_impl = get_learning_context_impl(usage_key) + if not context_impl.can_view_block_for_editing(request.user, usage_key): + raise PermissionDenied(f"You don't have permission to access the OLX of component '{usage_key}'.") + olx = get_block_olx(usage_key, version=version) + return Response(XBlockOlxSerializer({"olx": olx}).data) + + def cors_allow_xblock_handler(sender, request, **kwargs): # lint-amnesty, pylint: disable=unused-argument """ Sandboxed XBlocks need to be able to call XBlock handlers via POST, diff --git a/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py b/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py index 79dcc7a2f693..dde2084e54cd 100644 --- a/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py @@ -24,6 +24,7 @@ from openedx.core.lib.xblock_serializer.api import serialize_modulestore_block_for_learning_core from openedx.core.lib.xblock_serializer.data import StaticFile from ..data import AuthoredDataMode, LatestVersion +from ..utils import get_auto_latest_version from ..learning_context.manager import get_learning_context_impl from .runtime import XBlockRuntime @@ -178,7 +179,7 @@ def get_block(self, usage_key, for_parent=None, *, version: int | LatestVersion # just get it the easy way. component = self._get_component_from_usage_key(usage_key) - version = self.get_auto_latest_version(version) + version = get_auto_latest_version(version) if self.authored_data_mode == AuthoredDataMode.STRICTLY_PUBLISHED and version != LatestVersion.PUBLISHED: raise ValidationError("This runtime only allows accessing the published version of components") if version == LatestVersion.DRAFT: @@ -312,18 +313,6 @@ def save_block(self, block): ) self.authored_data_store.mark_unchanged(block) - def get_auto_latest_version(self, version: int | LatestVersion) -> int | LatestVersion: - """ - Gets the actual LatesVersion if is `LatestVersion.AUTO`; - otherwise, returns the same value. - """ - if version == LatestVersion.AUTO: - if self.authored_data_mode == AuthoredDataMode.DEFAULT_DRAFT: - return LatestVersion.DRAFT - else: - return LatestVersion.PUBLISHED - return version - def _get_component_from_usage_key(self, usage_key): """ Note that Components aren't ever really truly deleted, so this will diff --git a/openedx/core/djangoapps/xblock/utils.py b/openedx/core/djangoapps/xblock/utils.py index 375bb9d21450..882037c6b3b1 100644 --- a/openedx/core/djangoapps/xblock/utils.py +++ b/openedx/core/djangoapps/xblock/utils.py @@ -11,6 +11,9 @@ import crum from django.conf import settings +from openedx.core.djangoapps.xblock.apps import get_xblock_app_config + +from .data import AuthoredDataMode, LatestVersion def get_secure_token_for_xblock_handler(user_id, block_key_str, time_idx=0): """ @@ -167,3 +170,14 @@ def get_xblock_id_for_anonymous_user(user): return current_request.session["xblock_id_for_anonymous_user"] else: raise RuntimeError("Cannot get a user ID for an anonymous user outside of an HTTP request context.") + + +def get_auto_latest_version(version: int | LatestVersion) -> int | LatestVersion: + """ + Gets the actual LatestVersion if is `LatestVersion.AUTO`; + otherwise, returns the same value. + """ + authored_data_mode = get_xblock_app_config().get_runtime_params()["authored_data_mode"] + if version == LatestVersion.AUTO: + version = LatestVersion.DRAFT if authored_data_mode == AuthoredDataMode.DEFAULT_DRAFT else LatestVersion.PUBLISHED + return version \ No newline at end of file